/// <summary> /// Populates the Items array naming and assigning margin requirements to each item /// </summary> public void Optimize(TWEAKS tweaks) { List <PortfolioItem> results = new List <PortfolioItem>(); // group all positions by underlying security var underlyerBuckets = _input.GroupBy(x => ((OptionSymbol)x.Symbol).UnderlyingSecuritySymbol ?? x.Symbol).ToDictionary(x => x.Key, x => x.ToArray()); // for all underlying securities foreach (var underlyer in underlyerBuckets.Keys) { // select all options var options = underlyerBuckets[underlyer] .Where(x => ((OptionSymbol)x.Symbol).IsValid) .Where(x => x.Quantity != 0) .Select(x => new OptionPosition { Deliverables = DELIVERABLES, Multiplier = MULTIPLIER, Quantity = x.Quantity, Symbol = (OptionSymbol)x.Symbol, Price = _prices[x.Symbol], UnderlierPrice = _prices[underlyer] }).ToArray(); // select the stock position var q = underlyerBuckets[underlyer].Where(x => !((OptionSymbol)x.Symbol).IsValid).Sum(s => s.Quantity); var stock = new StockPosition { Quantity = q, Price = _prices[underlyer], Symbol = underlyer }; // group switch (tweaks) { case TWEAKS.SPREADS_NO_DIFFERENCE_IN_CREDIT_DEBIT: { var strategies = Group_WithCommonSpreads(stock, options); results.AddRange(strategies); break; } case TWEAKS.CREDIT_DEBIT_SPREADS_SEPEARATELY: default: { var strategies = Group_WithCreditDebitSpreads(stock, options); results.AddRange(strategies); break; } } // ASSERT if (stock.Quantity != 0 || options.Any(x => x.Quantity != 0)) { throw new Exception($"Pairing in {underlyer} failed. Some ungrouped quantity left"); } } _items = results.ToArray(); }
private PortfolioItem[] GroupStock(StockPosition stockPosition) { List <PortfolioItem> list = new List <PortfolioItem>(); if (stockPosition.Quantity == 0) { return(list.ToArray()); } var item = new PortfolioItem { Name = $"{stockPosition.Symbol} Stock", Quantity = stockPosition.Quantity, MarginRequirement = STOCK_MARGIN_RATE * Math.Abs(stockPosition.Quantity) * stockPosition.Price }; list.Add(item); stockPosition.Quantity = 0; return(list.ToArray()); }
private PortfolioItem[] GroupCoveredPuts(StockPosition stock, OptionPosition[] options) { List <PortfolioItem> list = new List <PortfolioItem>(); if (stock.Quantity >= 0) { return(list.ToArray()); } // grab all short puts ordered by in the money amount and expiration date var shortPuts = options .Where(x => x.Symbol.OptionType == OptionType.Put && x.Quantity < 0) .OrderByDescending(x => x.InTheMoneyAmount).ThenBy(x => x.Symbol.ExpirationDate).ToArray(); // locate covered puts foreach (var o in shortPuts) { var coverage = Math.Ceiling(Math.Min(stock.Quantity, 0) / o.Deliverables); // maximum number of puts we can cover var coveredPuts = Math.Min(Math.Abs(coverage), Math.Abs(o.Quantity)); // number of ut we actually cover if (coveredPuts > 0) { list.Add(new PortfolioItem { Name = $"Covered Put {o.Symbol.ToStringFriendly()}", Quantity = coveredPuts, MarginRequirement = STOCK_MARGIN_RATE * stock.Price * coveredPuts * o.Deliverables + Math.Max(o.Symbol.StrikePrice - stock.Price, 0) * coveredPuts, Items = new [] { new StrategyItem { Symbol = o.Symbol.ToOsiString(), Quantity = -1 * coveredPuts }, new StrategyItem { Symbol = $"{stock.Symbol} Stock", Quantity = -1 * coveredPuts * o.Deliverables } } }); stock.Quantity += coveredPuts * o.Deliverables; o.Quantity += coveredPuts; } } return(list.ToArray()); }
private PortfolioItem[] GroupCoveredCalls(StockPosition stock, OptionPosition[] options) { List <PortfolioItem> list = new List <PortfolioItem>(); if (stock.Quantity == 0) { return(list.ToArray()); } // grab all short calls starting with the most in-the-money var shortCalls = options .Where(x => x.Symbol.OptionType == OptionType.Call && x.Quantity < 0) .OrderByDescending(x => x.InTheMoneyAmount).ThenBy(x => x.Symbol.ExpirationDate).ToArray(); // locate covered calls foreach (var o in shortCalls) { var coverage = Math.Floor(Math.Max(stock.Quantity, 0) / o.Deliverables); // maximum number of calls we can cover var coveredCalls = Math.Min(coverage, Math.Abs(o.Quantity)); // number of calls we actually cover if (coveredCalls > 0) { list.Add(new PortfolioItem { Name = $"Covered Call {o.Symbol.ToStringFriendly()}", Quantity = coveredCalls, MarginRequirement = STOCK_MARGIN_RATE * stock.Price * coveredCalls * o.Deliverables, Items = new [] { new StrategyItem { Symbol = o.Symbol.ToOsiString(), Quantity = -1 * coveredCalls }, new StrategyItem { Symbol = $"{stock.Symbol} Stock", Quantity = coveredCalls * o.Deliverables } } }); stock.Quantity -= coveredCalls * o.Deliverables; o.Quantity += coveredCalls; } } return(list.ToArray()); }
private PortfolioItem[] Group_WithCreditDebitSpreads(StockPosition stock, OptionPosition[] options) { // TODO: Apply different multipliers to credit and debit spreads // for each underlying in the portfolio: // grouping short calls, order by in-the-money desc, then by expiration date asc: // try covered call // try debit spread // try credit spread // leave what is left for further grouping // grouping short puts, order by in-the-money desc, then by expiration date asc: // try debit spread // try credit spread // try covered put // try short straddle // every short put left is naked // every short call left is naked as well // every option that is left is one legged long // every stock that is left is just a stock // end List <PortfolioItem> list = new List <PortfolioItem>(); var coveredCalls = GroupCoveredCalls(stock, options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(coveredCalls); var debitCallSpreads = GroupDebitCallSpreads(options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(debitCallSpreads); var creditCallSpreads = GroupCreditCallSpreads(options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(creditCallSpreads); var debitPutSpreads = GroupDebitPutSpreads(options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(debitPutSpreads); var creditPutSpreads = GroupCreditPutSpreads(options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(creditPutSpreads); var coveredPuts = GroupCoveredPuts(stock, options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(coveredPuts); var shortStraddles = GroupShortStraddles(options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(shortStraddles); var nakedPuts = GroupNakedPuts(options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(nakedPuts); var nakedCalls = GroupNakedCalls(options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(nakedCalls); var optionsLong = GroupOneLeggedLong(options.Where(x => x.Quantity != 0).ToArray()); list.AddRange(optionsLong); var stocksNoStrategy = GroupStock(stock); list.AddRange(stocksNoStrategy); return(list.ToArray()); }