1633 lines
76 KiB
C#
1633 lines
76 KiB
C#
/*
|
|
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
|
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using NUnit.Framework;
|
|
|
|
using QuantConnect.Securities.Option;
|
|
using QuantConnect.Securities.Option.StrategyMatcher;
|
|
|
|
namespace QuantConnect.Tests.Common.Securities.Options
|
|
{
|
|
[TestFixture]
|
|
public class OptionStrategiesTests
|
|
{
|
|
[Test]
|
|
public void BuildsCoveredCallStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.CoveredCall(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.CoveredCall.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(1, strategy.OptionLegs.Count);
|
|
var optionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Call, optionLeg.Right);
|
|
Assert.AreEqual(strike, optionLeg.Strike);
|
|
Assert.AreEqual(expiration, optionLeg.Expiration);
|
|
Assert.AreEqual(-1, optionLeg.Quantity);
|
|
|
|
Assert.AreEqual(1, strategy.UnderlyingLegs.Count);
|
|
var underlyingLeg = strategy.UnderlyingLegs[0];
|
|
Assert.AreEqual(underlying, underlyingLeg.Symbol);
|
|
Assert.AreEqual(100, underlyingLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsProtectiveCallStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ProtectiveCall(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ProtectiveCall.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(1, strategy.OptionLegs.Count);
|
|
var optionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Call, optionLeg.Right);
|
|
Assert.AreEqual(strike, optionLeg.Strike);
|
|
Assert.AreEqual(expiration, optionLeg.Expiration);
|
|
Assert.AreEqual(1, optionLeg.Quantity);
|
|
|
|
Assert.AreEqual(1, strategy.UnderlyingLegs.Count);
|
|
var underlyingLeg = strategy.UnderlyingLegs[0];
|
|
Assert.AreEqual(underlying, underlyingLeg.Symbol);
|
|
Assert.AreEqual(-100, underlyingLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsCoveredPutStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.CoveredPut(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.CoveredPut.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(1, strategy.OptionLegs.Count);
|
|
var optionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Put, optionLeg.Right);
|
|
Assert.AreEqual(strike, optionLeg.Strike);
|
|
Assert.AreEqual(expiration, optionLeg.Expiration);
|
|
Assert.AreEqual(-1, optionLeg.Quantity);
|
|
|
|
Assert.AreEqual(1, strategy.UnderlyingLegs.Count);
|
|
var underlyingLeg = strategy.UnderlyingLegs[0];
|
|
Assert.AreEqual(underlying, underlyingLeg.Symbol);
|
|
Assert.AreEqual(-100, underlyingLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsProtectivePutStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ProtectivePut(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ProtectivePut.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(1, strategy.OptionLegs.Count);
|
|
var optionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Put, optionLeg.Right);
|
|
Assert.AreEqual(strike, optionLeg.Strike);
|
|
Assert.AreEqual(expiration, optionLeg.Expiration);
|
|
Assert.AreEqual(1, optionLeg.Quantity);
|
|
|
|
Assert.AreEqual(1, strategy.UnderlyingLegs.Count);
|
|
var underlyingLeg = strategy.UnderlyingLegs[0];
|
|
Assert.AreEqual(underlying, underlyingLeg.Symbol);
|
|
Assert.AreEqual(100, underlyingLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsProtectiveCollarStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var callStrike = 350m;
|
|
var putStrike = 300m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ProtectiveCollar(canonicalOptionSymbol, callStrike, putStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ProtectiveCollar.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
|
|
var callOptionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Call, callOptionLeg.Right);
|
|
Assert.AreEqual(callStrike, callOptionLeg.Strike);
|
|
Assert.AreEqual(expiration, callOptionLeg.Expiration);
|
|
Assert.AreEqual(-1, callOptionLeg.Quantity);
|
|
|
|
var putOptionLeg = strategy.OptionLegs[1];
|
|
Assert.AreEqual(OptionRight.Put, putOptionLeg.Right);
|
|
Assert.AreEqual(putStrike, putOptionLeg.Strike);
|
|
Assert.AreEqual(expiration, putOptionLeg.Expiration);
|
|
Assert.AreEqual(1, putOptionLeg.Quantity);
|
|
|
|
Assert.AreEqual(1, strategy.UnderlyingLegs.Count);
|
|
var underlyingLeg = strategy.UnderlyingLegs[0];
|
|
Assert.AreEqual(underlying, underlyingLeg.Symbol);
|
|
Assert.AreEqual(100, underlyingLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsConversionStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.Conversion(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.Conversion.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
|
|
var callOptionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Call, callOptionLeg.Right);
|
|
Assert.AreEqual(strike, callOptionLeg.Strike);
|
|
Assert.AreEqual(expiration, callOptionLeg.Expiration);
|
|
Assert.AreEqual(-1, callOptionLeg.Quantity);
|
|
|
|
var putOptionLeg = strategy.OptionLegs[1];
|
|
Assert.AreEqual(OptionRight.Put, putOptionLeg.Right);
|
|
Assert.AreEqual(strike, putOptionLeg.Strike);
|
|
Assert.AreEqual(expiration, putOptionLeg.Expiration);
|
|
Assert.AreEqual(1, putOptionLeg.Quantity);
|
|
|
|
Assert.AreEqual(1, strategy.UnderlyingLegs.Count);
|
|
var underlyingLeg = strategy.UnderlyingLegs[0];
|
|
Assert.AreEqual(underlying, underlyingLeg.Symbol);
|
|
Assert.AreEqual(100, underlyingLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsReverseConversionStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ReverseConversion(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ReverseConversion.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
|
|
var callOptionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Call, callOptionLeg.Right);
|
|
Assert.AreEqual(strike, callOptionLeg.Strike);
|
|
Assert.AreEqual(expiration, callOptionLeg.Expiration);
|
|
Assert.AreEqual(1, callOptionLeg.Quantity);
|
|
|
|
var putOptionLeg = strategy.OptionLegs[1];
|
|
Assert.AreEqual(OptionRight.Put, putOptionLeg.Right);
|
|
Assert.AreEqual(strike, putOptionLeg.Strike);
|
|
Assert.AreEqual(expiration, putOptionLeg.Expiration);
|
|
Assert.AreEqual(-1, putOptionLeg.Quantity);
|
|
|
|
Assert.AreEqual(1, strategy.UnderlyingLegs.Count);
|
|
var underlyingLeg = strategy.UnderlyingLegs[0];
|
|
Assert.AreEqual(underlying, underlyingLeg.Symbol);
|
|
Assert.AreEqual(-100, underlyingLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsNakedCallStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.NakedCall(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.NakedCall.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(1, strategy.OptionLegs.Count);
|
|
var optionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Call, optionLeg.Right);
|
|
Assert.AreEqual(strike, optionLeg.Strike);
|
|
Assert.AreEqual(expiration, optionLeg.Expiration);
|
|
Assert.AreEqual(-1, optionLeg.Quantity);
|
|
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsStraddleStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.Straddle(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.Straddle.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
|
|
var callLeg = strategy.OptionLegs.Single(leg => leg.Right == OptionRight.Call);
|
|
Assert.AreEqual(strike, callLeg.Strike);
|
|
Assert.AreEqual(expiration, callLeg.Expiration);
|
|
Assert.AreEqual(1, callLeg.Quantity);
|
|
|
|
var putLeg = strategy.OptionLegs.Single(leg => leg.Right == OptionRight.Put);
|
|
Assert.AreEqual(strike, putLeg.Strike);
|
|
Assert.AreEqual(expiration, putLeg.Expiration);
|
|
Assert.AreEqual(1, putLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortStraddleStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortStraddle(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortStraddle.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
|
|
var callLeg = strategy.OptionLegs.Single(leg => leg.Right == OptionRight.Call);
|
|
Assert.AreEqual(strike, callLeg.Strike);
|
|
Assert.AreEqual(expiration, callLeg.Expiration);
|
|
Assert.AreEqual(-1, callLeg.Quantity);
|
|
|
|
var putLeg = strategy.OptionLegs.Single(leg => leg.Right == OptionRight.Put);
|
|
Assert.AreEqual(strike, putLeg.Strike);
|
|
Assert.AreEqual(expiration, putLeg.Expiration);
|
|
Assert.AreEqual(-1, putLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingStrangleStrategyWithInvalidStrikePrices()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
// Same strikes
|
|
var callStrike = 350m;
|
|
var putStrike = 350m;
|
|
Assert.Throws<ArgumentException>(() => OptionStrategies.Strangle(canonicalOptionSymbol, callStrike, putStrike, expiration));
|
|
|
|
// Call strike < put strike
|
|
callStrike = 340m;
|
|
putStrike = 350m;
|
|
Assert.Throws<ArgumentException>(() => OptionStrategies.Strangle(canonicalOptionSymbol, callStrike, putStrike, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsStrangleStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var callStrike = 350m;
|
|
var putStrike = 340m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.Strangle(canonicalOptionSymbol, callStrike, putStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.Strangle.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
|
|
var callLeg = strategy.OptionLegs.Single(leg => leg.Right == OptionRight.Call);
|
|
Assert.AreEqual(callStrike, callLeg.Strike);
|
|
Assert.AreEqual(expiration, callLeg.Expiration);
|
|
Assert.AreEqual(1, callLeg.Quantity);
|
|
|
|
var putLeg = strategy.OptionLegs.Single(leg => leg.Right == OptionRight.Put);
|
|
Assert.AreEqual(putStrike, putLeg.Strike);
|
|
Assert.AreEqual(expiration, putLeg.Expiration);
|
|
Assert.AreEqual(1, putLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortStrangleStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var callStrike = 350m;
|
|
var putStrike = 340m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortStrangle(canonicalOptionSymbol, callStrike, putStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortStrangle.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
|
|
var callLeg = strategy.OptionLegs.Single(leg => leg.Right == OptionRight.Call);
|
|
Assert.AreEqual(callStrike, callLeg.Strike);
|
|
Assert.AreEqual(expiration, callLeg.Expiration);
|
|
Assert.AreEqual(-1, callLeg.Quantity);
|
|
|
|
var putLeg = strategy.OptionLegs.Single(leg => leg.Right == OptionRight.Put);
|
|
Assert.AreEqual(putStrike, putLeg.Strike);
|
|
Assert.AreEqual(expiration, putLeg.Expiration);
|
|
Assert.AreEqual(-1, putLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsNakedPutStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.NakedPut(canonicalOptionSymbol, strike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.NakedPut.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(1, strategy.OptionLegs.Count);
|
|
var optionLeg = strategy.OptionLegs[0];
|
|
Assert.AreEqual(OptionRight.Put, optionLeg.Right);
|
|
Assert.AreEqual(strike, optionLeg.Strike);
|
|
Assert.AreEqual(expiration, optionLeg.Expiration);
|
|
Assert.AreEqual(-1, optionLeg.Quantity);
|
|
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingButterflyCallStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var higherStrike = 350m;
|
|
var lowerStrike = 300m;
|
|
var middleStrike = 325m;
|
|
|
|
// Unordered strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyCall(canonicalOptionSymbol, lowerStrike, middleStrike, higherStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyCall(canonicalOptionSymbol, lowerStrike, higherStrike, middleStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyCall(canonicalOptionSymbol, middleStrike, lowerStrike, higherStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyCall(canonicalOptionSymbol, middleStrike, higherStrike, lowerStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyCall(canonicalOptionSymbol, higherStrike, lowerStrike, middleStrike, expiration));
|
|
|
|
// Uneven inter-strike distances
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyCall(canonicalOptionSymbol, lowerStrike, middleStrike + 1, higherStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyCall(canonicalOptionSymbol, lowerStrike, middleStrike - 1, higherStrike, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsButterflyCallStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var higherStrike = 350m;
|
|
var lowerStrike = 300m;
|
|
var middleStrike = 325m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ButterflyCall(canonicalOptionSymbol, higherStrike, middleStrike, lowerStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ButterflyCall.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(3, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var higherStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == higherStrike);
|
|
Assert.AreEqual(OptionRight.Call, higherStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, higherStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, higherStrikeLeg.Quantity);
|
|
|
|
var middleStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == middleStrike);
|
|
Assert.AreEqual(OptionRight.Call, middleStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, middleStrikeLeg.Expiration);
|
|
Assert.AreEqual(-2, middleStrikeLeg.Quantity);
|
|
|
|
var lowerStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == lowerStrike);
|
|
Assert.AreEqual(OptionRight.Call, lowerStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowerStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, lowerStrikeLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortButterflyCallStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var higherStrike = 350m;
|
|
var lowerStrike = 300m;
|
|
var middleStrike = 325m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortButterflyCall(canonicalOptionSymbol, higherStrike, middleStrike, lowerStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortButterflyCall.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(3, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var higherStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == higherStrike);
|
|
Assert.AreEqual(OptionRight.Call, higherStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, higherStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, higherStrikeLeg.Quantity);
|
|
|
|
var middleStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == middleStrike);
|
|
Assert.AreEqual(OptionRight.Call, middleStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, middleStrikeLeg.Expiration);
|
|
Assert.AreEqual(2, middleStrikeLeg.Quantity);
|
|
|
|
var lowerStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == lowerStrike);
|
|
Assert.AreEqual(OptionRight.Call, lowerStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowerStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, lowerStrikeLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingButterflyPutStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var higherStrike = 350m;
|
|
var lowerStrike = 300m;
|
|
var middleStrike = 325m;
|
|
|
|
// Unordered strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyPut(canonicalOptionSymbol, lowerStrike, middleStrike, higherStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyPut(canonicalOptionSymbol, lowerStrike, higherStrike, middleStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyPut(canonicalOptionSymbol, middleStrike, lowerStrike, higherStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyPut(canonicalOptionSymbol, middleStrike, higherStrike, lowerStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyPut(canonicalOptionSymbol, higherStrike, lowerStrike, middleStrike, expiration));
|
|
|
|
// Uneven inter-strike distances
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyPut(canonicalOptionSymbol, lowerStrike, middleStrike + 1, higherStrike, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ButterflyPut(canonicalOptionSymbol, lowerStrike, middleStrike - 1, higherStrike, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsButterflyPutStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var higherStrike = 350m;
|
|
var lowerStrike = 300m;
|
|
var middleStrike = 325m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ButterflyPut(canonicalOptionSymbol, higherStrike, middleStrike, lowerStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ButterflyPut.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(3, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var higherStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == higherStrike);
|
|
Assert.AreEqual(OptionRight.Put, higherStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, higherStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, higherStrikeLeg.Quantity);
|
|
|
|
var middleStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == middleStrike);
|
|
Assert.AreEqual(OptionRight.Put, middleStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, middleStrikeLeg.Expiration);
|
|
Assert.AreEqual(-2, middleStrikeLeg.Quantity);
|
|
|
|
var lowerStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == lowerStrike);
|
|
Assert.AreEqual(OptionRight.Put, lowerStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowerStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, lowerStrikeLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortButterflyPutStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var higherStrike = 350m;
|
|
var lowerStrike = 300m;
|
|
var middleStrike = 325m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortButterflyPut(canonicalOptionSymbol, higherStrike, middleStrike, lowerStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortButterflyPut.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(3, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var higherStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == higherStrike);
|
|
Assert.AreEqual(OptionRight.Put, higherStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, higherStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, higherStrikeLeg.Quantity);
|
|
|
|
var middleStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == middleStrike);
|
|
Assert.AreEqual(OptionRight.Put, middleStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, middleStrikeLeg.Expiration);
|
|
Assert.AreEqual(2, middleStrikeLeg.Quantity);
|
|
|
|
var lowerStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == lowerStrike);
|
|
Assert.AreEqual(OptionRight.Put, lowerStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowerStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, lowerStrikeLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingCallCalendarSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
// Invalid expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.CallCalendarSpread(canonicalOptionSymbol, strike, DateTime.MinValue, farExpiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.CallCalendarSpread(canonicalOptionSymbol, strike, DateTime.MaxValue, farExpiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.CallCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, DateTime.MinValue));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.CallCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, DateTime.MaxValue));
|
|
|
|
// Switched expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.CallCalendarSpread(canonicalOptionSymbol, strike, farExpiration, nearExpiration));
|
|
|
|
// Same expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.CallCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, nearExpiration));
|
|
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsCallCalendarSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
var strategy = OptionStrategies.CallCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, farExpiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.CallCalendarSpread.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var nearExpirationLeg = strategy.OptionLegs.Single(x => x.Expiration == nearExpiration);
|
|
Assert.AreEqual(OptionRight.Call, nearExpirationLeg.Right);
|
|
Assert.AreEqual(nearExpiration, nearExpirationLeg.Expiration);
|
|
Assert.AreEqual(-1, nearExpirationLeg.Quantity);
|
|
|
|
var farExpirationLeg = strategy.OptionLegs.Single(x => x.Expiration == farExpiration);
|
|
Assert.AreEqual(OptionRight.Call, farExpirationLeg.Right);
|
|
Assert.AreEqual(farExpiration, farExpirationLeg.Expiration);
|
|
Assert.AreEqual(1, farExpirationLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortCallCalendarSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
var strategy = OptionStrategies.ShortCallCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, farExpiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortCallCalendarSpread.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var nearExpirationLeg = strategy.OptionLegs.Single(x => x.Expiration == nearExpiration);
|
|
Assert.AreEqual(OptionRight.Call, nearExpirationLeg.Right);
|
|
Assert.AreEqual(nearExpiration, nearExpirationLeg.Expiration);
|
|
Assert.AreEqual(1, nearExpirationLeg.Quantity);
|
|
|
|
var farExpirationLeg = strategy.OptionLegs.Single(x => x.Expiration == farExpiration);
|
|
Assert.AreEqual(OptionRight.Call, farExpirationLeg.Right);
|
|
Assert.AreEqual(farExpiration, farExpirationLeg.Expiration);
|
|
Assert.AreEqual(-1, farExpirationLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingPutCalendarSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
// Invalid expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.PutCalendarSpread(canonicalOptionSymbol, strike, DateTime.MinValue, farExpiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.PutCalendarSpread(canonicalOptionSymbol, strike, DateTime.MaxValue, farExpiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.PutCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, DateTime.MinValue));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.PutCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, DateTime.MaxValue));
|
|
|
|
// Switched expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.PutCalendarSpread(canonicalOptionSymbol, strike, farExpiration, nearExpiration));
|
|
|
|
// Same expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.PutCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, nearExpiration));
|
|
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsPutCalendarSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
var strategy = OptionStrategies.PutCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, farExpiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.PutCalendarSpread.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var nearExpirationLeg = strategy.OptionLegs.Single(x => x.Expiration == nearExpiration);
|
|
Assert.AreEqual(OptionRight.Put, nearExpirationLeg.Right);
|
|
Assert.AreEqual(nearExpiration, nearExpirationLeg.Expiration);
|
|
Assert.AreEqual(-1, nearExpirationLeg.Quantity);
|
|
|
|
var farExpirationLeg = strategy.OptionLegs.Single(x => x.Expiration == farExpiration);
|
|
Assert.AreEqual(OptionRight.Put, farExpirationLeg.Right);
|
|
Assert.AreEqual(farExpiration, farExpirationLeg.Expiration);
|
|
Assert.AreEqual(1, farExpirationLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortPutCalendarSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
var strategy = OptionStrategies.ShortPutCalendarSpread(canonicalOptionSymbol, strike, nearExpiration, farExpiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortPutCalendarSpread.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var nearExpirationLeg = strategy.OptionLegs.Single(x => x.Expiration == nearExpiration);
|
|
Assert.AreEqual(OptionRight.Put, nearExpirationLeg.Right);
|
|
Assert.AreEqual(nearExpiration, nearExpirationLeg.Expiration);
|
|
Assert.AreEqual(1, nearExpirationLeg.Quantity);
|
|
|
|
var farExpirationLeg = strategy.OptionLegs.Single(x => x.Expiration == farExpiration);
|
|
Assert.AreEqual(OptionRight.Put, farExpirationLeg.Right);
|
|
Assert.AreEqual(farExpiration, farExpirationLeg.Expiration);
|
|
Assert.AreEqual(-1, farExpirationLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingIronButterflyStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 326m;
|
|
var strike4 = 350m;
|
|
|
|
// Unordered and non-equal interval strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronButterfly(canonicalOptionSymbol, strike1, strike4, strike2, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronButterfly(canonicalOptionSymbol, strike2, strike1, strike4, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronButterfly(canonicalOptionSymbol, strike2, strike4, strike1, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronButterfly(canonicalOptionSymbol, strike4, strike1, strike2, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronButterfly(canonicalOptionSymbol, strike4, strike2, strike1, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronButterfly(canonicalOptionSymbol, strike1, strike3, strike4, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsIronButterflyStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.IronButterfly(canonicalOptionSymbol, strike1, strike2, strike3, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.IronButterfly.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(4, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var longPutLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Put, longPutLeg.Right);
|
|
Assert.AreEqual(expiration, longPutLeg.Expiration);
|
|
Assert.AreEqual(+1, longPutLeg.Quantity);
|
|
|
|
var shortPutLeg = strategy.OptionLegs.Single(x => x.Strike == strike2 && x.Right == OptionRight.Put);
|
|
Assert.AreEqual(expiration, shortPutLeg.Expiration);
|
|
Assert.AreEqual(-1, shortPutLeg.Quantity);
|
|
|
|
var shortCallLeg = strategy.OptionLegs.Single(x => x.Strike == strike2 && x.Right == OptionRight.Call);
|
|
Assert.AreEqual(expiration, shortCallLeg.Expiration);
|
|
Assert.AreEqual(-1, shortCallLeg.Quantity);
|
|
|
|
var longCallLeg = strategy.OptionLegs.Single(x => x.Strike == strike3);
|
|
Assert.AreEqual(OptionRight.Call, longCallLeg.Right);
|
|
Assert.AreEqual(expiration, longCallLeg.Expiration);
|
|
Assert.AreEqual(+1, longCallLeg.Quantity);
|
|
}
|
|
|
|
|
|
[Test]
|
|
public void BuildsShortIronButterflyStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortIronButterfly(canonicalOptionSymbol, strike1, strike2, strike3, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortIronButterfly.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(4, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var shortPutLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Put, shortPutLeg.Right);
|
|
Assert.AreEqual(expiration, shortPutLeg.Expiration);
|
|
Assert.AreEqual(-1, shortPutLeg.Quantity);
|
|
|
|
var longPutLeg = strategy.OptionLegs.Single(x => x.Strike == strike2 && x.Right == OptionRight.Put);
|
|
Assert.AreEqual(expiration, longPutLeg.Expiration);
|
|
Assert.AreEqual(1, longPutLeg.Quantity);
|
|
|
|
var longCallLeg = strategy.OptionLegs.Single(x => x.Strike == strike2 && x.Right == OptionRight.Call);
|
|
Assert.AreEqual(expiration, longCallLeg.Expiration);
|
|
Assert.AreEqual(1, longCallLeg.Quantity);
|
|
|
|
var shortCallLeg = strategy.OptionLegs.Single(x => x.Strike == strike3);
|
|
Assert.AreEqual(OptionRight.Call, shortCallLeg.Right);
|
|
Assert.AreEqual(expiration, shortCallLeg.Expiration);
|
|
Assert.AreEqual(-1, shortCallLeg.Quantity);
|
|
}
|
|
[Test]
|
|
public void FailsBuildingIronCondorStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
var strike4 = 375m;
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronCondor(canonicalOptionSymbol, strike4, strike3, strike2, strike1, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronCondor(canonicalOptionSymbol, strike1, strike1, strike3, strike4, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronCondor(canonicalOptionSymbol, strike2, strike1, strike3, strike4, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronCondor(canonicalOptionSymbol, strike1, strike3, strike2, strike4, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.IronCondor(canonicalOptionSymbol, strike1, strike2, strike4, strike3, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsIronCondorStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
var strike4 = 375m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.IronCondor(canonicalOptionSymbol, strike1, strike2, strike3, strike4, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.IronCondor.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(4, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var longPutLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Put, longPutLeg.Right);
|
|
Assert.AreEqual(expiration, longPutLeg.Expiration);
|
|
Assert.AreEqual(1, longPutLeg.Quantity);
|
|
|
|
var shortPutLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Put, shortPutLeg.Right);
|
|
Assert.AreEqual(expiration, shortPutLeg.Expiration);
|
|
Assert.AreEqual(-1, shortPutLeg.Quantity);
|
|
|
|
var shortCallLeg = strategy.OptionLegs.Single(x => x.Strike == strike3);
|
|
Assert.AreEqual(OptionRight.Call, shortCallLeg.Right);
|
|
Assert.AreEqual(expiration, shortCallLeg.Expiration);
|
|
Assert.AreEqual(-1, shortCallLeg.Quantity);
|
|
|
|
var longCallLeg = strategy.OptionLegs.Single(x => x.Strike == strike4);
|
|
Assert.AreEqual(OptionRight.Call, longCallLeg.Right);
|
|
Assert.AreEqual(expiration, longCallLeg.Expiration);
|
|
Assert.AreEqual(1, longCallLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortIronCondorStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
var strike4 = 375m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortIronCondor(canonicalOptionSymbol, strike1, strike2, strike3, strike4, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortIronCondor.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(4, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var shortPutLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Put, shortPutLeg.Right);
|
|
Assert.AreEqual(expiration, shortPutLeg.Expiration);
|
|
Assert.AreEqual(-1, shortPutLeg.Quantity);
|
|
|
|
var longPutLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Put, longPutLeg.Right);
|
|
Assert.AreEqual(expiration, longPutLeg.Expiration);
|
|
Assert.AreEqual(+1, longPutLeg.Quantity);
|
|
|
|
var longCallLeg = strategy.OptionLegs.Single(x => x.Strike == strike3);
|
|
Assert.AreEqual(OptionRight.Call, longCallLeg.Right);
|
|
Assert.AreEqual(expiration, longCallLeg.Expiration);
|
|
Assert.AreEqual(+1, longCallLeg.Quantity);
|
|
|
|
var shortCallLeg = strategy.OptionLegs.Single(x => x.Strike == strike4);
|
|
Assert.AreEqual(OptionRight.Call, shortCallLeg.Right);
|
|
Assert.AreEqual(expiration, shortCallLeg.Expiration);
|
|
Assert.AreEqual(-1, shortCallLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingBoxSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BoxSpread(canonicalOptionSymbol, strike1, strike2, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BoxSpread(canonicalOptionSymbol, strike1, strike1, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsBoxSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var higherStrike = 320m;
|
|
var lowerStrike = 300m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.BoxSpread(canonicalOptionSymbol, higherStrike, lowerStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.BoxSpread.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(4, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var longPutLeg = strategy.OptionLegs.Single(x => x.Right == OptionRight.Put && x.Strike == higherStrike);
|
|
Assert.AreEqual(expiration, longPutLeg.Expiration);
|
|
Assert.AreEqual(1, longPutLeg.Quantity);
|
|
|
|
var shortPutLeg = strategy.OptionLegs.Single(x => x.Right == OptionRight.Put && x.Strike == lowerStrike);
|
|
Assert.AreEqual(expiration, shortPutLeg.Expiration);
|
|
Assert.AreEqual(-1, shortPutLeg.Quantity);
|
|
|
|
var shortCallLeg = strategy.OptionLegs.Single(x => x.Right == OptionRight.Call && x.Strike == higherStrike);
|
|
Assert.AreEqual(expiration, shortCallLeg.Expiration);
|
|
Assert.AreEqual(-1, shortCallLeg.Quantity);
|
|
|
|
var longCallLeg = strategy.OptionLegs.Single(x => x.Right == OptionRight.Call && x.Strike == lowerStrike);
|
|
Assert.AreEqual(expiration, longCallLeg.Expiration);
|
|
Assert.AreEqual(1, longCallLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortBoxSpreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var higherStrike = 320m;
|
|
var lowerStrike = 300m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortBoxSpread(canonicalOptionSymbol, higherStrike, lowerStrike, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortBoxSpread.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(4, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var longPutLeg = strategy.OptionLegs.Single(x => x.Right == OptionRight.Put && x.Strike == lowerStrike);
|
|
Assert.AreEqual(expiration, longPutLeg.Expiration);
|
|
Assert.AreEqual(1, longPutLeg.Quantity);
|
|
|
|
var shortPutLeg = strategy.OptionLegs.Single(x => x.Right == OptionRight.Put && x.Strike == higherStrike);
|
|
Assert.AreEqual(expiration, shortPutLeg.Expiration);
|
|
Assert.AreEqual(-1, shortPutLeg.Quantity);
|
|
|
|
var shortCallLeg = strategy.OptionLegs.Single(x => x.Right == OptionRight.Call && x.Strike == lowerStrike);
|
|
Assert.AreEqual(expiration, shortCallLeg.Expiration);
|
|
Assert.AreEqual(-1, shortCallLeg.Quantity);
|
|
|
|
var longCallLeg = strategy.OptionLegs.Single(x => x.Right == OptionRight.Call && x.Strike == higherStrike);
|
|
Assert.AreEqual(expiration, longCallLeg.Expiration);
|
|
Assert.AreEqual(1, longCallLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingJellyRollStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
// Invalid expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.JellyRoll(canonicalOptionSymbol, strike, DateTime.MinValue, farExpiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.JellyRoll(canonicalOptionSymbol, strike, DateTime.MaxValue, farExpiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.JellyRoll(canonicalOptionSymbol, strike, nearExpiration, DateTime.MinValue));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.JellyRoll(canonicalOptionSymbol, strike, nearExpiration, DateTime.MaxValue));
|
|
|
|
// Switched expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.JellyRoll(canonicalOptionSymbol, strike, farExpiration, nearExpiration));
|
|
|
|
// Same expiration dates
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.JellyRoll(canonicalOptionSymbol, strike, nearExpiration, nearExpiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsJellyRollStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
var strategy = OptionStrategies.JellyRoll(canonicalOptionSymbol, strike, nearExpiration, farExpiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.JellyRoll.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(4, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var nearExpirationCall = strategy.OptionLegs.Single(x =>
|
|
x.Expiration == nearExpiration && x.Right == OptionRight.Call);
|
|
Assert.AreEqual(strike, nearExpirationCall.Strike);
|
|
Assert.AreEqual(-1, nearExpirationCall.Quantity);
|
|
|
|
var farExpirationCall = strategy.OptionLegs.Single(x =>
|
|
x.Expiration == farExpiration && x.Right == OptionRight.Call);
|
|
Assert.AreEqual(strike, farExpirationCall.Strike);
|
|
Assert.AreEqual(+1, farExpirationCall.Quantity);
|
|
|
|
var nearExpirationPut = strategy.OptionLegs.Single(x =>
|
|
x.Expiration == nearExpiration && x.Right == OptionRight.Put);
|
|
Assert.AreEqual(strike, nearExpirationPut.Strike);
|
|
Assert.AreEqual(+1, nearExpirationPut.Quantity);
|
|
|
|
var farExpirationPut = strategy.OptionLegs.Single(x =>
|
|
x.Expiration == farExpiration && x.Right == OptionRight.Put);
|
|
Assert.AreEqual(strike, farExpirationPut.Strike);
|
|
Assert.AreEqual(-1, farExpirationPut.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortJellyRollStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike = 350m;
|
|
var nearExpiration = new DateTime(2023, 08, 18);
|
|
var farExpiration = new DateTime(2023, 09, 18);
|
|
|
|
var strategy = OptionStrategies.ShortJellyRoll(canonicalOptionSymbol, strike, nearExpiration, farExpiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.ShortJellyRoll.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(4, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var nearExpirationCall = strategy.OptionLegs.Single(x =>
|
|
x.Expiration == nearExpiration && x.Right == OptionRight.Call);
|
|
Assert.AreEqual(strike, nearExpirationCall.Strike);
|
|
Assert.AreEqual(+1, nearExpirationCall.Quantity);
|
|
|
|
var farExpirationCall = strategy.OptionLegs.Single(x =>
|
|
x.Expiration == farExpiration && x.Right == OptionRight.Call);
|
|
Assert.AreEqual(strike, farExpirationCall.Strike);
|
|
Assert.AreEqual(-1, farExpirationCall.Quantity);
|
|
|
|
var nearExpirationPut = strategy.OptionLegs.Single(x =>
|
|
x.Expiration == nearExpiration && x.Right == OptionRight.Put);
|
|
Assert.AreEqual(strike, nearExpirationPut.Strike);
|
|
Assert.AreEqual(-1, nearExpirationPut.Quantity);
|
|
|
|
var farExpirationPut = strategy.OptionLegs.Single(x =>
|
|
x.Expiration == farExpiration && x.Right == OptionRight.Put);
|
|
Assert.AreEqual(strike, farExpirationPut.Strike);
|
|
Assert.AreEqual(+1, farExpirationPut.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingBearCallLadderStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BearCallLadder(canonicalOptionSymbol, strike3, strike2, strike1, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BearCallLadder(canonicalOptionSymbol, strike1, strike1, strike3, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BearCallLadder(canonicalOptionSymbol, strike2, strike1, strike3, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BearCallLadder(canonicalOptionSymbol, strike1, strike3, strike2, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsBearCallLadderStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.BearCallLadder(canonicalOptionSymbol, strike1, strike2, strike3, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.BearCallLadder.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(3, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Call, lowStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, lowStrikeLeg.Quantity);
|
|
|
|
var middleStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Call, middleStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, middleStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, middleStrikeLeg.Quantity);
|
|
|
|
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike3);
|
|
Assert.AreEqual(OptionRight.Call, highStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, highStrikeLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingBearPutLadderStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strike1 = 350m;
|
|
var strike2 = 325m;
|
|
var strike3 = 300m;
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BearPutLadder(canonicalOptionSymbol, strike3, strike2, strike1, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BearPutLadder(canonicalOptionSymbol, strike1, strike1, strike3, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BearPutLadder(canonicalOptionSymbol, strike2, strike1, strike3, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BearPutLadder(canonicalOptionSymbol, strike1, strike3, strike2, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsBearPutLadderStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 350m;
|
|
var strike2 = 325m;
|
|
var strike3 = 300m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.BearPutLadder(canonicalOptionSymbol, strike1, strike2, strike3, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.BearPutLadder.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(3, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Put, highStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, highStrikeLeg.Quantity);
|
|
|
|
var middleStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Put, middleStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, middleStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, middleStrikeLeg.Quantity);
|
|
|
|
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike3);
|
|
Assert.AreEqual(OptionRight.Put, lowStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, lowStrikeLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingBullCallLadderStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BullCallLadder(canonicalOptionSymbol, strike3, strike2, strike1, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BullCallLadder(canonicalOptionSymbol, strike1, strike1, strike3, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BullCallLadder(canonicalOptionSymbol, strike2, strike1, strike3, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BullCallLadder(canonicalOptionSymbol, strike1, strike3, strike2, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsBullCallLadderStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var strike3 = 350m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.BullCallLadder(canonicalOptionSymbol, strike1, strike2, strike3, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.BullCallLadder.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(3, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Call, lowStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, lowStrikeLeg.Quantity);
|
|
|
|
var middleStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Call, middleStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, middleStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, middleStrikeLeg.Quantity);
|
|
|
|
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike3);
|
|
Assert.AreEqual(OptionRight.Call, highStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, highStrikeLeg.Quantity);
|
|
}
|
|
|
|
[Test]
|
|
public void FailsBuildingBullPutLadderStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strike1 = 350m;
|
|
var strike2 = 325m;
|
|
var strike3 = 300m;
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BullPutLadder(canonicalOptionSymbol, strike3, strike2, strike1, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BullPutLadder(canonicalOptionSymbol, strike1, strike1, strike3, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BullPutLadder(canonicalOptionSymbol, strike2, strike1, strike3, expiration));
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.BullPutLadder(canonicalOptionSymbol, strike1, strike3, strike2, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsBullPutLadderStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 350m;
|
|
var strike2 = 325m;
|
|
var strike3 = 300m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.BullPutLadder(canonicalOptionSymbol, strike1, strike2, strike3, expiration);
|
|
|
|
Assert.AreEqual(OptionStrategyDefinitions.BullPutLadder.Name, strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(3, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Put, highStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, highStrikeLeg.Quantity);
|
|
|
|
var middleStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Put, middleStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, middleStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, middleStrikeLeg.Quantity);
|
|
|
|
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike3);
|
|
Assert.AreEqual(OptionRight.Put, lowStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, lowStrikeLeg.Quantity);
|
|
}
|
|
|
|
[TestCase(325, 300)]
|
|
[TestCase(300, 300)]
|
|
public void FailsBuildingCallBackspreadStrategy(decimal strike1, decimal strike2)
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.CallBackspread(canonicalOptionSymbol, strike1, strike2, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildCallBackspreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.CallBackspread(canonicalOptionSymbol, strike1, strike2, expiration);
|
|
|
|
Assert.AreEqual("Call Backspread", strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Call, lowStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, lowStrikeLeg.Quantity);
|
|
|
|
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Call, highStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
|
Assert.AreEqual(2, highStrikeLeg.Quantity);
|
|
}
|
|
|
|
[TestCase(325, 350)]
|
|
[TestCase(300, 300)]
|
|
public void FailsBuildingPutBackspreadStrategy(decimal strike1, decimal strike2)
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.PutBackspread(canonicalOptionSymbol, strike1, strike2, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsPutBackspreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 350m;
|
|
var strike2 = 325m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.PutBackspread(canonicalOptionSymbol, strike1, strike2, expiration);
|
|
|
|
Assert.AreEqual("Put Backspread", strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Put, highStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
|
Assert.AreEqual(-1, highStrikeLeg.Quantity);
|
|
|
|
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Put, lowStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
|
Assert.AreEqual(2, lowStrikeLeg.Quantity);
|
|
}
|
|
|
|
[TestCase(325, 300)]
|
|
[TestCase(300, 300)]
|
|
public void FailsBuildingShortCallBackspreadStrategy(decimal strike1, decimal strike2)
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ShortCallBackspread(canonicalOptionSymbol, strike1, strike2, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortCallBackspreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 300m;
|
|
var strike2 = 325m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortCallBackspread(canonicalOptionSymbol, strike1, strike2, expiration);
|
|
|
|
Assert.AreEqual("Short Call Backspread", strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Call, lowStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, lowStrikeLeg.Quantity);
|
|
|
|
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Call, highStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
|
Assert.AreEqual(-2, highStrikeLeg.Quantity);
|
|
}
|
|
|
|
[TestCase(325, 350)]
|
|
[TestCase(300, 300)]
|
|
public void FailsBuildingShortPutBackspreadStrategy(decimal strike1, decimal strike2)
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
// Unordered and repeated strikes
|
|
Assert.Throws<ArgumentException>(
|
|
() => OptionStrategies.ShortPutBackspread(canonicalOptionSymbol, strike1, strike2, expiration));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildsShortPutBackspreadStrategy()
|
|
{
|
|
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var underlying = Symbols.SPY;
|
|
var strike1 = 350m;
|
|
var strike2 = 300m;
|
|
var expiration = new DateTime(2023, 08, 18);
|
|
|
|
var strategy = OptionStrategies.ShortPutBackspread(canonicalOptionSymbol, strike1, strike2, expiration);
|
|
|
|
Assert.AreEqual("Short Put Backspread", strategy.Name);
|
|
Assert.AreEqual(underlying, strategy.Underlying);
|
|
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
|
|
|
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
|
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
|
|
|
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
|
Assert.AreEqual(OptionRight.Put, highStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
|
Assert.AreEqual(1, highStrikeLeg.Quantity);
|
|
|
|
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
|
Assert.AreEqual(OptionRight.Put, lowStrikeLeg.Right);
|
|
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
|
Assert.AreEqual(-2, lowStrikeLeg.Quantity);
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> GetSymbolsTestCases
|
|
{
|
|
get
|
|
{
|
|
// Equity option
|
|
var spyCanonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
|
var spyContract = Symbol.CreateOption(Symbols.SPY, spyCanonicalOptionSymbol.ID.Market,
|
|
spyCanonicalOptionSymbol.ID.OptionStyle, OptionRight.Call, 300m, new DateTime(2023, 08, 18));
|
|
yield return new TestCaseData(spyCanonicalOptionSymbol, spyContract);
|
|
|
|
// Index options
|
|
var spxCanonicalOptionSymbol = Symbol.CreateCanonicalOption(Symbols.SPX);
|
|
var spxContract = Symbol.CreateOption(Symbols.SPX, spxCanonicalOptionSymbol.ID.Market,
|
|
spxCanonicalOptionSymbol.ID.OptionStyle, OptionRight.Put, 4000m, new DateTime(2023, 08, 18));
|
|
yield return new TestCaseData(spxCanonicalOptionSymbol, spxContract);
|
|
|
|
// Index weekly option
|
|
var spxwCanonicalOptionSymbol = Symbol.CreateCanonicalOption(Symbols.SPX, targetOption: "SPXW");
|
|
var spxwContract = Symbol.CreateOption(Symbols.SPX, "SPXW", spxwCanonicalOptionSymbol.ID.Market,
|
|
spxwCanonicalOptionSymbol.ID.OptionStyle, OptionRight.Call, 4000m, new DateTime(2023, 08, 18));
|
|
yield return new TestCaseData(spxwCanonicalOptionSymbol, spxwContract);
|
|
|
|
// Future option
|
|
var esFutureContractSymbol = Symbol.CreateFuture("ES", Market.CME, new DateTime(2023, 09, 15));
|
|
var esCanonicalOptionSymbol = Symbol.CreateCanonicalOption(esFutureContractSymbol);
|
|
var esOptionContract = Symbol.CreateOption(esFutureContractSymbol, esFutureContractSymbol.ID.Market,
|
|
OptionStyle.American, OptionRight.Put, 4000m, new DateTime(2023, 08, 18));
|
|
yield return new TestCaseData(esCanonicalOptionSymbol, esOptionContract);
|
|
}
|
|
}
|
|
|
|
[TestCaseSource(nameof(GetSymbolsTestCases))]
|
|
public void SetsOptionLegsSymbols(Symbol canonicalSymbol, Symbol contractSymbol)
|
|
{
|
|
var strategy = new OptionStrategy("Test Strategy", canonicalSymbol, new List<OptionStrategy.OptionLegData>
|
|
{
|
|
new OptionStrategy.OptionLegData
|
|
{
|
|
Right = contractSymbol.ID.OptionRight,
|
|
Strike = contractSymbol.ID.StrikePrice,
|
|
Expiration = contractSymbol.ID.Date
|
|
}
|
|
});
|
|
var leg = strategy.OptionLegs.Single();
|
|
Assert.AreEqual(contractSymbol, leg.Symbol);
|
|
}
|
|
}
|
|
}
|