public void ResultingPositionsAreCorrectWithUnderlying() { var positions = Empty.AddRange( Position(Call[100], -5), // should match 4 covered calls new OptionPosition(Underlying, 4) ); var matches = CoveredCall.Match(positions).ToList(); Assert.AreEqual(1, matches.Count); // underlying isn't included yet Assert.AreEqual(1, matches[0].Legs.Count); Assert.AreEqual(1, matches[0].Legs.Count(m => m.Position.Strike == 100 && m.Position.Quantity == -5)); var strategy = matches[0].CreateStrategy(); Assert.AreEqual(1, strategy.OptionLegs.Count); Assert.AreEqual(1, strategy.UnderlyingLegs.Count); Assert.AreEqual(1, strategy.OptionLegs.Count(m => m.Strike == 100 && m.Quantity == -4)); Assert.AreEqual(1, strategy.UnderlyingLegs.Count(m => m.Quantity == 4)); // assert the remaining positions are the expected ones positions = matches[0].RemoveFrom(positions); Assert.AreEqual(1, positions.Count); Assert.AreEqual(1, positions.Count(m => !m.IsUnderlying && m.Strike == 100 && m.Quantity == -1)); }
public void CoveredCall_MatchesMultipleTimes_ForEachUniqueShortCallContract() { // CoveredCall // 0: +1 underlying lot // 1: -1 Call // so we should match // (4U, -4C100) // (3U, -3C110) // (5U, -5C120) // (9U, -9C130) // (20U, -20C140) // OptionStrategyDefinition.Match produces ALL possible matches var positions = Empty.AddRange(Position(Underlying, +20), Position(Call[100], -4), Position(Put[105], -4), Position(Call[105], +4), Position(Put[110], +4), Position(Call[110], -3), Position(Put[115], -3), Position(Call[115], +3), Position(Put[120], +3), Position(Call[120], -5), Position(Put[125], -5), Position(Call[125], +5), Position(Put[130], +5), Position(Call[130], -9), Position(Put[135], -9), Position(Call[140], -21), Position(Put[145], -21) ); // force lower strikes to be evaluated first to provide determinism for this test var options = OptionStrategyMatcherOptions.ForDefinitions(CoveredCall) .WithPositionEnumerator(new FunctionalOptionPositionCollectionEnumerator( pos => pos.OrderBy(p => p.IsUnderlying ? 0 : p.Strike) )); var matches = CoveredCall.Match(options, positions).ToList(); Assert.AreEqual(5, matches.Count); Assert.AreEqual(1, matches.Count(m => m.Multiplier == 4)); Assert.AreEqual(1, matches.Count(m => m.Multiplier == 3)); Assert.AreEqual(1, matches.Count(m => m.Multiplier == 5)); Assert.AreEqual(1, matches.Count(m => m.Multiplier == 9)); Assert.AreEqual(1, matches.Count(m => m.Multiplier == 20)); }
private static List <CoveredCall> GetCoveredCallsByLegType(OptionChain data, Dictionary <DateAndNumberOfDaysUntil, Prediction> predictions, /*EarningsCalendar calendar,*/ LegType legType, /*Signal volOfVol,*/ double?minimumPremiumParam, double?minimumReturnParam) { double minimumPremium = minimumPremiumParam ?? DefaultMinimumPremium; double minimumReturn = minimumReturnParam ?? DefaultMinimumReturn; HashSet <DateTime> optimalIsFound = new HashSet <DateTime>(); List <CoveredCall> coveredCalls = new List <CoveredCall>(); foreach (OptionPair optionChain in data) { Option option = legType == LegType.Call ? optionChain.CallOption : optionChain.PutOption; double bid = option.Bid; double currentPremium = bid; DateAndNumberOfDaysUntil expiry = optionChain.Expiry; double lastPrice = data.UnderlyingCurrentPrice; double strkePrice = optionChain.StrikePrice; List <OptionPair> chainsWithTheSameExpiry = data.Where(x => x.Expiry == expiry).ToList(); // todo: optionChain.OptionType //bool typeisSuitable = optionChain.OptionType == OptionType.Standard; double daysQuantity = expiry.TotalNumberOfDaysUntilExpiry; // Ignore expired options if (daysQuantity < 0 || daysQuantity.AlmostEqual(0) /*|| !typeisSuitable*/) { continue; } bool strikePriceIsSuitable = legType == LegType.Call ? optionChain.StrikePrice > lastPrice : optionChain.StrikePrice < lastPrice; if (!strikePriceIsSuitable) { // Check if fifth strike is suitable strikePriceIsSuitable = legType == LegType.Call ? chainsWithTheSameExpiry.Where(x => x.StrikePrice < lastPrice) .OrderBy(x => lastPrice - x.StrikePrice) .Take(5).Last().StrikePrice <optionChain.StrikePrice : chainsWithTheSameExpiry.Where(x => x.StrikePrice > lastPrice) .OrderBy(x => x.StrikePrice - lastPrice) .Take(5).Last().StrikePrice> optionChain.StrikePrice; } if (!strikePriceIsSuitable) { continue; } double intrinsicValue = legType == LegType.Call ? lastPrice > optionChain.StrikePrice ? lastPrice - optionChain.StrikePrice : 0 : lastPrice < optionChain.StrikePrice ? optionChain.StrikePrice - lastPrice : 0; double currentReturn = (Math.Pow(1 + (currentPremium - intrinsicValue) / lastPrice, 365.0 / daysQuantity) - 1) * 100; bool firstAboveBelowStdDev = false; Prediction prediction = predictions[optionChain.Expiry]; StdDevPrices stdDev = prediction.GetStdDevPrices(1); bool deviationIsSuitable = legType == LegType.Call ? strkePrice > stdDev.UpPrice : strkePrice < stdDev.DownPrice; if (deviationIsSuitable) { firstAboveBelowStdDev = legType == LegType.Call ? chainsWithTheSameExpiry .Where(x => x.StrikePrice >= stdDev.UpPrice) .Select(y => y.StrikePrice - stdDev.UpPrice) .Min() .AlmostEqual(strkePrice - stdDev.UpPrice) : chainsWithTheSameExpiry .Where(x => x.StrikePrice <= stdDev.DownPrice) .Select(y => stdDev.DownPrice - y.StrikePrice) .Min() .AlmostEqual(stdDev.DownPrice - optionChain.StrikePrice); } double probability = prediction.GetProbabilityByPrice(optionChain.StrikePrice) * 100; double probabilityInSigmas = MarketMath.GetSigmaProbabilityByDeviation(probability / 100); CoveredCall coveredCall = new CoveredCall(); coveredCall.Premium = currentPremium; coveredCall.Return = currentReturn; coveredCall.OptionNumber = option.OptionNumber; coveredCall.Probability = probability; coveredCall.ProbabilityInSigmas = probabilityInSigmas; // if (volOfVol != null) // { // coveredCall.VolOfVol = volOfVol.Value; // } coveredCall.PercentAboveBelowCurrentPrice = Math.Abs((optionChain.StrikePrice - lastPrice)) / lastPrice * 100; int numberOfStrikes = legType == LegType.Call ? chainsWithTheSameExpiry.Where(x => x.StrikePrice >= lastPrice) .OrderBy(x => x.StrikePrice) .ToList() .FindIndex(x => x.StrikePrice.AlmostEqual(optionChain.StrikePrice)) + 1 : chainsWithTheSameExpiry.Where(x => x.StrikePrice <= lastPrice) .OrderByDescending(x => x.StrikePrice) .ToList() .FindIndex(x => x.StrikePrice.AlmostEqual(optionChain.StrikePrice)) + 1; coveredCall.NumberOfStrikesBelowAboveCurrentPrice = numberOfStrikes; coveredCall.NumberOfSdBelowAboveCurrentPrice = probabilityInSigmas; // coveredCall.HasEarnings = calendar != null && calendar.Date >= DateTime.Now && calendar.Date <= optionChain.Expiry; // if (calendar != null) // { // coveredCall.DaysQuantityBeforeEarnings = (calendar.Date - DateTime.UtcNow).TotalDays; // } coveredCalls.Add(coveredCall); if (bid.AlmostEqual(0.0)) { coveredCall.RiskTolerance = RiskTolerance.NoBid; continue; } if (!deviationIsSuitable) { if (currentPremium < minimumPremium) { coveredCall.RiskTolerance = RiskTolerance.LowPremium; continue; } coveredCall.RiskTolerance = currentReturn >= minimumReturn ? RiskTolerance.Aggressive : RiskTolerance.LowReturn; continue; } if (currentPremium < minimumPremium) { coveredCall.RiskTolerance = RiskTolerance.LowPremium; continue; } if (currentReturn < minimumReturn) { coveredCall.RiskTolerance = RiskTolerance.LowReturn; continue; } if (!optimalIsFound.Contains(optionChain.Expiry.FutureDate) && daysQuantity >= 3 && daysQuantity <= 60 //&& (!coveredCall.HasEarnings || daysQuantity < coveredCall.DaysQuantityBeforeEarnings) && firstAboveBelowStdDev) { coveredCall.RiskTolerance = RiskTolerance.Optimal; optimalIsFound.Add(optionChain.Expiry.FutureDate); } else { coveredCall.RiskTolerance = RiskTolerance.Conservative; } } return(coveredCalls); }