Overall Statistics |
Total Trades 4 Average Win 0% Average Loss 0% Compounding Annual Return -0.042% Drawdown 0.100% Expectancy 0 Net Profit -0.010% Sharpe Ratio -0.908 Probabilistic Sharpe Ratio 17.087% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha -0 Beta -0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -0.401 Tracking Error 0.184 Treynor Ratio 1.443 Total Fees $23.00 Estimated Strategy Capacity $2900000000.00 Lowest Capacity Asset CVS WAP1QG5IV7OM|CVS R735QTJ8XC9X |
using QuantConnect.Securities.Option; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public class PutSpread { public decimal stockPrice; // 2 public DateTime exDate; // 3 may not be necessary public DateTime tradeDate; // 4 public DateTime putExpiry; // 5 public Symbol oldPutSymb; // 6 public Symbol newPutSymb; // 7 public decimal oldPutBid; // 8 public decimal newPutAsk; // 9 public decimal oldPutStrike; // 10 public decimal newPutStrike; // 11 public decimal newPutOpenInterest; // 12 //public decimal newPutDelta; //public decimal newPutGamma; //public decimal newPutVega; //public decimal newPutRho; //public decimal newPutTheta; //public decimal newPutImpliedVol; public decimal divAmt; // 13 public decimal divCount; // 14 public decimal divDollars; // 15 public decimal stkIncr; // 16 appreciation in stock value public decimal intCost; // 17 public decimal downsideRisk; // 18 public decimal upsidePotential; // 19 public decimal netIncome; // 20 public decimal netOptions; // 21 public decimal haircut; // 22 committed capital in a portfolio margin account public string description1; // 23 //public string description2; //public string description3; public override string ToString() { return this.description1; } public bool IsEmpty() { return this.description1.IsNullOrEmpty(); } } public List<PutSpread> AssemblePutSpreads(Slice slc, Dictionary<int, DateTime> expiries, TradePerfRec tPRec, IEnumerable<Symbol> allUndrOptSymbs, decimal sPrice, decimal incrAmt){ // only roll puts up if the appreciation in stock price + the expected dividends is greater than the cost of the put spread + interest cost // appreciation = incrAmt // get the expected dividends int yearsInTrade = 0; // to calculate dividends decimal monthsInTrade = 0; // to calculate dividends int daysInTrade = 0; // to calculate interest int intCost = 0; // interest cost decimal dividends = 0.0M; int k = 1; // initialize iterator for AddOptionContracts below Symbol optSymbol; // initialize option symbol for building the list of contracts Option tempOption; // initialize option contract for building list of contracts and obtaining pricing data Option thisPutOpt; // initialize option contract for building list of contracts and obtaining pricing data var justDate = slc.Time.Date; // separate out the DATEVALUE from the DateTime variable bc fedFundsRates are so indexed thisFFRate = fedFundsRates[justDate]; // fedFundsRates is a Dictionary of all dates where DateTime index are all 12:00:00am decimal oldPutPrem = Securities[tPRec.pSymbol].BidPrice; // need the price at which we might sell the puts; List<Option> putOptionsList = new List<Option>(); DateTime oldPutExpiry = tPRec.expDate; // use old put expiry for selecting put options to examine var atmPut = allUndrOptSymbs.Where(s => s.ID.OptionRight == OptionRight.Put) // get the ATM put strike for selecting put options to examine .OrderBy(s => Math.Abs(s.ID.StrikePrice - sPrice)) .FirstOrDefault(); if (haltProcessing && doTracing) { Debug(" ********* ******* WE GOT AN ATM PUT " ); } var atmStrike = atmPut.ID.StrikePrice; // get the ATM strike var lowStrike = tPRec.pStrike; var highStrik = atmStrike; //var lowStrike = (1 - (maxPutOTM / (decimal)100)) * atmStrike; // ~~ for selecting put options to examine //var highStrike = (decimal)1.1 * atmStrike; // ~~ for selecting put options to examine List<PutSpread> pSpreads = new List<PutSpread>(); // ~~ List for assembling filterd put options var putSymbs = allUndrOptSymbs; // declare the variable before the conditional branching // can we get current Put Expiration date? if (doTracing) Debug("---------------------- PUTS ROLLUP EXPIRIES PASS 1 ----------------------------"); if (doTracing) Debug("--" + stockPrice.ToString() +", " + expiries[2].ToString("MM/dd/yy") + ", " + expiries[3].ToString("MM/dd/yy") + ", " + expiries[4].ToString("MM/dd/yy") + ", " + expiries[5].ToString("MM/dd/yy")); /*putSymbs = allUndrOptSymbs.Where( o=> (DateTime.Compare(o.ID.Date, expiries[1])==0 | DateTime.Compare(o.ID.Date, expiries[2])==0 | DateTime.Compare(o.ID.Date, expiries[3])==0 | DateTime.Compare(o.ID.Date, expiries[4])==0 ) && o.ID.OptionRight == OptionRight.Put && o.ID.StrikePrice >= lowStrike && o.ID.StrikePrice < atmStrike) .OrderByDescending(o => o.ID.StrikePrice); */ putSymbs = allUndrOptSymbs.Where( o=> o.ID.Date.Subtract(slc.Time).Days >= 10 & o.ID.OptionRight == OptionRight.Put & o.ID.StrikePrice >= lowStrike & o.ID.StrikePrice <= atmStrike) .OrderByDescending(o => o.ID.StrikePrice); if (haltProcessing) { if (doTracing) IterateChain(putSymbs, "putSymbols"); } if (putSymbs == null | putSymbs.Count()== 0) { if (doTracing) Debug(" AP AP AP AP putSymbs is null or empty "); return pSpreads; } // putSymbs !=null && putSymbs.Count() != 0 -- in other words continue var pEnumerator = putSymbs.GetEnumerator(); // convert the options contracts list to an enumerator while (pEnumerator.MoveNext()) // process the contracts enumerator to add the options { optSymbol = pEnumerator.Current; tempOption = AddOptionContract(optSymbol, Resolution.Minute, true); tempOption.PriceModel = OptionPriceModels.BinomialTian(); /// necessary for Greeks putOptionsList.Add(tempOption); } var putEnum = putOptionsList.GetEnumerator(); // get the enumerator to build the List<PutSpread> while (putEnum.MoveNext()) { thisPutOpt = putEnum.Current; //if ( thisPutOpt.Expiry.Subtract(slc.Time).Days >= 10 ) { PutSpread pSpread = new PutSpread(); pSpread.stockPrice = sPrice; pSpread.tradeDate = justDate; pSpread.stkIncr = incrAmt; pSpread.oldPutSymb = tPRec.pSymbol; pSpread.newPutSymb = thisPutOpt.Symbol; pSpread.oldPutBid = oldPutPrem; pSpread.newPutAsk = thisPutOpt.AskPrice; pSpread.oldPutStrike = tPRec.pSymbol.ID.StrikePrice; pSpread.newPutStrike = thisPutOpt.StrikePrice; pSpread.putExpiry = thisPutOpt.Expiry; daysInTrade = (thisPutOpt.Expiry - justDate).Days; // use the new put option expiration to calculate potential days in trade pSpread.intCost = (thisFFRate + ibkrRateAdj)/workingDays * (decimal) daysInTrade * stockPrice; monthsInTrade = ((thisPutOpt.Expiry.Year - justDate.Year) * 12) + (thisPutOpt.Expiry.Month - justDate.Month); pSpread.divCount = Math.Truncate(monthsInTrade/3.00M) + 1.00M; // add 1 for the next dividend and 1 for every 3 months thereafter pSpread.divAmt = stockDividendAmount; pSpread.divDollars = stockDividendAmount * pSpread.divCount; // pSpread.divDollars = stockDividendAmount * pSpread.divCount; pSpread.divDollars = stockDividendAmount * 1M; // for profit calc and filtering, omit more than one dividend. Many PTS's end before 1st dividend is paid pSpread.netOptions = oldPutPrem - tPRec.pStartPrice - thisPutOpt.AskPrice; // get the total net cost of the options trade (not the spread traded) pSpread.netIncome = incrAmt + pSpread.divDollars - pSpread.intCost; // net potential profit including unrealized gain in underlying since initial trade //pSpread.newPutOpenInterest; //pSpread.newPutDelta; //pSpread.newPutGamma; //pSpread.newPutVega; //pSpread.newPutRho; //pSpread.newPutTheta; //pSpread.newPutImpliedVol; //pSpread.haircut; // committed capital in a portfolio margin account //pSpread.description1; //pSpread.description2; pSpreads.Add(pSpread); //} } return pSpreads; // return filled pSpreads; } // ********************** GetBestPutSpread ************************************** // *** This sub routine takes in the assembled List of PutSpreads // *** available in the Slice.Data and calculates the best spread to use // *** to the roll up the puts // *********************************************************************************** public PutSpread GetBestPutSpread(List<PutSpread> pSpreads) { PutSpread pSprd = new PutSpread(); // get a null empty PutSpread pSprd = pSpreads.Where(s => s.netIncome + s.netOptions > 0 ).OrderByDescending( s => (s.netIncome + s.netOptions)/Math.Abs(s.stockPrice - s.newPutStrike)).FirstOrDefault(); if (haltProcessing) { if (doTracing) Debug(" HALTED IN GETBESTPUTSPREAD -- CHECKING PSPREADS"); var orderedPSpreads = pSpreads.Where(s => s.netIncome + s.netOptions > 0 ).OrderByDescending( s => (s.netIncome + s.netOptions)/Math.Abs(s.stockPrice - s.newPutStrike)); IterateOrderedPutSpreadList(orderedPSpreads); } // null pSpread can occur when sPrice>oldPStrike but (sPrice-oldPStrike)/oldPStrike < ~2%: Also, rolling forward would cost money. return pSprd; } } }
namespace QuantConnect { /// 2020-12-03: Arranged all trade pathways, usingDeltas and not, to utilze GetPotentialCollars() /////// /// ####-##-##: in order to IterateOrderedMatrices solely when executing a trade. /// 2020-12-04: Added [[bestSSQRColumn = new SSQRColumn();]] to prevent looping and Matrix Iteration after initial SSQRMatrix buiding /// ####-##-## This was found to occur and created multiple copies of the same SSQR in subsequent OnData() events. /// 2020-12-07: Corrected RollTheCollar to calculate callQty by putPrem/callPrem (as is done in ExecuteTheTrade()). /// ####-##-## Also added bool didTheTrade to IterateOrderedSSQRMatix solely when actually trading /// 2020-12-08 Found GetPotentialCollars for ABBV would only return 2 divs (not 3 or 4) in 2015-10. April Options missing. Has May '16 options /// ####-##-## conferred with John, and decided to look further (LEAPS) for more possible trades. Added fifthExpirationDate to GetOptionsExpiries() /// 2020-12-08 Prevented duplicate call/put contracts from being added to SSQRMatrix in AssembleSSQRMatrix (!SSQRMatrix.Any(o=>o.optSymbo == optSymbol) /// 2020-12-13 Re-configured assembleSSQRMatrix to put and call list enumarators with all the options for 2-5 dividends, and loop 1X /// ####-##-## Build SSQR only occurs for calls >= put strike and expiration. /// 2020-12-13 Evaluation of SSQR Matrix reveals the potential of using call time spreads (selling longer dated calls to pay for puts) /// 2020-12-15 Saw several instances of divide-by-zero error when evaluating vcc/pot. loss (stockprice - putstrike) /// ####-##-## decided to reformulate the algorithm to sort first by loss potential and then by VCC. /// 2020-12-16 SIGNIFICANT -- modified bestSSQRColumn to sort descending by Math.Abs(stockPrice-putStrike) then ascending by putPremium/callPremium to get lowest risk and least call coverage /// 2021-01-04 Captured DivideByZero errors when StockPrice = PutStrike in CCOR calculations /// 2021-01-06 Added LogTrace to turn Debug on/off /// 2021-01-06 Debug placing and filling of limit orders for Call and Put closure /// 2021-01-07 refined debug placing/filling of Call/Put closure -- include MKT orders to better trace /// 2021-01-19 debugged oldRollDate. Never set initially and not always set in various branches of code. /// 2021-01-19 Found that in longer expirations, may try to set AddedMonths to 24. Error where Months%12 =0 /// 2021-01-21 Added code to exercise puts when rolling is more expensive than exercising. /// 2021-01-24 Added code to conditionally roll up puts when stock appreciates /// 2021-01-31 Added code in OnOrder() to detect call assignment so that the primary TradeRec collar PUTs are sold uEndPrice is recorded and record is closed /// 2021-01-31 Modified OTM code because in VCC put and call expirations may be different. Old code didnt trap all OTM situations /// 2021-02-01 Implemented calling Divididend Check to move code bytes to a different .cs file /// 2021-02-05 Wrapped OnEndOfDay in try-catch as well as .GetOpenOrders() routines. /// 2021-02-05 Found that LimitOrderTicket.Update() was not executing -- replaced update with MarketOrder /// 2021-02-08 ERROR: Found System.InvalidOperationException: Collection was modified; enumeration operation may not execute. /// Remedied this by creating a list<int> of oLOs.Indices to remove in a second step /// 2021-02-10 Version 13 Found that slightly OTM 2nd TPRs will not roll at expiration because they are OTM but spread is very small ($1.00). Thus, /// had to force exercise /// 2021-02-10 Version 13 Found that the orderTicket.Quantity follows the option, not the stock. Have to multiply by 100M in order to find the TPR /// 2021-02-10 Version 14 wrote foreach(2ndTPR in SecondTPRs) to process additional 2nd TPRs /// 2021-02-12 Version 15 reduced minDivs on PutRoll to 1 and only look out to 4th Div, not 5th. Found appreciating stocks move up faster and longer durations unnecessary /// 2021-02-15 changed formatting codes in IterateOrderedPutSpreads to make visible the ExpirationDate and to limit the decimals to 2 places /// 2021-02-17 fixed RollPut where expireDateDelta2P<1 and OTM--call Close2TPR. If ITM, then Exercise PUT /// 2021-02-18 Verssion 16 Found the 2nd TPR loop was using "current2ndRec" (1st 2nd TPR) data, not the actual sTPR from the loop. In situations with more than 1 2nd TPR, was totally wrong /// 2021-02-20 Version 17 Modified GetExpiries to ensure expires[1] is more than 10 days after the trade date /// 2021-02-21 modified to allow various paths, CheckDiv, CheckCall, CheckPut, & CheckOTM to execute serially until a good threshold and non-losing roll can be found /// until the last day, when a Kill or Close is called and forced. Modified OnOrder to track LEAN-intitiated call assignment /// 2021-02-23 Add GrossPnL and SSQR.netIncome to TPRs for analysis of roll PnL /// 2021-02-28 Attempted evaluation of ITM based upon actual option premiums rather than an arbitrary 5% based solely upon strikes -- failed due to QC internal algo's /// 2021-03-03 Base 2ndTPR split based upon intitial short call premium. Rationale is that stock appreciation above that number results in nullification of inititial short term capital collar credit. /// 2021-03-03 Modified 2ndTPR roll up based upon incrAmount > cost-to-sell-original-puts /// 2021-03-03 fixed a nit in creating thetaTPR.isSecondary -- make it false to prevent null pointers in processing puts in 2nd TPR Rec /// 2021-03-05 /// 2021-03-10 Converted to Wing Trade -- added PerfRec columns for wing call performance tracking and removed 2ndTPR Put Rolling and thetaCall processing /// 2021-03-12 Amended oLO (open limit order) processing to accomondate shoring calls to open collars and wing calls. /// 2021-03-12 WING VERSION 3 ELIMINATED CONVERSION TRADES -- SET CALLSTRIKE >> PUTSTRIKE /// 2021-03-12 WING VERSION 4 FIXED WINGFACTOR ERROR IN ROLLS /// 2021-03-12 WING VERSION 4C implemented hasDividends check /// 2021-03-12 WING VERSION 4D replaced TPR iteration loop AtEndOfAlogrithm() with expanded line-by-line string concatenation.... could not get actual options symbols otherwise /// 2021-03-21 WING VERSION 5 adjusted DownsideRisk to use Collar.netBid. Check for ITM WingCall to sell ahead of ITM ShortCall (new code in OnData() after Dividend Approachment /*var OpenOrders = Transactions.GetOpenOrders(); // Get the open orders to search for open limit orders if (OpenOrders.Count() > 0) { // process them only if there's any open foreach (var OrderTkt in OpenOrders){ // loop through and process open options limit orders (HasUnderlying) if (OrderTkt.Status == OrderStatus.Submitted && OrderTkt.Type == OrderType.Limit) { if (OrderTkt.Symbol.HasUnderlying) { if (OrderTkt.Symbol.ID.OptionRight == OptionRight.Call) { var orderUnderlyingPrice = Securities[OrderTkt.Symbol.ID.Underlying.Symbol].Price; var Ticket = Extensions.ToOrderTicket(OrderTkt,Securities.SecurityTransactionManager); var orderLimitPrice = Ticket.Get(OrderField.LimitPrice); var orderStrikePrice = Ticket.Symbol.ID.StrikePrice; if (orderLimitPrice < orderUnderlyingPrice - orderStrikePrice + 0.10M) { /// this is the criteria for placing a call buyback limit order. This contition will exist if the underlying price has moved up Ticket.Update(new UpdateOrderFields{LimitPrice = orderUnderlyingPrice - orderStrikePrice + 0.10M}); } } else if (OrderTkt.Symbol.ID.OptionRight == OptionRight.Put) { var orderUnderlyingPrice = Securities[OrderTkt.Symbol.ID.Underlying.Symbol].Price; var orderLimitPrice = OrderTkt.Get(OrderField.LimitPrice); var orderStrikePrice = OrderTkt.Symbol.ID.StrikePrice; if (orderLimitPrice > orderStrikePrice - orderUnderlyingPrice - 0.10M) { /// this is the criteria for placing a put sell-to-close limit order. This contition will exist if the underlying price has moved down. OrderTkt.Update(new UpdateOrderFields{LimitPrice = orderStrikePrice - orderUnderlyingPrice - 0.10M}); } } } } } }*/ /* var OpenTickets = Transactions.GetOrderTickets(); // Get all the orders to search for open limit orders if (OpenTickets.Count() > 0) { // process them only if there's any open Debug(" |||||||| We have " + OpenTickets.Count() + " tickets"); foreach (var Ticket in OpenTickets){ // loop through and process open options limit orders (HasUnderlying) if (Ticket.Status == OrderStatus.Submitted && Ticket.OrderType == OrderType.Limit) { if (Ticket.Symbol.HasUnderlying) { Debug(" |||||||| Ticket for " + Ticket.Symbol + " is " + Ticket.Status + " submitted at " + Ticket.Time + " for " + Ticket.Quantity + "."); if ((int)data.Time.Subtract(Ticket.Time).TotalMinutes > 15) { if (Ticket.Symbol.ID.OptionRight == OptionRight.Call) { var orderUnderlyingPrice = Securities[Ticket.Symbol.ID.Underlying.Symbol].Price; var orderLimitPrice = Ticket.Get(OrderField.LimitPrice); var orderStrikePrice = Ticket.Symbol.ID.StrikePrice; var lPrice = orderUnderlyingPrice - orderStrikePrice + 0.10M; if (orderLimitPrice < orderUnderlyingPrice - orderStrikePrice + 0.10M) { /// this is the criteria for placing a call buyback limit order. This contition will exist if the underlying price has moved up //Debug(" |||||||| with " + Ticket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + ", updating " + Ticket.Symbol + "limit order to new limit price: " + lPrice ); //Ticket.Update(new UpdateOrderFields{LimitPrice = orderUnderlyingPrice - orderStrikePrice + 0.10M}); Ticket.Cancel(); Debug(" |||||||| With " + Ticket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + ", updating " + Ticket.Quantity + " of " + Ticket.Symbol + "limit order to market order"); var buyCallTkt = MarketOrder(Ticket.Symbol, Ticket.Quantity); if (buyCallTkt.Status == OrderStatus.Filled ){ bool anyTPRs = tradeRecs.Any(tr => tr.cSymbol.Equals(Ticket.Symbol) && -tr.cQty == Ticket.Quantity); if (anyTPRs) { var callTradeRec = tradeRecs.Where(tr => tr.cSymbol.Equals(Ticket.Symbol) && -tr.cQty == Ticket.Quantity).FirstOrDefault(); callTradeRec.cEndPrice = buyCallTkt.AverageFillPrice; //foreach (TradePerfRec tpr in callTradeRecs) { //tpr.cEndPrice = buyCallTkt.AverageFillPrice; //} } } } } else if (Ticket.Symbol.ID.OptionRight == OptionRight.Put) { var orderUnderlyingPrice = Securities[Ticket.Symbol.ID.Underlying.Symbol].Price; var orderLimitPrice = Ticket.Get(OrderField.LimitPrice); var orderStrikePrice = Ticket.Symbol.ID.StrikePrice; var lPrice = orderStrikePrice - orderUnderlyingPrice - 0.10M; if (orderLimitPrice > orderStrikePrice - orderUnderlyingPrice - 0.10M) { /// this is the criteria for placing a put sell-to-close limit order. This contition will exist if the //Debug(" |||||||| with " + Ticket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + ", updating " + Ticket.Symbol + "limit order to new limit price: " + lPrice ); //underlying price has moved down. //Ticket.Update(new UpdateOrderFields{LimitPrice = orderStrikePrice - orderUnderlyingPrice - 0.10M}); Ticket.Cancel(); Debug(" |||||||| With " + Ticket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + ", updating " + Ticket.Quantity + " of " + Ticket.Symbol + "limit order to market order"); var sellPutTkt = MarketOrder(Ticket.Symbol, Ticket.Quantity); if (sellPutTkt.Status == OrderStatus.Filled ){ bool anyTPRs = tradeRecs.Any(tr => tr.pSymbol.Equals(Ticket.Symbol) && -tr.pQty == Ticket.Quantity); if (anyTPRs) { var putTradeRec = tradeRecs.Where(tr => tr.pSymbol.Equals(Ticket.Symbol) && -tr.pQty == Ticket.Quantity).FirstOrDefault(); putTradeRec.pEndPrice = sellPutTkt.AverageFillPrice; //foreach (TradePerfRec tpr in putTradeRecs) { //tpr.pEndPrice = sellPutTkt.AverageFillPrice; //} } // is there TPR } // if order filled } // limit price needs to be changed } /// < 15" after order submission } // PUT } // OPTION ORDER } // FOR LOOP } } } catch (Exception errMsg) { Debug(" ERROR " + errMsg ); if (errMsg.Data.Count > 0) { Debug(" Extra details:"); foreach (DictionaryEntry de in errMsg.Data) Debug(" Key: {0,-20} Value: {1}'" + de.Key.ToString() + "'" + de.Value); } } */ }
namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { // ********************** DividendRecord ************************************** // *** This structure contains the dividend information necessary to calculate trading // *** decision. It is used to build a List<DividendRecord> that can be searched to // *** produce the nextExDivDate and dividend amount public struct DividendRecord { public string ticker; public DateTime exDate; public decimal divAmt; public string frequency; } // ********************** GetTickers() ************************************** // *** This function downloads the tickers.csv file from Dropbox and loads it into // *** a Dictionary<int, string> for randomly selecting stocks to put on // *** this dictionary is used when making a trading decision public void GetTickers() { // https://www.dropbox.com/s/hkpr3luefv3u141/StockTickers.csv?dl=1 // 2020-09-26 // https://www.dropbox.com/sh/05qjk3o3y53fp4i/AAA6fEJg8J50xMQWm5nlg7M4a?dl=1 // prior var tickerFile = Download("https://www.dropbox.com/s/hkpr3luefv3u141/StockTickers.csv?dl=1"); if (tickerFile == null) { Debug(" TTTTTTTTTTTTTTTTTTTTTTT EMPTY TICKER FILE TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"); return; } string[] tickerLines = tickerFile.Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries); int h = 0; foreach (string tickerLine in tickerLines) { if(h==0) // discard header row { h++; continue; } var vals = tickerLine.Split(','); tickers.Add(h, vals[0]); h++; } // these next 2 lines are for debugging only -- return; } // ********************** GetFedFundsRates() ************************************** // *** This function downloads the DFF.csv file from Dropbox and loads it into // *** a Dictionary<DateTime, interest rate> for each day // *** this dictionary is used when making a trading decision to calculate the interest public Dictionary<DateTime, decimal> GetFedFundsRates() { var ffFile = Download("https://www.dropbox.com/s/s25jzi5ng47wv4k/DFF.csv?dl=1"); if (ffFile == null) return null; Dictionary<DateTime, decimal> ffDict = new Dictionary<DateTime, decimal>(); string[] ffLines = ffFile.Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries); int h = 0; foreach (string ffLine in ffLines) { if(h==0) // discard header row { h++; continue; } var vals = ffLine.Split(','); ffDict.Add(DateTime.Parse(vals[0]), Convert.ToDecimal(vals[1])/100M); // convert percentage to decimal h++; } // these next 2 lines are for debugging only -- //DateTime testFind = DateTime.Parse("02/02/2015 16:30:00"); //var justDate = testFind.Date; return ffDict; } // ********************** GetDividendDates() ************************************** // *** This function downloads the DividendDates.csv file from Dropbox and loads it into // *** a List<DividendRecord>. The List is used to lookup the next ex-dividend date // *** this list is used when making a trading decision to calculate the dividend payout public List<DividendRecord> GetDividendDates() { // 2020-9-25 9:24 https://www.dropbox.com/s/ap8s120gksb858h/DividendData.csv?dl=1 // 2020-09-25 8:11 https://www.dropbox.com/s/ap8s120gksb858h/DividendData.csv?dl=1 // 2020-09-25 8:09 https://www.dropbox.com/sh/05qjk3o3y53fp4i/AAA6fEJg8J50xMQWm5nlg7M4a?dl=1 -- zip file // 2021-01-14 8:33 var csvFile = Download("https://www.dropbox.com/s/ap8s120gksb858h/DividendData.csv?dl=1"); var csvFile = Download("https://www.dropbox.com/s/jv0aaajwsw8auwo/FiveYearDividends.csv?dl=1"); if (csvFile == null) return null; decimal lastDiv = 0; List<DividendRecord> dividendDates = new List<DividendRecord>(); // want to use Microsoft.VisualBasic.FileIO csv parser but is not available // use the system's /cr /lf to parse the file string into lines string[] csvLines = csvFile.Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries); int i = 0; foreach (string csvLine in csvLines) { if (i == 0) { i++; continue; //discard the header row } var values = csvLine.Split(','); // this file is comma delimited if (!values[3].Equals("annual") ) { DividendRecord divRec = new DividendRecord(); divRec.ticker = values[0]; if (values[1] == "") { divRec.divAmt = 0; } else { divRec.divAmt = Convert.ToDecimal(values[1]); } divRec.exDate = DateTime.Parse(values[2]); divRec.frequency = values[3]; dividendDates.Add(divRec); } } i++; return dividendDates; } } }
using QuantConnect.Securities.Option; using System.Collections.Generic; using System.Drawing; using Newtonsoft.Json; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { // Initialize trade control variables used to intercept automated options exercise. public bool badDtParameter; // get this from the parameters for debugging public bool haltProcessing = false; // use this to trap ERROR public bool doTracing = true; // turn Debug() process tracing on/off int callLoopCntr = 0; // for debugging weird looping int logged = 0; // Switch to toggle log entry writing bool doItOnce = true; // Switch to toggle options exercise interdiction bool didTheTrade = false; // Flag that permits InterateOrderedSSQRMatrix only if a trade was done OrderTicket closeCallTicket; // use this to track and manage collar rolling and killing trades OrderTicket closePutTicket; // use this to track and manage collar rolling and killing trades OrderTicket closeWCallTicket; // use this to track and manage collar rolling and killing trades List<OpenLimitOrder> oLOs = new List<OpenLimitOrder>(); // maintain a list of open limit orders to manage bool iteratePortfolio = false; // Switch to toggle Iterating and Logging portfolio public int addedSecCount = 0; // used to figure out how options data is added. OptionChain testChain; // used to figure out when options chains are available after adding them OptionContract textContract; // used to figure out when options greeks are available decimal stockDollarValue; // get the dollar value for the position decimal sharesToBuy; // Get the number shares to achieve the value bool hasDividends = true; // Bool set (unset=false) to determine whether to add security to portfolio decimal optionsToTrade; // Get the initial Options to trade (1/10th the sharesToBuy) decimal callsToTrade; // Get the initial call options to trade in a variable call coverage strategy decimal maxPutOTM = 0.5M; // Instantiate and set maximum depth of PUT OTM -- percentage int MinNmbrDivs = 1; // Instantiate and set minimum number of dividends acceptable in BestSSQRMatrix decimal wingFactor = 0; // wing factor to multiply optionsToTrade to trade the wings List<optGrksRec> GrksList; // List of options greeks records acquired in SSQR building List<Symbol> jajoStocks; // list of stocks paying dividends in January, April, July, and October List<Symbol> fmanStocks; // list of stocks paying dividends in February, May, August, and November List<Symbol> mjsdStocks; // list of stocks paying dividends in March, June, September, and December bool doTheTrade = false; // Used to allow trades the algorithm initiates bool doingARoll = false; // Used to flag if a roll is in progress when usingDeltas and rolling to next minute bool useDeltas = false; // used to turn use of deltas in trade determination on or off DateTime oldRollDate; // used to make sure we don't do an ITM-FORCED ROLL on the same DateTime newRollDate; // date as a DIVIDEND roll. public Symbol tradablePut; // for roll determination public Symbol tradableCall; // for roll determination public Symbol tradableWCall; // for execution and rolling wings public decimal ROCThresh; // return on (risk/margin-committed) capital public decimal RORThresh; // return on risk (= net collar cost - put strike) public decimal CCORThresh; // call coverage ratio / risk for 0 cost collar (risk = stockPrice - putStrike) bool goodThresh = false; // used to determine go/no-go on trade public bool switchROC = true; List<DividendRecord> exDividendDates = new List<DividendRecord>(); Dictionary<DateTime, decimal> fedFundsRates = new Dictionary<DateTime, decimal>(); public decimal workingDays = 365M; public decimal thisFFRate = 0M; public decimal ibkrRateAdj = .006M; // IBKR adds 60bps to FFR (blended over $3,000,000) Dictionary<decimal, decimal> ibkrHairCuts = new Dictionary<decimal, decimal>(); List<TradePerfRec> tradeRecs = new List<TradePerfRec>(); // used to track P&L of trades int tradeRecCount = 0; // track the trade count int secndRecCount = 0; // loop counter for processing 2nd Recs int collarIndex = 0; bool hasPrimaryRec = false; bool hasSecondaryRec = false; bool hasThetaRec = false; List<TradePerfRec> secTPRs = new List<TradePerfRec>(); List<TradePerfRec> thetaTPRs = new List<TradePerfRec>(); int curr2ndTPR = 0; // Used to store index int curr1stTPR = 0; // used to store index of 1st TPR // initialize class variables for ex-dividend dates, Active Dictionary<int, string> tickers = new Dictionary<int, string> (); //List<OptionContract> ActiveOptions = new List<OptionContract>(); //List<Option> callOptions = new List<Option>(); Symbol thisSymbol; // Initialize Symbol T as class variable Symbol shortedCallSymbol; // primary tradeRec call symbol Symbol longPutSymbol; // primary tradeRec put symbol Symbol wingCallSymbol; // primary tradeRec wing call symbol Symbol secondLongPutSymbol; // secondary tradeRec put symbol Symbol thetaCallSymbol; // theta call symbol IEnumerable crntTPRs; // primary trade recs TradePerfRec crntTPR; // current primary trade rec TradePerfRec currentSecondRec; // current secondary trade rec TradePerfRec currThetaRec; // current theta trade rec TimeSpan expireDateDeltaC; // use vars for checking days before expiration TimeSpan expireDateDeltaP; TimeSpan expireDateDelta2P; TimeSpan expireDateDeltaTC; decimal incrPrice = 0; // check for underlying price appreciation decimal curr1stTPRPPrice = 0; // for calculating potential roll P&L decimal curr1stTPRCPrice = 0; // for calculating potential roll P&L decimal curr1stTPRPAsk = 0; decimal curr1stTPRCAsk = 0; decimal curr2ndTPRPBid = 0; // for calculating PnL on before rolling decimal currSellPnL = 0; // for calculating potential roll P&L decimal currExrcsPutPnL = 0; // for calculating potential roll P&L decimal currExrcsCallPnL = 0; // for calculating potential roll P&L decimal callStrike; decimal putStrike; decimal sTPRPutStrike; // strike of 2nd TPR Put Strike decimal wcStrike; // strike of wing call for evaluating sale Symbol debugSymbol; // general purpose debugging variable OptionChain debugChain; // special purpose debugging variable decimal stockPrice = 0; decimal fTPRPutPrice = 0; // used when rolling up stop losses or deciding to exercise ITM positions decimal sTPRPutPrice = 0; // used when evaluating sTPRs for rolling or extinguishing decimal thisROC = 0; decimal thisROR = 0; decimal thisCCOR = 0; decimal heldValue = 0; // value of thisSymbol held bool buyMoreShares = false; // decision to buy more shares of thisSymbol or keep managing inventory SSQRColumn bestSSQRColumn = new SSQRColumn(); List<SSQRColumn> potentialCollars = new List<SSQRColumn>(); decimal stockDividendAmount = 0M; string divFrequency = "Quarterly"; decimal divPlotValue = 0M; DateTime fmrNextExDate; bool sellThePut = false; // ORDER MANAGEMENT CONTROL -- SET sellThePut whenever calls are exercised by LEAN bool buyTheCall = false; // ORDER MANAGEMENT CONTROL -- SET buyThePut whenever puts are exercised by LEAN // Instatiate and set plotting information //Stochastic sto; // Stochastic AccumulationDistribution ad; // Accumulation / Distribution AccumulationDistributionOscillator adOsc; // Accumulation / Distribution Oscillator AverageDirectionalIndex adx; // Average Directional Index AverageDirectionalMovementIndexRating adxr; // Average Directional Index Rating OnBalanceVolume obv; // On Balance Volumne indicator Variance variance; // Variance of this stock //decimal lastSto; // store values from night before decimal lastAd; decimal lastAdOsc; decimal lastAdx; decimal lastAdxr; decimal lastObv; decimal lastVariance; Chart stockPlot; // initialize Series Variables to reference during order processing and endofday plotting Series buyOrders; Series sellOrders; Series rollOrders; Series ptsOrders; Series assetPrice; Series varianceS; //Series stochastics; Series dividendsS; public override void Initialize() { DateTime startDate = DateTime.Parse(GetParameter("StartDate")); DateTime endDate = DateTime.Parse(GetParameter("EndDate")); SetStartDate(startDate.Year, startDate.Month, startDate.Day); //Set Start Date SetEndDate(endDate.Year, endDate.Month, endDate.Day); // Set End Date SetCash(10000000); //Set Strategy Cash SetWarmup(TimeSpan.FromDays(31), Resolution.Daily); //ABBV ADM BA BBY BMY CVS DOW GIS GM IBM IRM KO LVS M OHI OXY PM PG PSX QCOM SO T VZ WFC XOM // Get RunTime control parameters for Minimum # of Dividends and Maximum OTM Put Depth badDtParameter = GetParameter("CheckBadDate") == "true" ? true : false; // get this from parameters doTracing = GetParameter("LogTrace") == "true" ? true : false; // get this from paramters to turn Debug() tracing on/off stockDollarValue = Convert.ToDecimal(GetParameter("StockDollarValue")); maxPutOTM = Convert.ToDecimal(GetParameter("MaxOTMPutDepth")); // get and set the Maximum OTM Put Depth MinNmbrDivs = Convert.ToInt16(GetParameter("MinNmbrDivs")); // get and set minimum number of dividends acceptable in BestSSQRMatrix useDeltas = GetParameter("UseDeltas") == "true" ? true : false; // get this from parameters wingFactor = Convert.ToDecimal(GetParameter("wingFactor")); // get wing factor for multiplying optionsToTrade when putting on wings thisSymbol = AddEquity(GetParameter("stockTicker"), Resolution.Minute).Symbol; Securities[thisSymbol].SetDataNormalizationMode(DataNormalizationMode.Raw); Securities[thisSymbol].VolatilityModel = new StandardDeviationOfReturnsVolatilityModel(31); //sto = STO(thisSymbol, 14, Resolution.Daily); // Stochastic ad = AD(thisSymbol, Resolution.Daily); // Accumulation / Distribution adOsc = ADOSC(thisSymbol, 3, 14, Resolution.Daily); // Accumulation / Distribution Oscillator adx = ADX(thisSymbol, 7, Resolution.Daily); // Average Directional Index adxr = ADXR(thisSymbol, 7, Resolution.Daily); // Average Directional Index Rating obv = OBV(thisSymbol, Resolution.Daily); // On Balance Volume variance = VAR(thisSymbol, 14, Resolution.Daily); // Variance of this stock // Chart - Master Container for the Chart: stockPlot = new Chart("Stock Chart"); // On the Trade Plotter Chart we want 3 series: trades and price: buyOrders = new Series("Buys", SeriesType.Scatter, "$", Color.Green, ScatterMarkerSymbol.Triangle); rollOrders = new Series("Rolls", SeriesType.Scatter, "$", Color.Blue, ScatterMarkerSymbol.Square); ptsOrders = new Series("PTSs", SeriesType.Scatter, "$", Color.Crimson, ScatterMarkerSymbol.Square); sellOrders = new Series("Sells", SeriesType.Scatter, "$", Color.Red, ScatterMarkerSymbol.TriangleDown); dividendsS = new Series("Divs", SeriesType.Scatter, "$", Color.Pink, ScatterMarkerSymbol.Diamond); assetPrice = new Series("EOD Price", SeriesType.Line, "$", Color.Purple); varianceS = new Series("Variance", SeriesType.Line, "$", Color.Magenta); //stochastics = new Series("Stochastics", SeriesType.Line, "%", Color.DarkBlue); assetPrice.Index = 0; buyOrders.Index = 0; rollOrders.Index = 0; ptsOrders.Index = 0; sellOrders.Index = 0; dividendsS.Index = 0; //varianceS.Index = 1; //stochastics.Index = 1; //stochastics.Index = 2; stockPlot.AddSeries(buyOrders); stockPlot.AddSeries(rollOrders); stockPlot.AddSeries(ptsOrders); stockPlot.AddSeries(sellOrders); stockPlot.AddSeries(dividendsS); stockPlot.AddSeries(assetPrice); //stockPlot.AddSeries(varianceS); //stockPlot.AddSeries(stochastics); AddChart(stockPlot); GetTickers(); exDividendDates = GetDividendDates(); if (exDividendDates == null) if (doTracing) Debug("|||||||||||||||||| MISSING DIV DATES |||||||||||||||"); fedFundsRates = GetFedFundsRates(); if (fedFundsRates == null) if (doTracing) Debug("|||||||||||||||||| MISSING FED FUNDS |||||||||||||||"); ibkrHairCuts = InitializeHaircuts(); // per Rahul, set the Security Initializer to a custom proc // so the Options we add will be "warmed up" to provide prices SetSecurityInitializer(HistoricalSecurityInitializer); } // Initialize() /// OnData event is the primary entry point for the algorithm. Each new data point will be pumped in here. /// Slice object keyed by symbol containing the stock and options data public void OnData(TradeBars tbData) { // Does this update the indicators? } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void OnData(Dividends dData) { try{ if (Portfolio.Invested) { // DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS // If there's a dividend in this Slice, log the payment amount in tradeRecs. if(dData.ContainsKey(thisSymbol)) { int k = 0; // counter for updates var paymentAmount = dData[thisSymbol].Distribution; if (doTracing) Debug(" DDDDDDDDDD DDDDDDDDDDD DIVIDENDS FOR " + thisSymbol + " ARE " + paymentAmount); if (tradeRecs.Any(tpr=> tpr!=null && tpr.isOpen && tpr.uSymbol.Equals(thisSymbol))) { foreach(var tprec in tradeRecs.Where(tpr=> tpr.isOpen && tpr.uSymbol.Equals(thisSymbol))) { tprec.numDividends = tprec.numDividends + 1; tprec.divIncome = tprec.divIncome + paymentAmount; k = k + 1; } } if (doTracing) Debug(" DDDDDDDDDD DDDDDDDDDDD UPDATED " + k.ToString() + " TRADE PERF RECORDS. "); if (doTracing) Debug("-"); Plot("Stock Chart", "Divs", divPlotValue); } // DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS DIVIDENDS } } catch (Exception errMsg) { if (doTracing) Debug(" DIV ERROR DIV ERROR DIV ERROR " + errMsg); return; } } public override void OnData(Slice data) { if (!IsMarketOpen(thisSymbol)) return; //if (!sto.IsReady | !variance.IsReady) return; //check if bar data exists in current Slice before attempting to access if(!data.Bars.ContainsKey(thisSymbol)) return; // only process every quarter hour (and the subsequent minute for useDeltas) //if (data.Time.Minute != 0 & data.Time.Minute != 1 & data.Time.Minute != 15 & data.Time.Minute != 16 & data.Time.Minute != 30 & data.Time.Minute != 31 & data.Time.Minute != 45 & data.Time.Minute !=46) return; /// restrict processing to each quarter hour because options resolution is 1 minute if (data.Time.Minute != 0 & data.Time.Minute != 15 & data.Time.Minute != 30 & data.Time.Minute != 45 & data.Time.Minute !=46) return; /// restrict processing to each quarter hour because options resolution is 1 minute goodThresh = false; // set the threshold switch to false; hasPrimaryRec = hasSecondaryRec = false; // reset processing branch flags if (CheckBadDate(data.Time)) { //////////////// USE THIS DIAGNOSTICALLY /////////////////////////////// haltProcessing = true; Debug(" @@@@@@ BAD DATE @@@@@@@@@@ The price of " + thisSymbol + " is " + data[thisSymbol].Price); foreach(var kvp in Securities) { var security = kvp.Value; if (security.Invested) { Debug($" |-|-|-|- HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } } else haltProcessing = false; if (haltProcessing) { Debug(" HALTED IN ONDATA()"); } if (data.Time.Minute == 0 | data.Time.Minute == 15 | data.Time.Minute == 30 | data.Time.Minute == 45) { try { if (oLOs != null && oLOs.Count > 0) { Debug(" |()|()|() We have " + oLOs.Count+ " open limit order tickets"); List<int> RemovableLOs = new List<int>(); foreach (OpenLimitOrder olo in oLOs) { int currentLO = oLOs.IndexOf(olo); string oloMsg = ""; if (doTracing) Debug(" |()|()|() Found a " + olo.oTicket.Status + " limit order."); if(olo.oTicket.Status == OrderStatus.Canceled | olo.oTicket.Status == OrderStatus.Invalid ) { if (doTracing) Debug(" |()|()|() Found a " + olo.oTicket.Status + " order and marking olo record index " + currentLO + " for removal."); RemovableLOs.Add(currentLO); } if (olo.oTicket.Status == OrderStatus.Filled) { TradePerfRec updateTPR = olo.tpr; if (olo.oRight == OptionRight.Call) { if (olo.isWingCall) { if (doTracing) Debug(" |()|()|() Found a filled wing call closing limit order and updated end price to " + olo.oTicket.AverageFillPrice); updateTPR.wcEndPrice = olo.oTicket.AverageFillPrice; } else { if (olo.oTicket.Quantity < 0 ) { // Sell order for Short Call -- initializing a collar updateTPR.cStartPrice = olo.oTicket.AverageFillPrice; if (doTracing) Debug(" |()|()|() Found a filled collar initiating short call limit order and updated start price to " + olo.oTicket.AverageFillPrice); } else { updateTPR.cEndPrice = olo.oTicket.AverageFillPrice; if (doTracing) Debug(" |()|()|() Found a filled collar ending short call limit order and updated end price to " + olo.oTicket.AverageFillPrice); } } } else if (olo.oRight == OptionRight.Put) { updateTPR.pEndPrice = olo.oTicket.AverageFillPrice; } if (doTracing) Debug(" |()|()|() marking olo at " + currentLO + " for removal "); RemovableLOs.Add(currentLO); // oLOs.RemoveAt(currentLO); } else if (olo.oTicket.Status == OrderStatus.Submitted & (int)data.Time.Subtract(olo.oTicket.Time).TotalMinutes > 15) { TradePerfRec updateTPR = olo.tpr; if (olo.oRight == OptionRight.Call) { var option = (Option)Securities[olo.oTicket.Symbol]; var orderUnderlyingPrice = option.Underlying.Price; //var orderUnderlyingPrice = Securities[olo.oTicket.Symbol.ID.Underlying.Symbol].Price; // this error out on {T} b/c Underlying.Symbol gives a "Ticker" per Alex var orderLimitPrice = olo.oTicket.Get(OrderField.LimitPrice); var orderStrikePrice = olo.oTicket.Symbol.ID.StrikePrice; var lPrice = orderUnderlyingPrice - orderStrikePrice + 0.10M; if (orderLimitPrice < orderUnderlyingPrice - orderStrikePrice + 0.10M) { /// this is the criteria for placing a call buyback limit order. This contition will exist if the underlying price has moved up //Debug(" |()|()|() with " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + ", updating " + olo.oTicket.Symbol + "limit order to new limit price: " + lPrice ); //Ticket.Update(new UpdateOrderFields{LimitPrice = orderUnderlyingPrice - orderStrikePrice + 0.10M}); olo.oTicket.Cancel(); Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); Debug(" |()|()|() , updating " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + " limit order to market order"); var callTkt = MarketOrder(olo.oTicket.Symbol, olo.oTicket.Quantity); if (callTkt.Status == OrderStatus.Filled ){ if (olo.isWingCall) { updateTPR.wcEndPrice = callTkt.AverageFillPrice; if (doTracing) Debug(" |()|()|() Converted and filled wing call limit to market order and updated end price to " + callTkt.AverageFillPrice); } else { if (olo.oTicket.Quantity < 0){ // this is a collar initiating sell short call order updateTPR.cStartPrice = callTkt.AverageFillPrice; if (doTracing) Debug(" |()|()|() Converted and filled short call sell limit to market order and updated end price to " + callTkt.AverageFillPrice); } else { updateTPR.cEndPrice = callTkt.AverageFillPrice; if (doTracing) Debug(" |()|()|() Converted and filled short call sell limit to market order and updated end price to " + callTkt.AverageFillPrice); } } // oLOs.RemoveAt(currentLO); if (doTracing) Debug(" |()|()|() marking converted olo at " + currentLO + " for removal "); RemovableLOs.Add(currentLO); } } else { Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); Debug(" |()|()|() , waiting on " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + "limit order fulfillment."); } } else if (olo.oRight == OptionRight.Put) { var option = (Option)Securities[olo.oTicket.Symbol]; var orderUnderlyingPrice = option.Underlying.Price; // var orderUnderlyingPrice = Securities[olo.oTicket.Symbol.ID.Underlying.Symbol].Price; var orderLimitPrice = olo.oTicket.Get(OrderField.LimitPrice); var orderStrikePrice = olo.oTicket.Symbol.ID.StrikePrice; var lPrice = orderStrikePrice - orderUnderlyingPrice + 0.10M; if (orderLimitPrice > orderStrikePrice - orderUnderlyingPrice + 0.10M) { /// this is the criteria for placing a call buyback limit order. This contition will exist if the underlying price has moved up //Debug(" |()|()|() with " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + ", updating " + olo.oTicket.Symbol + "limit order to new limit price: " + lPrice ); //Ticket.Update(new UpdateOrderFields{LimitPrice = orderUnderlyingPrice - orderStrikePrice + 0.10M}); olo.oTicket.Cancel(); Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); Debug(" |()|()|() , updating " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + " limit order to market order"); var sellPutTkt = MarketOrder(olo.oTicket.Symbol, olo.oTicket.Quantity); if (sellPutTkt.Status == OrderStatus.Filled ){ updateTPR.pEndPrice = sellPutTkt.AverageFillPrice; if (doTracing) Debug(" |()|()|() Converted and filled long put sell limit to market order and updated end price to " + sellPutTkt.AverageFillPrice); if (doTracing) Debug(" |()|()|() marking converted olo at " + currentLO + " for removal "); RemovableLOs.Add(currentLO); } } else { Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); Debug(" |()|()|() , waiting on " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + "limit order fulfillment."); } } // OptionRight == call or put } // ticket = submitted } // for each OLO in oLOs if (haltProcessing) { Debug(" HALTED IN OLO PROCESSING"); } if (RemovableLOs.Count > 0 ) { if (haltProcessing) { Debug(" ************ HALT IN REMOVABLE OLO PROCESSING "); } if (doTracing) Debug(" |||| |||| |||| ITERATING REMOVABLE LOs DIAGNOSTICALLY " ); foreach (int r in RemovableLOs ) { if (doTracing) Debug(" |||| |||| RemovableOLO @ index:" + r + " = " + oLOs[r].oTicket.Symbol); } for (int i = RemovableLOs.Count - 1; i > -1; i--) // have to count down because deleting olo[0] resets next olo to 0. { int rOLO = RemovableLOs[i]; if (doTracing) Debug(" |||| |||| Removing OLO @ index " + RemovableLOs[i] + " for " + oLOs[rOLO].oTicket.Symbol); oLOs.RemoveAt(rOLO); } } } // if oLOs.Count > 0 } catch (Exception errMsg) { Debug(" ERROR " + errMsg ); if (errMsg.Data.Count > 0) { Debug(" Extra details:"); foreach (DictionaryEntry de in errMsg.Data) Debug(" Key: {0,-20} Value: {1}'" + de.Key.ToString() + "'" + de.Value); } } } /// if 0,15,30, 45 //if (haltProcessing) if (didTheTrade) { Debug($" |||| |||| |||| DID A TRADE "); foreach(var kvp in Securities) /// make sure there's no leaking of abandoned stocks or options { var security = kvp.Value; if (security.Invested) { //saveString = "," + security.Symbol + ", " + security.Holdings.Quantity + Environment.NewLine; Debug($" |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } //Debug(" HALTED IN ONDATA() "); didTheTrade = false; } if (newRollDate.GetHashCode() == 0) newRollDate = data.Time.Date; if (oldRollDate.GetHashCode() == 0) oldRollDate = data.Time.Date; // get the next ex-dividend date for initializing positions or managing them string tickerString = thisSymbol.Value; DateTime nextExDate = getNextExDate(tickerString, data.Time); if (nextExDate.Equals(DateTime.MinValue)) { if (doTracing) Debug("----- NULL nextExDate ----- NULL nextExDate -----"); hasDividends = false; return; } else hasDividends = true; stockPrice = data[thisSymbol].Price; if (divPlotValue == 0) { divPlotValue = stockPrice - 5; } thisROC = 0; thisROR = 0; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // IF NOT INVESTED AT ALL OR IF LESS THAN // QUARTER'S ALLOCATION, CREATE AND TEST // POTENTIAL OPTIONS COLLARS AND IF // GOOD, ESTABLISH A POSITION // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// heldValue = Portfolio[thisSymbol].HoldingsValue; // get the if (heldValue < stockDollarValue) { sharesToBuy = Math.Round((stockDollarValue-heldValue)/stockPrice/100, 0) * 100; if ( sharesToBuy >= 1000M ) { buyMoreShares = true; } else buyMoreShares = false; } if (haltProcessing) { Debug(" --- "); } hasPrimaryRec = tradeRecs.Any(t => t!=null && t.isOpen && !t.isSecondary && t.uSymbol.Equals(thisSymbol)); hasSecondaryRec = false; secndRecCount = 0; // reset the 2ndTPR processing loop counter //hasSecondaryRec = tradeRecs.Any(t => t!=null && t.isOpen && t.isSecondary && t.uSymbol.Equals(thisSymbol)); hasThetaRec = tradeRecs.Any(t => t!=null && t.isOpen && t.isTheta && t.uSymbol.Equals(thisSymbol)); try { if (!hasPrimaryRec && buyMoreShares & hasDividends) { // get the underlying stock price in this Slice if (nextExDate.Month == data.Time.Month) { ///////////////////////////// ONLY TEST EVERY 15 QUARTER HOUR AND EACH SUBSEQUENT MINUTE if (data.Time.Minute != 0 & data.Time.Minute != 1 & data.Time.Minute != 15 & data.Time.Minute != 16 & data.Time.Minute != 30 & data.Time.Minute != 31 & data.Time.Minute != 45 & data.Time.Minute !=46) return; if (doTracing) Debug($" ----- PROCESSING COLLAR INITIAZATION: {thisSymbol}"); if (doTracing) Debug(" ----- "); if (useDeltas) // if Greeks.Delta will be used to calculate most optimal trade { if(data.Time.Minute == 0 | data.Time.Minute == 15 | data.Time.Minute == 30 | data.Time.Minute == 45) { potentialCollars = GetPotentialCollars(data, thisSymbol, nextExDate); // get a list of potential collars and evaluate deltas in next minute return; // leave thisSlice and evaluate potentialCollars in the next minute when options greeks are available } else if ((potentialCollars.Count != 0) && (data.Time.Minute == 1 | data.Time.Minute == 16 | data.Time.Minute == 31 | data.Time.Minute == 46) ) { bestSSQRColumn = GetBestSSQRFromPotentialCollars(data, thisSymbol, nextExDate, potentialCollars); potentialCollars.Clear(); // Clear the PotentialCollars list. NOTE: this will obviate subsequent "next minute" evaluations callLoopCntr = 0; if (bestSSQRColumn == null) return; // if there's no bestSSQRColumn, then loop around and try again } else return; // on next minute in useDeltas, potentialCollars.Count == 0, return and try again } else { // not using deltas -- original execution path if(data.Time.Minute != 0 & data.Time.Minute != 15 & data.Time.Minute != 30 & data.Time.Minute != 45) return; potentialCollars = GetPotentialCollars(data, thisSymbol, nextExDate); // get a list of potential collars if (potentialCollars.Count == 0) { if (doTracing) Debug($"----- NO COLLARS RETURNED IN PROCESSING INITIAZATION: {thisSymbol}"); return; // if there's no collars, return and process again.rollOrders } bestSSQRColumn = GetBestSSQRFromPotentialCollars(data, thisSymbol, nextExDate, potentialCollars); // separate GetBestSSQRColum here to Log only the traded SSQRMatrices (reducing log size) if (bestSSQRColumn == null) { if (doTracing) Debug($"----- NULL bestSSQR RETURNED IN PROCESSING INITIAZATION: {thisSymbol}"); return; // if there's no bestSSQRColumn, then loop around and try again } } if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty()) // just in case somehow we got here with a null bestSSQRColumn { potentialCollars.Clear(); callLoopCntr = 0; if (doTracing) Debug($" ************** null OR EMPTY bestSSQR in Trade Initializing {thisSymbol} *************"); return; } else { if (haltProcessing) { Debug($" ******************* ITERATING UNORDERED POTENTIAL COLLARS -- DIAGNOSING IN HALT PROCESSING: {thisSymbol}"); IterateSSQRMatrix(potentialCollars); } if (!bestSSQRColumn.IsEmpty()) { Debug($" ******************* EXECUTING BESTSSQRCOLUMN -- {thisSymbol} --- "); ExecuteTrade(data, bestSSQRColumn); if (didTheTrade) { oldRollDate = data.Time.Date; var orderedSSQRMatrix = potentialCollars.OrderByDescending(p => p.CCOR); IterateOrderedSSQRMatrix(orderedSSQRMatrix); //didTheTrade = false; } } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return; } } // if data.Time.Month == nextExDividendDate.Month } // if !Portfolio.Invested else // POSITION MANAGEMENT --- Options Expiry and Ex-Dividend Approachment { if (!useDeltas && (data.Time.Minute != 0 & data.Time.Minute != 15 & data.Time.Minute != 30 & data.Time.Minute != 45)) return; // don't process follow-on minutes if (hasPrimaryRec) { //if (doTracing) Debug(" CCCCCCCCCCCCCC HAS CURRENT PRIMARY RECORD." ); crntTPR = tradeRecs.Where(t => t.isOpen && !t.isSecondary && t.uSymbol.Equals(thisSymbol)).FirstOrDefault(); if (data.Time.Subtract(crntTPR.startDate).Days == 0) return; // don't evaluate the TPR for trade progress on the first day if (crntTPR.cSymbol == null) return; // don't evaluate the TPR for trade progress until cSymbol is placed shortedCallSymbol = crntTPR.cSymbol; longPutSymbol = crntTPR.pSymbol; wingCallSymbol = crntTPR.wcSymbol; expireDateDeltaC = shortedCallSymbol.ID.Date.Subtract(data.Time); expireDateDeltaP = longPutSymbol.ID.Date.Subtract(data.Time); callStrike = shortedCallSymbol.ID.StrikePrice; putStrike = longPutSymbol.ID.StrikePrice; wcStrike = wingCallSymbol.ID.StrikePrice; curr1stTPRPPrice = Securities[longPutSymbol].BidPrice; curr1stTPRPAsk = Securities[longPutSymbol].AskPrice; curr1stTPRCPrice = Securities[shortedCallSymbol].AskPrice; //curr1stTPRCBid = Securities[shortedCallSymbol].BidPrice; currSellPnL = (crntTPR.uQty*(stockPrice-crntTPR.uStartPrice)) + (100*crntTPR.pQty*(curr1stTPRPPrice - crntTPR.pStartPrice)) + (-100*crntTPR.cQty*(crntTPR.cStartPrice - curr1stTPRCPrice)) + (100*crntTPR.wcQty*(crntTPR.wcEndPrice - crntTPR.wcStartPrice)); currExrcsPutPnL = (crntTPR.uQty*(crntTPR.pStrike-crntTPR.uStartPrice)) + (100*crntTPR.pQty*(0 - crntTPR.pStartPrice)) + (-100*crntTPR.cQty*(crntTPR.cStartPrice - curr1stTPRCPrice)) + (100*crntTPR.wcQty*(crntTPR.wcEndPrice - crntTPR.wcStartPrice)); currExrcsCallPnL = (crntTPR.uQty*(crntTPR.cStrike-crntTPR.uStartPrice)) + (100*crntTPR.pQty*(curr1stTPRPPrice - crntTPR.pStartPrice)) + (-100*crntTPR.cQty*(crntTPR.cStartPrice - 0)) + (100*crntTPR.wcQty*(crntTPR.wcEndPrice - crntTPR.wcStartPrice)); } if (hasThetaRec){ var currThetaRecs = tradeRecs.Where(t => t.isOpen && t.isTheta && t.uSymbol.Equals(thisSymbol)); foreach (TradePerfRec tTPR in currThetaRecs) { thetaCallSymbol = tTPR.cSymbol; expireDateDeltaTC = thetaCallSymbol.ID.Date.Subtract(data.Time); if (expireDateDeltaTC.Days <= 10){ var tCallPrice = (Securities[thetaCallSymbol].AskPrice - Securities[thetaCallSymbol].BidPrice)/2; if (tCallPrice + tTPR.cStrike < stockPrice) { } } } } if (haltProcessing) { Debug(" HALTED IN INVESTED OnData() ||"); } if (hasPrimaryRec){ newRollDate = data.Time.Date; // 2021-02-06 -- added this newRollDate here to make sure it's set //if (!newRollDate.Equals(oldRollDate)) return; // 2021-01-08 -- added this validation to prevent evaluations of conditions that would result in abrogation int dayDelta = nextExDate.Date.Subtract(data.Time).Days; // DIVIDEND APPROACHMENT DIVIDEND APPROACHMENT DIVIDEND APPROACHMENT DIVIDEND APPROACHMENT DIVIDEND APPROACHMENT if (dayDelta < 4 && dayDelta > 0) // check every time slice for call assignment ??--ask John { // About differentiating calls shorted for Theta Harvesting, ask John if those are weekly options?? // Should we, and if so, how do we differentiate those in the context of call assignment during // ex-dividend approachment // -------------------------------------------------------------------------------------- // // /// /// return true in CheckDividendRoll if corresponding put premium is less than dividend amount. Otherwise, return false and continue processing if (CheckDividendRoll (data, nextExDate, crntTPR, shortedCallSymbol, longPutSymbol, stockPrice, dayDelta) ) { return; } } // DIVIDEND APPROACHMENT DIVIDEND APPROACHMENT DIVIDEND APPROACHMENT DIVIDEND APPROACHMENT DIVIDEND APPROACHMENT // WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION if (crntTPR.wcSymbol != null && crntTPR.wcQty != 0 && crntTPR.wcEndPrice == 0) { if ( (stockPrice - wcStrike)/stockPrice >= .05M && expireDateDeltaP.Days <= 5){ if (doTracing) Debug(" ** ** ** SELLING ITM WING CALL SELLING ITM WING CALL SELLING ITM WING CALL"); var wcSellTkt = MarketOrder(crntTPR.wcSymbol, -crntTPR.wcQty); if (wcSellTkt.Status == OrderStatus.Filled) { crntTPR.wcEndPrice = wcSellTkt.AverageFillPrice; } } } // WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION WING CALL EVALUATION // CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE if (((stockPrice - callStrike)/stockPrice >= .05M && expireDateDeltaC.Days <= 10 && expireDateDeltaC.Days > 1) || ((stockPrice - callStrike) > 0 && expireDateDeltaC.Days <= 1)) //} else if ((callStrike + curr1stTPRCPrice) < stockPrice && expireDateDeltaC.Days <= 10 ) { if (CheckCallRoll(data, nextExDate, crntTPR, shortedCallSymbol, longPutSymbol, stockPrice, expireDateDeltaC.Days)) { return; } } // CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE CALL EXERCISE // PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE else if (( (putStrike - stockPrice )/stockPrice >= .05M && expireDateDeltaP.Days <= 10 && expireDateDeltaP.Days > 1) || ( (putStrike > stockPrice) && expireDateDeltaP.Days <= 1) ) //else if ( (putStrike + curr1stTPRPAsk) > stockPrice && expireDateDeltaP.Days <= 10 ) { if (CheckPutRoll(data, nextExDate, crntTPR, shortedCallSymbol, longPutSymbol, stockPrice, expireDateDeltaC.Days)) { return; } } // PUT if 5% over stock price -- what should we do? // PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE PUT EXERCISE // NORMAL EXPIRATION NORMAL EXPIRATION NORMAL EXPIRATION NORMAL EXPIRATION NORMAL EXPIRATION NORMAL EXPIRATION // PRESUMABLY ALL POTENTIAL ASSIGNMENTS ARE TRAPPED BY THIS POINT IN THE EXECUTION // if both the put and call are OTM else if ((expireDateDeltaC.Days <= 1 && stockPrice <= callStrike) | (expireDateDeltaP.Days <= 1 && stockPrice >= putStrike)) // this is the put expiration by design. the puts always control the collar and the risk { if (CheckOTMRoll(data, nextExDate, crntTPR, shortedCallSymbol, longPutSymbol, stockPrice, expireDateDeltaC.Days, expireDateDeltaP.Days)) { return; } } // normal expiration } // hasPrimaryRecd } // PortfolioInvested -- trade management } catch (Exception errMsg) { Debug(" ERROR " + errMsg ); if (errMsg.Data.Count > 0) { Debug(" Extra details:"); foreach (DictionaryEntry de in errMsg.Data) Debug(" Key: {0,-20} Value: {1}'" + de.Key.ToString() + "'" + de.Value); } return; } } // OnData() /// Rahul said this is beta and possibly inactive public void OnAssignmentOrderEvent(OrderEvent assignmentEvent) { //if (doTracing) Debug("ASSIGNMENT ==== " + assignmentEvent.Symbol.Value); Debug("AAAAAAAAAAAAAAA ASSIGNMENT ==== " + assignmentEvent.Symbol.Value); } // ********************** OnOrderEvent *********************************************** // *** Generalized function to iterate through and print members of an IEnumerable of Contracts // *** This is used for debugging only tricky part is passing an IOrderedEnumerable into this // **************************************************************************************************** public override void OnOrderEvent(OrderEvent orderEvent) { var order = Transactions.GetOrderById(orderEvent.OrderId); var oeSymb = orderEvent.Symbol; if (haltProcessing) { Debug(" HALTED IN ONORDER()"); } Debug(" OO+++ " + order.Type + " order for " + oeSymb + ", Order Status: " + orderEvent.Status); try { if (orderEvent.Status == OrderStatus.Filled) { //var order = Transactions.GetOrderById(orderEvent.OrderId); //var oeSymb = orderEvent.Symbol; if (order.Type == OrderType.OptionExercise) { Debug(" OO OPTION EXERCISE ORDER EVENT AT:" + orderEvent.UtcTime + " OOOO"); if (orderEvent.IsAssignment) { // .IsAssignment seems only to occur when LEAN creates the ASSIGNMENT. -- use this to troubleshoot // Check for this now because DIVIDEND APPROACHMENT may Debug(" OO " + orderEvent.UtcTime + " LEAN LEAN LEAN ASSIGNMENT ORDER EVENT LEAN LEAN LEAN OOOOOO"); Debug(" OO ASSIGNMENT SYMBOL: " + oeSymb ); if (oeSymb.HasUnderlying && oeSymb.ID.OptionRight == OptionRight.Call) { sellThePut = true; } } Debug(" OO Quantity: " + orderEvent.FillQuantity + ", price: " + orderEvent.FillPrice); if (oeSymb.HasUnderlying) { didTheTrade = true; var thisOption = (Option)Securities[oeSymb]; var stkSymbol = thisOption.Underlying; Debug(" OO OPTIONS ORDER FOR : " + oeSymb + " IS A " + (oeSymb.ID.OptionRight == OptionRight.Put ? "PUT. " : "CALL.") + "for underlying: " + stkSymbol); // Get the open tradePerfRecord (if any still exists) ??? what is the order of exercise events ??? // tradePerfRec Call termination handled in code prior to PUT EXERCISE // Execute TradePerfRec Underlying termination in OnOrder() upon Stock Assignment if(oeSymb.ID.OptionRight == OptionRight.Put) { Debug(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo "); Debug(" oo PUT OPTION EXERCISE ORDER FOR : " + oeSymb); Debug(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo "); if (tradeRecs.Any(t => t!=null && t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity)) { var pTPR = tradeRecs.Where(t => t!=null && t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity).FirstOrDefault(); pTPR.pEndPrice = orderEvent.FillPrice; Debug(" OO UPDATED PUT END PRICE TO : " + orderEvent.FillPrice); if (pTPR.cSymbol != null) { var shrtCall = (Option)Securities[pTPR.cSymbol]; TimeSpan daysToCallExpiry = shrtCall.Expiry.Subtract(orderEvent.UtcTime); /*if (daysToCallExpiry.Days > 10 ) { Debug(" OO CALL " + shrtCall + " EXPIRES IN " + daysToCallExpiry.Days + ". CREATING THETA TPR."); // create a thetaTPR to move the call data and track it. Buy it back when theta decays. TradePerfRec newThTPR = new TradePerfRec(); newThTPR.uSymbol = pTPR.uSymbol; newThTPR.index = pTPR.index; newThTPR.isOpen = pTPR.isOpen; newThTPR.isInitializer = true; newThTPR.isSecondary = true; newThTPR.isTheta = true; newThTPR.startDate = pTPR.startDate; newThTPR.strtngCndtn = "SPINNING OFF THETA CALLS"; newThTPR.expDate = shrtCall.Expiry; newThTPR.cSymbol = pTPR.cSymbol; newThTPR.cQty = pTPR.cQty; newThTPR.cStartPrice = pTPR.cStartPrice; newThTPR.tradeCriteria = pTPR.tradeCriteria; tradeRecs.Add(newThTPR); pTPR.cSymbol = null; // eliminate the call from the existint TPR pTPR.cStartPrice = 0; pTPR.cQty = 0; } else { */ Debug(" OO SELLING THE CALL IF IT EXISTS"); var closeCTkt = MarketOrder(pTPR.cSymbol, -pTPR.cQty); if (closeCTkt.Status == OrderStatus.Filled) { pTPR.cEndPrice = closeCTkt.AverageFillPrice; } //} } Debug(" OO SELLING THE WING CALL IF IT EXISTS"); if (pTPR.wcSymbol != null) { //var wingCall = (Option)Securities[pTPR.wcSymbol]; var closeWingTkt = MarketOrder(pTPR.wcSymbol, -pTPR.wcQty); if (closeWingTkt.Status == OrderStatus.Filled) { pTPR.wcEndPrice = closeWingTkt.AverageFillPrice; } } } else { // 1st TPR in PUT EXERCISE Debug(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo "); Debug(" oo PUT OPTION ORDER FOR : " + oeSymb); Debug(" oo NOT SURE HOW THIS WAS ACCESSED - NO 1st TPR FOUND "); Debug(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo "); //string jsonString = ConvertTradePerfRec(tradeRecs); /* if (tradeRecs.Any(t => t!=null && t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity)) { var cTPR = tradeRecs.Where(t => t!=null && t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity).FirstOrDefault(); cTPR.cEndPrice = orderEvent.FillPrice; Debug(" OO UPDATED CALL END PRICE TO : " + orderEvent.FillPrice); Debug(" OO SELLING THE PUT IF IT EXISTS"); if (cTPR.cSymbol != null) { var closePTkt = MarketOrder(cTPR.pSymbol, -cTPR.pQty); if (closePTkt.Status == OrderStatus.Filled) { cTPR.pEndPrice = closePTkt.AverageFillPrice; } } } */ } } else if (oeSymb.ID.OptionRight == OptionRight.Call){ Debug(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo "); Debug(" oo CALL OPTION EXERCISE ORDER FOR : " + oeSymb); Debug(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo "); if (tradeRecs.Any(t => t!=null && t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity)) { Debug(" oo oo FOUND SHORT CALL 1ST TPR oo "); var cTPR = tradeRecs.Where(t => t!=null && t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity).FirstOrDefault(); cTPR.cEndPrice = orderEvent.FillPrice; Debug(" oo oo UPDATED 1ST TPR SHORT CALL END PRICE TO : " + orderEvent.FillPrice); if (cTPR.pSymbol != null) { var longPut = (Option)Securities[cTPR.pSymbol]; Debug(" oo SELLING THE PUT IF IT EXISTS"); var closePTkt = MarketOrder(cTPR.pSymbol, -cTPR.pQty); if (closePTkt.Status == OrderStatus.Filled) { cTPR.pEndPrice = closePTkt.AverageFillPrice; } } if (cTPR.wcSymbol != null) { var wCallSymbol = (Option)Securities[cTPR.wcSymbol]; Debug(" oo oo oo SELLING THE WING CALL IF IT EXISTS OR HASN'T BEEN BOUGHT"); if (cTPR.wcEndPrice != 0) { Debug(" oo oo oo oo SELLING THE WING CALL"); var closeWCTkt = MarketOrder(cTPR.wcSymbol, -cTPR.wcQty); if (closeWCTkt.Status == OrderStatus.Filled) { cTPR.wcEndPrice = closeWCTkt.AverageFillPrice; } } else Debug(" oo oo oo oo THE WING CALL WAS ALREADY SOLD"); } //// THE FOLLOWING WOULD EXECUTE IF ALGO EXERCISED THE WING CALL -- NOT CONTEMPLATED } else if (tradeRecs.Any(t => t!=null && t.wcSymbol.Equals(oeSymb) & t.wcQty == order.Quantity)) { var wcTPR = tradeRecs.Where(t => t!=null && t.wcSymbol.Equals(oeSymb) & t.wcQty == order.Quantity).FirstOrDefault(); Debug(" oo FOUND SHORT CALL 1ST TPR oo "); Debug(" oo UPDATED WING CALL END PRICE TO : " + orderEvent.FillPrice); wcTPR.wcEndPrice = orderEvent.FillPrice; } } } else { /// !.HasUnderlying -- this is stock being assigned Debug(" oo ASSIGNMENT OF UNDERLYING ORDER FOR : " + oeSymb); Debug(" oo STOCK EXERCISE ORDER EVENT FOR: " + order.Quantity + " shares." ); if (haltProcessing) { Debug(" oo oo oo oo => HALTED IN OnOrder() "); } didTheTrade = true; if(tradeRecs.Any(t => t!=null && t.isOpen & t.uSymbol.Equals(oeSymb) & t.uQty == -order.Quantity * 100M)) { Debug(" oo UPDATING 2ND TRADE RECORD"); var uTPR = tradeRecs.Where(t => t!=null && t.isOpen & t.uSymbol.Equals(oeSymb) & t.uQty == -order.Quantity * 100M).FirstOrDefault(); Plot("Stock Chart", "Sells", orderEvent.FillPrice); tradeRecCount = 0; // reset tradeRec Counter ??? may be obviated uTPR.isOpen = false; uTPR.uEndPrice = orderEvent.FillPrice; uTPR.endDate = orderEvent.UtcTime; if (uTPR.reasonForClose !=null || uTPR.reasonForClose != "") { uTPR.reasonForClose = uTPR.reasonForClose + "OPTIONS ASSIGNMENT -- UNDERLYING CLOSED"; } else uTPR.reasonForClose = "OPTIONS ASSIGNMENT -- UNDERLYING CLOSED"; if (Portfolio[uTPR.pSymbol].Invested && uTPR.pSymbol != null) { var sellPutTicket = MarketOrder(uTPR.pSymbol, -uTPR.pQty); if (doTracing) Debug(" oo oo oo oo ooo Selling the PUT and setting the TPR.EndPrice"); if (sellPutTicket.Status == OrderStatus.Filled) { uTPR.pEndPrice = sellPutTicket.AverageFillPrice; } } if (Portfolio[uTPR.wcSymbol].Invested && uTPR.wcSymbol != null) { var sellWCallTicket = MarketOrder(uTPR.wcSymbol, -uTPR.wcQty); if (doTracing) Debug(" oo oo oo oo ooo Selling the Wing Call and setting the TPR.wcEndPrice"); if (sellWCallTicket.Status == OrderStatus.Filled) { uTPR.wcEndPrice = sellWCallTicket.AverageFillPrice; } } /// NOTE: OPTIONS WILL EXPIRE OR EXERCISE AT ENDPRICE = 0. THEREFORE THESE VALUES ARE NOT SET HERE /// BECAUSE THE END PRICES MAY BE SET OTHERWISE ELSEWHERE } else { Debug(" oo oo oo oo => FAILED TO LOCATE 2ND TPR"); } } Debug(" ---------------------------------------------------------------------------"); } // Order.Type = OrderType.OptionExercise else { Debug(" OO ** ** ** ** ** ** ** ** ** NON EXERCISE OPTION ORDER -- " + oeSymb); Debug(" OO ** " + order.Type + ": " + orderEvent.UtcTime + ": " + orderEvent.Direction + " ** OO "); Debug(" OO ** " + orderEvent.Status + ": " + orderEvent.Direction + " " + order.Quantity + " @ " + orderEvent.FillPrice ); if (oeSymb.HasUnderlying && order.Type == OrderType.Limit ) { /// Option if (oeSymb.ID.OptionRight == OptionRight.Put) { Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); Debug(" OO PUT OPTION LIMIT ORDER FOR : " + oeSymb); Debug(" OO PROCESSING TPR IN NEXT ON DATA oo oo oo oo "); Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); /*if (tradeRecs.Any(t => t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity)) { var transRec = tradeRecs.Where(t => t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity).FirstOrDefault(); transRec.pEndPrice = orderEvent.FillPrice; Debug(" OO ** Setting pEndPrice."); } */ //Debug(" OO NOTE PUT EXPIRATION execute a market order to sell underlying"); } else if (oeSymb.ID.OptionRight == OptionRight.Call) { Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); Debug(" OO CALL OPTION LIMIT ORDER FOR : " + oeSymb); Debug(" OO PROCESSING TPR IN NEXT ON DATA oo oo oo oo "); Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); /*if (tradeRecs.Any(t => t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity)) { var transRec = tradeRecs.Where(t => t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity).FirstOrDefault(); transRec.cEndPrice = orderEvent.FillPrice; Debug(" OO ** Setting cEndPrice."); }*/ //Debug(" OO NOTE CALL EXPIRATION execute a market order to sell underlying"); } } else if (oeSymb.HasUnderlying && order.Type == OrderType.Market) { if (oeSymb.ID.OptionRight == OptionRight.Put) { Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); Debug(" OO PUT OPTION MARKET ORDER FOR : " + oeSymb); Debug(" OO PROCESSING TPR SYNCHRONOUSLY IN LINE oo oo oo "); Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); /*if (tradeRecs.Any(t => t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity)) { var transRec = tradeRecs.Where(t => t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity).FirstOrDefault(); transRec.pEndPrice = orderEvent.FillPrice; Debug(" OO ** Setting pEndPrice."); } */ //Debug(" OO NOTE ALGO-DRIVEN PUT market order"); } else if (oeSymb.ID.OptionRight == OptionRight.Call) { Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); Debug(" OO CALL OPTION MARKET ORDER FOR : " + oeSymb); Debug(" OO PROCESSING TPR SYNCHRONOUSLY IN LINE oo oo oo "); Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); /*if (tradeRecs.Any(t => t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity)) { var transRec = tradeRecs.Where(t => t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity).FirstOrDefault(); transRec.cEndPrice = orderEvent.FillPrice; Debug(" OO ** Setting cEndPrice."); }*/ //Debug(" OO NOTE ALGO-DRIVEN CALL MARKET ORDER"); } } else if (!oeSymb.HasUnderlying) { Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); Debug(" OO UNDERLYING ORDER FOR : " + oeSymb); Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); } else { // NON EXERCISE ORDER HAS UNDERLYING Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); Debug(" OO UNKNOWN ALGO ORDER ORDER FOR : " + oeSymb); Debug(" OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO OO "); /*if (tradeRecs.Any(tpr => tpr.uSymbol.Equals(oeSymb) & tpr.uQty == -order.Quantity)) { var transRec = tradeRecs.Where(tpr => tpr.uSymbol.Equals(oeSymb) & tpr.uQty == -order.Quantity).FirstOrDefault(); Debug (" OO ** THERE IS A TPR THAT IS " + (transRec.isOpen ? " OPEN" : " CLOSED")); transRec.isOpen = false; transRec.uEndPrice = orderEvent.FillPrice; transRec.endDate = orderEvent.UtcTime; transRec.reasonForClose = "Options Expiration"; Plot("Stock Chart", "Sells", orderEvent.FillPrice); }*/ } Debug(" ---------------------------------------------------------------------------"); } } // orderStatus = Filled } catch (Exception errMsg) { Debug(" ERROR " + errMsg ); if (errMsg.Data.Count > 0) { Debug(" Extra details:"); foreach (DictionaryEntry de in errMsg.Data) Debug(" Key: {0,-20} Value: {1}'" + de.Key.ToString() + "'" + de.Value); } return; } } //private void CheckExpiration() { // foreach(var callOption in callOptions) { // if(callOption.Symbol.ID.Date == Time.Date) { // Debug($"{callOption.Symbol}"); // } //} // } public override void OnEndOfDay(Symbol symbol) { //lastSto = sto.Current.Value; // store values from night before lastAd = ad.Current.Value; lastAdOsc = adOsc.Current.Value; lastAdx = adx.Current.Value; lastAdxr = adxr.Current.Value; lastObv = obv.Current.Value; lastVariance = variance.Current.Value; Plot("Stock Chart", "EOD Price", Securities[thisSymbol].Price); //Plot("Stock Chart", "Variance", variance.Current.Value); //Plot("Stock Chart", "Stochastics", sto.Current.Value); Plot("Momentums" , "AD", ad.Current.Value); Plot("Momentums", "ADOSC", adOsc.Current.Value); Plot("Momentums", "OBV", obv.Current.Value); Plot("Trends", "ADX", adx.Current.Value); Plot("Trends", "ADXR", adxr.Current.Value); //string saveString = ""; foreach(var kvp in Securities) { var security = kvp.Value; if (security.Invested) { //saveString = "," + security.Symbol + ", " + security.Holdings.Quantity + Environment.NewLine; //Debug($"{security.Symbol} : {security.Holdings.Quantity}"); } } } public override void OnEndOfAlgorithm() { string saveString = ""; bool hasStock = false; bool hasPuts = false; bool hasCalls = false; var tprEnum = tradeRecs.GetEnumerator(); while (tprEnum.MoveNext()) { TradePerfRec tpr = tprEnum.Current; if (tpr.isOpen) { if (tpr.uEndPrice == 0 && tpr.cSymbol != null) { tpr.uEndPrice = Securities[tpr.uSymbol].Price; } if (tpr.pEndPrice == 0 && tpr.pSymbol != null) { tpr.pEndPrice = Securities[tpr.pSymbol].Price; } if (tpr.cEndPrice == 0 && tpr.cSymbol != null) { tpr.cEndPrice = Securities[tpr.cSymbol].Price; } tpr.endDate = Time; } } string jsonString = ConvertTradePerfRec(tradeRecs); } public override void OnSecuritiesChanged(SecurityChanges changes) { /* addedSecCount = addedSecCount + 1; foreach(var security in changes.AddedSecurities) { if (security.Type == SecurityType.Equity) return; Debug(" -- " + addedSecCount + " ADDED SECURTITIES CHANGES :" + security.Symbol); var history = History(security.Symbol, 60, Resolution.Minute); foreach (var histSymb in history) { Debug("History: @" + histSymb.EndTime + " " + histSymb); } } foreach(var security in changes.RemovedSecurities) { Debug($"{security.Symbol} removed at {Time}"); } // changes.AddedSecurities */ } private void HistoricalSecurityInitializer(Security security) { var bar = GetLastKnownPrice(security); security.SetMarketPrice(bar); if(security.Type == SecurityType.Option){ var openInterest = History<OpenInterest>(security.Symbol, TimeSpan.FromDays(1)); var tradeBar = History<TradeBar>(security.Symbol, TimeSpan.FromDays(1)); //////////////////////////// WARNING THIS LINE OF CODE PREVENTS OPTIONS FROM BEING PRICED ////////////////// //var optionChain = History<OptionChain>(security.Symbol, TimeSpan.FromDays(1)); } } private bool CheckBadDate(DateTime checkDate) { DateTime badDate1 = Convert.ToDateTime(GetParameter("BadDate")); DateTime badDate2 = badDate1.AddMinutes(1); //DateTime badDate1 = new DateTime(2020, 1, 6, 9, 45, 0); //DateTime badDate2 = new DateTime(2020, 11, 1, 13, 45, 0); if(checkDate.Equals(badDate1) | checkDate.Equals(badDate2)) { return badDtParameter; } else { return false; } } } // class } // namespace
using QuantConnect.Securities.Option; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public class SSQRColumn { public decimal stockPrice; public DateTime exDate; public DateTime optExpiry; public DateTime callExpiry; public int daysInPosition; public decimal interestCost; public Symbol putSymbol; public Symbol callSymbol; public Symbol wCallSymbol; public decimal putPremium; // paid for buying the body public decimal callPremium; // received for selling back call public decimal wCallPremium; // paid for buying the wings public decimal putStrike; public decimal callStrike; public decimal wCallStrike; public decimal putOpenInterest; public decimal callOpenInterest; public decimal putDelta; public decimal callDelta; public decimal wcDelta; public decimal wingFactor; public decimal putGamma; public decimal callGamma; public decimal putVega; public decimal callVega; public decimal putRho; public decimal callRho; public decimal putTheta; public decimal callTheta; public decimal putImpliedVol; public decimal callImpliedVol; public decimal divAmt; public int divCount; public decimal downsideRisk; public decimal upsidePotential; public decimal netIncome; public decimal netOptions; public decimal divDollars; public decimal haircut; // committed capital in a portfolio margin account public decimal ROC; // Return on Capital public decimal ROR; // Return on Risk public decimal CCOR; // Call Coverage over downside Risk public string description1; public string description2; //public string description3; public override string ToString() { return this.description1; } public bool IsEmpty() { return this.description1.IsNullOrEmpty(); } } } }
///////////////////////////// 2020-12-01: Added CCOR member to SSQR Column and to description2 for SSQR Matrices spreadsheet using System.Linq; using QuantConnect.Securities.Option; namespace QuantConnect.Algorithm.CSharp { // // Make sure to change "BasicTemplateAlgorithm" to your algorithm class name, and that all // files use "public partial class" if you want to split up your algorithm namespace into multiple files. // public partial class CollarAlgorithm : QCAlgorithm { // ********************** assembleSSQRMatrix ************************************** // *** This sub routine takes in the options expiries and all the symbols // *** available in the Slice.Data and builds the puts chains and calls symbols lists // *** The puts and calls symbols lists are used to build the contracts lists // ** The contracts lists are used to build the SSQR Matrix // *********************************************************************************** public List<SSQRColumn> assembleSSQRMatrix(Dictionary<int, DateTime> expiries, IEnumerable <Symbol> allUndrOptSymbs, decimal stockPrice, decimal amtDividend, DateTime tradeDate, DateTime thisExDivDate) { if (doTracing) Debug($" -- AA AA ASSEMBLE SSQR MATRIX FOR {thisSymbol}"); if (haltProcessing) { Debug(" @@@@@ Halted assembleSSQR processing"); } OptionChain putChain; // chain object to get put contracts OptionChain callChain; // chain object to get call contracts OptionChain wcChain; // chain object to get wc contracts Greeks putGreeks; // chain object to collect put greeks Greeks callGreeks; // chain object to collect call greeks Greeks wcGreeks; // chain object to collect wing call greeks List<SSQRColumn> SSQRMatrix = new List<SSQRColumn>(); List<Symbol> ntmSymbs = new List<Symbol>(); var callSymbolsForThisExpiry = allUndrOptSymbs; var putSymbolsForThisExpiry = allUndrOptSymbs; // Get the ATM call contract var atmCall = allUndrOptSymbs.Where(s => s.ID.OptionRight == OptionRight.Call) .OrderBy(s => Math.Abs(s.ID.StrikePrice - stockPrice)) .FirstOrDefault(); var atmPut = allUndrOptSymbs.Where(s => s.ID.OptionRight == OptionRight.Put) .OrderBy(s => Math.Abs(s.ID.StrikePrice - stockPrice)) .FirstOrDefault(); Option atmCallOpt = AddOptionContract(atmCall); Option atmPutOpt = AddOptionContract(atmPut); var atmStrike = atmCall.ID.StrikePrice; var lowStrike = (1 - (maxPutOTM / (decimal)100)) * atmStrike; // ~~ eventually need a mechanism to determine strike steps var highStrike = (decimal)1.1 * atmStrike; // ~~ and use strike steps to set upper and lower bounds // decimal highWCStrike = 0; // for evaluating the range of wing call strikes // decimal lowWCStrike = 0; // for evaluating the range of wing call strikes // decimal wcInterval = 0; // for calculating wing calls // examine options expiries to get proper dividend-correlated time-frames if (badDtParameter) debugSymbol = atmPut; // if debugging, set the debugSymbol to the atmPut for diagnostics // Alex informed me that the openInterest data has to be warmed up // Alex further informed me that .OpenInterest does not work, rather, use .Value var callOpenInterest = History<OpenInterest>(atmCall, TimeSpan.FromDays(5)).FirstOrDefault(); var putOpenInterest = History<OpenInterest>(atmPut, TimeSpan.FromDays(5)).FirstOrDefault(); var totOpenInterest = callOpenInterest.Value + putOpenInterest.Value; //if (doTracing) Debug("0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O"); //if (doTracing) Debug("0O0O0O0O0O0O0O0O0O0O0O0O OPEN INTEREST 0O0O0O0O0O0O0O0O0O0O0O0O0O0O0"); //if (doTracing) Debug(" The total Put + Call Open Interest for " + atmCall.ID.Underlying + " is " + atmCallOpt.OpenInterest + "."); //if (doTracing) Debug("0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O0O"); int k = 1; // initialize iterator for AddOptionContracts below Symbol optSymbol; // initialize option symbol for building the list of contracts Option tempOption; // initialize option contract for building list of contracts and obtaining pricing data List<Option> callOptionsList = new List<Option>(); List<Option> putOptionsList = new List<Option>(); List<Option> wcCallsList = new List<Option>(); decimal haircut; // for buliding SSQR -- used when trading decision is based upon return on committed capital int daysInTrade; // for building SSQR -- calculating interest cost Option thisPutOpt; // for building SSQR Option thisCallOpt; // for building SSQR Option thisWCCallOpt; Symbol thisPutSymb; // for building SSQR Symbol thisCallSymb; // for building SSQR Symbol thisWCCallSymb; // decimal thisPutPrem; // for building SSQR decimal thisCallPrem; // for building SSQR decimal thisWCCallPrem; // for building SSQR decimal thisPutStrike; // for building SSQR decimal thisCallStrike; // for building SSQR decimal thisWCCallStrike; // for building SSQR DateTime thisPutExpiry; // for building SSQR DateTime thisCallExpiry; // for building SSQR //DateTime thisExpiry; // for building SSQR //decimal putOpenInterest; //decimal callOpenInterest; //decimal putDelta; //decimal callDelta; //decimal putGamma; //decimal callGamma; //decimal putVega; //decimal callVega; //decimal putRho; //decimal callRho; //decimal putTheta; //decimal callTheta; //decimal putImpliedVol; //decimal callImpliedVol; int numDividends; // for building SSQR //decimal amtDividend; // for building SSQR decimal interestCost; // for building SSQR decimal amtSpread; // for building SSQR decimal amtTotCost; // for building SSQR DateTime whichExpiry = new DateTime(); //daysInTrade = ((TimeSpan) (whichExpiry - tradeDate)).Days; // get the # of days from trade date to expiry for carry cost ///////// NOTE : CATCH THE EXCEPTION WHERE LOOKUP FAILS var justDate = tradeDate.Date; // separate out the DATEVALUE from the DateTime variable thisFFRate = fedFundsRates[justDate]; // fedFundsRates is a Dictionary where DateTime index are all 12:00:00am //thisFFRate = thisFFRate; // fedFundsRates entries are expressed as percentages and were converted upon reading them into dictionary //interestCost = (thisFFRate + ibkrRateAdj)/workingDays * (decimal) daysInTrade * stockPrice; // create a range of expiration dates from the prior expiration to the whichExpiry date. //callSymbolsForThisExpiry = allUnderlyingOptionsSymbols.Where( o=> DateTime.Compare(o.ID.Date, pastExpiry) > 0 && DateTime.Compare(o.ID.Date, whichExpiry)<=0 && if (haltProcessing) { Debug("---------------------- Expiries ----------------------------"); Debug("-----" + stockPrice.ToString() + ", " + lowStrike.ToString() + ", " + highStrike.ToString() + ", " + expiries[2].ToString("MM/dd/yy") + ", " + expiries[3].ToString("MM/dd/yy") + ", " + expiries[4].ToString("MM/dd/yy") + ", " + expiries[5].ToString("MM/dd/yy")); //IterateChain(allUnderlyingOptionsSymbols, "allUnderlyingOptionsSymbols"); } callLoopCntr = callLoopCntr + 1; callSymbolsForThisExpiry = allUndrOptSymbs.Where( o=> ( DateTime.Compare(o.ID.Date, expiries[2])==0 | DateTime.Compare(o.ID.Date, expiries[3])==0 | DateTime.Compare(o.ID.Date, expiries[4])==0 | DateTime.Compare(o.ID.Date, expiries[5])==0 ) && o.ID.OptionRight == OptionRight.Call && o.ID.StrikePrice >= lowStrike && o.ID.StrikePrice < highStrike) .OrderByDescending(o => o.ID.StrikePrice); // **************** Check if any calls are returned. If not, increment expiries by 1 month and try again if (callSymbolsForThisExpiry == null | callSymbolsForThisExpiry.Count() == 0) { if (doTracing) Debug("-- get callSymbolsForThisExpiry failed 1st Pass "); expiries[2] = FindNextOptionsExpiry(expiries[1], 4); expiries[3] = FindNextOptionsExpiry(expiries[1], 7); expiries[4] = FindNextOptionsExpiry(expiries[1], 10); expiries[5] = FindNextOptionsExpiry(expiries[1], 13); callSymbolsForThisExpiry = allUndrOptSymbs.Where( o=> ( DateTime.Compare(o.ID.Date, expiries[2])==0 | DateTime.Compare(o.ID.Date, expiries[3])==0 | DateTime.Compare(o.ID.Date, expiries[4])==0 | DateTime.Compare(o.ID.Date, expiries[5])==0 ) && o.ID.OptionRight == OptionRight.Call && o.ID.StrikePrice >= lowStrike && o.ID.StrikePrice < highStrike) .OrderByDescending(o => o.ID.StrikePrice); if (haltProcessing) { if (doTracing) Debug("---------------------- Expiries 2nd Pass ----------------------------"); if (doTracing) Debug("--" + stockPrice.ToString() +", " + expiries[2].ToString("MM/dd/yy") + ", " + expiries[3].ToString("MM/dd/yy") + ", " + expiries[4].ToString("MM/dd/yy") + ", " + expiries[5].ToString("MM/dd/yy")); //if (doTracing);(callSymbolsForThisExpiry, "callSymbols"); } // ***************** If no calls are returned in 2nd pass, exit with null SSQRMatrix if (callSymbolsForThisExpiry == null || callSymbolsForThisExpiry.Count() == 0) { if (doTracing) Debug("-- get callSymbolsForThisExpiry failed 2nd Pass "); return SSQRMatrix; } else if (doTracing) Debug("-- get callSymbolsForTheseExpiries succeeded 2nd Pass "); } else { if (doTracing) Debug(" -- getCallSymbolsForThisExpiry succeded 1st pass "); } var enumerator = callSymbolsForThisExpiry.GetEnumerator(); //callOptionsList.Clear(); while (enumerator.MoveNext()) { optSymbol = enumerator.Current; tempOption = AddOptionContract(optSymbol, Resolution.Minute, true); tempOption.PriceModel = OptionPriceModels.BinomialTian(); /// necessary for Greeks? callOptionsList.Add(tempOption); } // if the first attempt at obtaining calls fails, then expiries[2-5] are incremented by 1 month. If that fails // this subroutine was exited before here. In any case, if calls can be obtained, puts can be as well //putSymbolsForThisExpiry = allUnderlyingOptionsSymbols.Where( o=> DateTime.Compare(o.ID.Date, pastExpiry) > 0 && DateTime.Compare(o.ID.Date, whichExpiry)<=0 && putSymbolsForThisExpiry = allUndrOptSymbs.Where( o=> ( DateTime.Compare(o.ID.Date, expiries[2])==0 | DateTime.Compare(o.ID.Date, expiries[3])==0 | DateTime.Compare(o.ID.Date, expiries[4])==0 | DateTime.Compare(o.ID.Date, expiries[5])==0 ) && o.ID.OptionRight == OptionRight.Put && o.ID.StrikePrice >= lowStrike && o.ID.StrikePrice < atmStrike) .OrderByDescending(o => o.ID.StrikePrice); if (haltProcessing) { if (doTracing) Debug("---------------------- Expiries PUTS Pass ----------------------------"); if (doTracing) Debug(" --" + stockPrice.ToString() +", " + expiries[2].ToString("MM/dd/yy") + ", " + expiries[3].ToString("MM/dd/yy") + ", " + expiries[4].ToString("MM/dd/yy") + ", " + expiries[5].ToString("MM/dd/yy")); //if (doTracing) IterateChain(putSymbolsForThisExpiry, "putSymbols"); } if (putSymbolsForThisExpiry == null | putSymbolsForThisExpiry.Count()== 0) { if (doTracing) Debug("-- get putSymbolsForThisExpiry failed 2nd Pass (after call succeeded)"); return SSQRMatrix; // return null SSQRMatrix and pass control back to OnData() } else { if (doTracing) Debug("-- get putSymbolsForTheseExpiries succeeded."); var penumerator = putSymbolsForThisExpiry.GetEnumerator(); //putOptionsList.Clear(); while (penumerator.MoveNext()) { optSymbol = penumerator.Current; tempOption = AddOptionContract(optSymbol, Resolution.Minute, true); tempOption.PriceModel = OptionPriceModels.BinomialTian(); /// necessary for Greeks? putOptionsList.Add(tempOption); } IterateContracts(putOptionsList); IterateContracts(callOptionsList); } // Now iterate through the puts and sub-iterate through the calls to assemble the SSQRMatrix // for pricing, puts are bought at the offer and calls are sold at the bid prices. // Each price should be the midpoint between the open and close. if (putOptionsList == null | callOptionsList == null | putOptionsList.Count() ==0 | callOptionsList.Count() == 0) { Debug("-- get call and put options failed both passes "); return SSQRMatrix; // return empty SSQRMatrix } else { var putEnum = putOptionsList.GetEnumerator(); Debug("HELLooooo"); while (putEnum.MoveNext()) { thisPutOpt = putEnum.Current; thisPutPrem = thisPutOpt.AskPrice; thisPutStrike = thisPutOpt.StrikePrice; thisPutExpiry = thisPutOpt.Expiry; //putChain = History<OptionChain>(thisPutOpt.Symbol, TimeSpan.FromDays(5)).FirstOrDefault(); //putGreeks = putChain.Contracts[thisPutOpt.Symbol]; var underlying = thisPutOpt.Underlying.Symbol; var chains = CurrentSlice.OptionChains; Debug("Here 1"); foreach(var chainSymbol in chains.Keys){ Debug("Here 2"); var chain = chains[chainSymbol]; foreach(var contract in chain){ Debug("Here 3"); if(contract.Symbol == thisPutOpt.Symbol){ Debug("Contract Found in Chain! " + contract.Symbol.Value); putGreeks = contract.Greeks; Debug(putGreeks.Delta); }else{ Debug(contract.Symbol + "!=" + thisPutOpt.Symbol); } } } // var optionChainSymbol = AddOption(underlying).Symbol; // thisPutGreeks = CurrentSlice.OptionChains[] atmCall = allUndrOptSymbs.Where(s => s.ID.OptionRight == OptionRight.Call & DateTime.Compare(s.ID.Date, thisPutExpiry)==0) /// get atmCall for this Put Option Expiration .OrderBy(s => Math.Abs(s.ID.StrikePrice - stockPrice)) .FirstOrDefault(); atmCallOpt = AddOptionContract(atmCall); wcCallsList.Clear(); ntmSymbs = allUndrOptSymbs.Where( o=> ( DateTime.Compare(o.ID.Date, thisPutExpiry)==0) & o.ID.OptionRight == OptionRight.Call & o.ID.StrikePrice >= atmStrike & o.ID.StrikePrice <= (decimal)1.1 * atmStrike).Distinct().ToList(); if (haltProcessing & doTracing) Debug($" -- Put Option {putEnum.Current} "); foreach (var optSymb in ntmSymbs) { atmCallOpt = AddOptionContract(optSymb); wcCallsList.Add(atmCallOpt); if (haltProcessing & doTracing) Debug(" -- -- NTM Wing Call " + atmCallOpt); } wcCallsList.Sort((x,y) => x.StrikePrice.CompareTo(y.StrikePrice)); var wcEnum = wcCallsList.GetEnumerator(); var callEnum = callOptionsList.GetEnumerator(); while (callEnum.MoveNext()) { thisCallOpt = callEnum.Current; thisCallPrem = thisCallOpt.BidPrice; thisCallStrike = thisCallOpt.StrikePrice; thisCallExpiry = thisCallOpt.Expiry; //callChain = History<OptionChain>(thisCallOpt.Symbol, TimeSpan.FromDays(30)).FirstOrDefault(); //callGreeks = callChain.Contracts[thisCallOpt.Symbol]; ///*dividends, daysInTrade, interestCost //if (thisCallStrike > thisPutStrike & DateTime.Compare(thisCallExpiry,thisPutExpiry)>=0 ) // only add put/call combinations where call strike is above put strike and call expiry is equal to or later than put if ((thisCallStrike > thisPutStrike & DateTime.Compare(thisCallExpiry,thisPutExpiry)>=0) | (thisCallStrike >= thisPutStrike & DateTime.Compare(thisCallExpiry,thisPutExpiry)>0 )) // only add put/call combinations where call strike is equal to or above put strike and call expiry is later than put OR (c.strike>=put.strike AND c.Expiry>=p.Expiry) { foreach (var wcCallOpt in wcCallsList) { thisWCCallPrem = wcCallOpt.AskPrice; if (wcCallOpt.StrikePrice > thisCallStrike ) { SSQRColumn thisSSQRColumn = buildSSQRColumn(thisPutOpt, thisCallOpt, wcCallOpt, tradeDate, thisExDivDate, amtDividend, stockPrice, thisFFRate); SSQRMatrix.Add(thisSSQRColumn); } } } // if thisCallStrike == thisPutStrike } // while callEnum } // while putEnum if (doTracing) Debug($" -- AA AA RETURNED {SSQRMatrix.Count()} SSQR MATRICES FOR {thisSymbol}" ); } // !null return SSQRMatrix; } // AssembleSSQRMatrix // ********************** buildSSQRColumn ************************************** // *** This sub routine takes in the variables for the iterated put and call Options Lists // *** as well as the dividends count, dividend amount, and stock price // *** and returns an SSQRColumt to be added to the SSQRMatrix list // *********************************************************************************** public SSQRColumn buildSSQRColumn(Option thisPutOpt, Option thisCallOpt, Option wcCall, DateTime tradeDate, DateTime firstExDate, decimal amtDividend, decimal stockPrice, decimal FFRate) //public SSQRColumn buildSSQRColumn(Option thisPutOpt, Option thisCallOpt, OptionContract pGrks, OptionContract cGrks, DateTime whichExpiry, DateTime tradeDate, DateTime exDate, int dividends, decimal amtDividend, decimal stockPrice, int daysInTrade, decimal intCost) { decimal thisSpread = 1M; decimal wingFactor = .2M; // factor to determine wings contract load decimal wingPremium = 0; // added premium to do the wings int monthsInTrade = 0; int daysInTrade = 0; int dividends = 0; decimal thisPutPrem = thisPutOpt.AskPrice; decimal thisCallPrem = thisCallOpt.BidPrice; decimal wcCallPrem = wcCall.AskPrice; // ATM premium to buy the calls wingPremium = (thisPutPrem + wcCallPrem); // premium (proportionate) for the wings SSQRColumn thisColumn = new SSQRColumn(); // get a new SSQRColumn if (thisPutPrem == 0 | thisCallPrem == 0) return thisColumn; // don't build SSQRColumns with missing premium values decimal thisPutStrike = thisPutOpt.StrikePrice; decimal thisCallStrike = thisCallOpt.StrikePrice; decimal wcCallStrike = wcCall.StrikePrice; DateTime thisPutExpiry = thisPutOpt.Expiry; DateTime thisCallExpiry = thisCallOpt.Expiry; //DateTime atmCallExpiry = atmCall.Expiry; daysInTrade = (thisPutExpiry - tradeDate).Days; decimal intCost = (FFRate + ibkrRateAdj)/workingDays * (decimal) daysInTrade * stockPrice; if (haltProcessing) { Debug(" HALTED IN buildSSQRColumn processing") ; } monthsInTrade = thisPutExpiry.Month - firstExDate.Month; if( thisPutExpiry.Year != firstExDate.Year) { monthsInTrade = monthsInTrade + 12; } if (divFrequency.Equals("monthly", StringComparison.OrdinalIgnoreCase)) { dividends = monthsInTrade + 1; } else { dividends = monthsInTrade/3 + 1; // add 1 for the next dividend and 1 for every 3 months thereafter } thisColumn.putSymbol = thisPutOpt.Symbol; thisColumn.callSymbol = thisCallOpt.Symbol; thisColumn.wCallSymbol = wcCall.Symbol; // atm call for this column (based upon put) thisColumn.putPremium = thisPutPrem; thisColumn.callPremium = thisCallPrem; thisColumn.wCallPremium = wcCall.AskPrice; // thisColumn.putStrike = thisPutStrike; thisColumn.callStrike = thisCallStrike; thisColumn.wCallStrike = wcCall.StrikePrice; thisColumn.exDate = firstExDate; thisColumn.optExpiry = thisPutExpiry; thisColumn.callExpiry = thisCallExpiry; /* if (useDeltas) { thisColumn.putDelta = pGrks.Greeks.Delta; thisColumn.callDelta = cGrks.Greeks.Delta; thisColumn.putGamma = pGrks.Greeks.Gamma; thisColumn.callGamma = cGrks.Greeks.Gamma; thisColumn.putVega = pGrks.Greeks.Vega; thisColumn.callVega = cGrks.Greeks.Vega; thisColumn.putRho = pGrks.Greeks.Rho; thisColumn.callRho = cGrks.Greeks.Rho; thisColumn.putTheta = pGrks.Greeks.Theta; thisColumn.callTheta = cGrks.Greeks.Theta; thisColumn.putImpliedVol = pGrks.ImpliedVolatility; thisColumn.callImpliedVol = cGrks.ImpliedVolatility; } */ thisColumn.divAmt = amtDividend; thisColumn.divCount = dividends; thisColumn.stockPrice = stockPrice; thisColumn.daysInPosition = daysInTrade; thisColumn.interestCost = intCost; thisSpread = thisCallStrike - thisPutStrike; if (!ibkrHairCuts.ContainsKey( (thisCallStrike - thisPutStrike)) ) { //Debug("*^*^*^*^*^*^*^*^*^*^**^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*"); //Debug("Make a haircut entry for " + (thisCallStrike - thisPutStrike).ToString()); //Debug("*^*^*^*^*^*^*^*^*^*^**^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*"); if (thisSpread < 5M) { thisColumn.haircut = .5M; } else thisColumn.haircut = thisSpread; }else { thisColumn.haircut = ibkrHairCuts[thisSpread]; } decimal divDollars = amtDividend * dividends; thisColumn.divDollars = divDollars; decimal stockLossIfCalled = (thisCallStrike>stockPrice) ? 0 : (thisPutPrem>thisCallPrem) ? (thisCallStrike -stockPrice) : 0; // loss=0 if cStrike>stkPrice, otherwise negative ***Loss (negative value) if ITM calls are assigned (0 if #calls<#puts) decimal netOptions = -thisPutPrem + thisCallPrem; /// netOptions equals negative putPrem (expense) plus positive call premium (income) thisColumn.netOptions = netOptions; thisColumn.netIncome = divDollars + netOptions - intCost; // Net Income in SSQR.xls subtracts interest cost but does not allow for appreciation to OTM call strike /// obviated in Wing System which has an upside long call wingFactor = (netOptions + divDollars - intCost) / wingPremium; if (wingFactor < 0) wingFactor = 0; if (wingFactor > 0.2M ) wingFactor = 0.2M; thisColumn.wingFactor = wingFactor; thisColumn.ROC = (divDollars + netOptions + stockLossIfCalled - intCost) / thisColumn.haircut; // store ROC for statistical analysis // 2021-03-21 -- (factored in netOptions into downsideRisk calculation) //decimal downsideRisk = thisPutStrike - stockPrice + divDollars + netOptions - intCost; // downside risk is defined as the potential loss due to stock price depreciation _ decimal downsideRisk = ((stockPrice - netOptions) > thisPutStrike) ? stockPrice - netOptions - thisPutStrike + intCost: intCost; thisColumn.downsideRisk = downsideRisk; // subtracts dividends collected and net options premiums and intCost decimal upsidePotential = thisCallStrike - stockPrice + divDollars + netOptions - intCost; // When writing OTM calls, there is a potential thisColumn.upsidePotential = upsidePotential; // upside appreciation from net collar cost to the call strike. thisColumn.ROR = upsidePotential/-downsideRisk; // store ROR /*if (stockPrice == thisPutStrike) { thisColumn.CCOR = (1 - thisPutPrem/thisCallPrem)/0.01M; // get the maximum upside potential for a unit of actual risk } else { thisColumn.CCOR = (1 - thisPutPrem/thisCallPrem)/(stockPrice - thisPutStrike); } */ // 2021-03-21 -- -- changed to ordered by downsideRisk/upsidePotential //thisColumn.CCOR = netOptions/downsideRisk; // get the maximum upside potential for a unit of actual risk thisColumn.CCOR = downsideRisk/upsidePotential; thisColumn.description1 = "Combination in " + thisPutOpt.Symbol.Underlying + " @ " + stockPrice + " is the " + thisPutStrike + "/" + thisCallStrike + " collar "; thisColumn.description2 = "," + thisPutOpt.Symbol.Underlying + "," + String.Format("{0:0.00}", stockPrice) + "," + firstExDate.ToString("MM/dd/yy") + "," + dividends + "," + String.Format("{0:0.00}", amtDividend) + "," + String.Format("{0:0.00}",divDollars) + "," + daysInTrade + ", " + String.Format("{0:0.00}", intCost) + ", " + thisPutExpiry.ToString("MM/dd/yy") + ", " + thisCallExpiry.ToString("MM/dd/yy") + ", " + String.Format("{0:0.00}",thisPutStrike) + ", " + String.Format("{0:0.00}",thisPutPrem) + ", " + String.Format("{0:0.00}",thisCallStrike) + ", " + String.Format("{0:0.00}", thisCallPrem) + ", " + String.Format("{0:0.00}", thisColumn.wCallStrike) + ", " + String.Format("{0:0.00}", thisColumn.wCallPremium) + ", " + String.Format("{0:0.00}",thisColumn.putDelta) + ", " + String.Format("{0:0.00}", thisColumn.callDelta) + ", " + String.Format("{0:0.00}",netOptions) + ", " + String.Format("{0:0.00}", thisColumn.netIncome) + ", " + String.Format("{0:0.00}", thisColumn.haircut) + ", " + String.Format("{0:0.00}",thisColumn.ROC) + "," + String.Format("{0:0.00}", upsidePotential) + "," + String.Format("{0:0.00}", downsideRisk) + "," + String.Format("{0:0.00}",thisColumn.ROR) + "," + String.Format("{0:0.00}", thisColumn.CCOR ) + "," + String.Format("{0:0.00}", thisColumn.wingFactor) + "," + thisColumn.putSymbol + "," + thisColumn.callSymbol; return thisColumn; } // ********************** AddCorrespondingPut ******************************************* // *** This code will add the put option which corresponds to the call shorted // *** for purposes of evaluating it in ex-dividend approachment. // *** Option must be constructed with correct parameters before it can be added // ****************************************************************************************** public Option AddCorrespondingPut(Symbol tradableCall) { int indexOfC = tradableCall.ToString().LastIndexOf("C"); char[] charArrayC = tradableCall.ToString().ToCharArray(); char[] charArrayP = charArrayC; charArrayP[indexOfC] = 'P'; string putString = new string(charArrayP); var putSymbol = QuantConnect.Symbol.CreateOption( tradableCall.Underlying, Market.USA, OptionStyle.American, OptionRight.Put, tradableCall.ID.StrikePrice, tradableCall.ID.Date); Option correspondingPut = AddOptionContract(putSymbol); return correspondingPut; } // ********************** GetCorrespondingPut ******************************************* // *** This code will get the put option which corresponds to the call shorted // *** for purposes of evaluating it in ex-dividend approachment -- // *** ??? return Symbol or string? // ****************************************************************************************** public string GetCorrespondingPut(Symbol tradableCall) { int indexOfC = tradableCall.ToString().LastIndexOf("C"); char[] charArrayC = tradableCall.ToString().ToCharArray(); char[] charArrayP = charArrayC; charArrayP[indexOfC] = 'P'; string putString = new string(charArrayP); return putString; } // ********************** getNextExDate ************************************** // *** Use this to find and return the next ex-dividend date from // *** the list exDividendDates given a Slice.DateTime // *********************************************************************************** public DateTime getNextExDate(string tickStr, DateTime sliceTime) { // // /// /// NOTE: Adjusted this to .Compare(sliceTime, d.exDate <=0) to accommondate BND ex-dates on 1st of month // // /// /// NOTE: This should work because most stocks will be traded before progressing through the month to their ex-div dates if (haltProcessing) { Debug(" HALTED IN getNextExDate"); } DividendRecord nextExDateRec = exDividendDates.Where(d => DateTime.Compare(sliceTime.Date, d.exDate.Date)<=0 && d.ticker == tickStr) .OrderBy(d => d.exDate) .FirstOrDefault(); DateTime nextExDate = nextExDateRec.exDate; stockDividendAmount = nextExDateRec.divAmt; divFrequency = nextExDateRec.frequency; if (DateTime.Compare(nextExDate, fmrNextExDate) > 0) logged = 0; //Console.WriteLine("This slice was on " + sliceTime + " and the last NextExDate was " + fmrNextExDate + "."); if (logged < 1) { fmrNextExDate = nextExDate; //Console.WriteLine("This slice was on " + sliceTime + " and we found the next Ex-Date was " + nextExDate + " "); logged++; } return nextExDate; } public SSQRColumn fillSSQRColumn () { SSQRColumn anotherSSQRColumn = new SSQRColumn(); return anotherSSQRColumn; } // ********************** GetOptionsExpiries ************************************** // *** Use this to find and return the next 4 options expirations expirations dates // *** Function will determine if a date is a holiday and subtract 1 day // *********************************************************************************** public Dictionary<int, DateTime> GetOptionExpiries(DateTime tradeD, DateTime nextExDate, DateTime thisMonthExpiry, bool isPrimary){ // Initialize expiration date variables // DateTime firstExpiry = new DateTime(); DateTime secondExpiry = new DateTime(); DateTime thirdExpiry = new DateTime(); DateTime fourthExpiry = new DateTime(); DateTime fifthExpiry = new DateTime(); // Initialize the dictionary for return // 1 : first expiry // 2 : second expiry... Dictionary<int, DateTime> expiries = new Dictionary<int, DateTime>(); // is the nextExDate before or after the 3rd Friday? Before ? use this month expiration // After ? use next month's expiration. if (isPrimary) { if (DateTime.Compare(nextExDate, thisMonthExpiry) <= 0) { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, 0); if (firstExpiry.Subtract(tradeD).Days <= 10) { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, 1); secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 4); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 7); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 10); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 13); } secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 3); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 6); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 9); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 12); }else { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, 1); secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 4); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 7); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 10); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 13); } } else { if (DateTime.Compare(nextExDate, thisMonthExpiry) <= 0) { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, 0); secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 1); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 2); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 3); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 4); }else { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, 1); secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 2); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 3); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 4); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 5); } } expiries.Add(1, firstExpiry); expiries.Add(2, secondExpiry); expiries.Add(3, thirdExpiry); expiries.Add(4, fourthExpiry); expiries.Add(5, fifthExpiry); return expiries; } // ********************** FindNextOptionsExpiry ************************************** // *** Use this to find and return the next options expirations date x months ahead // *** Check the new date to make sure it isn't a holiday and if it is, subtract 1 day // ******************************************************************************************** public DateTime FindNextOptionsExpiry(DateTime thisExpiry, int addedMonths){ // Given a 3rd friday expiration, it will find the next 3rd friday expiration, addedMonths ahead // figure out how to handle holidays such as Good Friday, April 19, 2019. // **************** should this be amended for non-quarterly dividend frequencies? **************** int year = thisExpiry.Year; int month = thisExpiry.Month; while (addedMonths >= 12) { year = year + 1; addedMonths = addedMonths - 12; } month = month + addedMonths; // Adjust if bigger than 12 if(month > 12){ month = month % 12; year = year + 1; } if (haltProcessing) { Debug(" HALTED IN FindNextOptionsExpiry() " + year.ToString() + "-" + month.ToString() ); } DateTime findDate = FindDay(year, month, DayOfWeek.Friday, 3); // Evaluate if found expirations fall upon holidays and if they do, decrement them 1 day while (USHoliday.Dates.Contains(findDate)) findDate = findDate.AddDays(-1); return findDate; } // ********************** FindDay ******************************************************* // *** Generalized function to find and return a DateTime for a given year, month, DayOfWeek // *** and occurrence in the month. // *** // ******************************************************************************************** public DateTime FindDay(int year, int month, DayOfWeek Day, int occurrence) { if (haltProcessing) { Debug(" HALTED IN FindDay() " + year.ToString() + "-" + month.ToString() + "-" + Day.ToString() + ", at " + occurrence.ToString() + " day"); } // Given a valid month, it will find the datetime for the 3rd friday of the month if (occurrence <= 0 || occurrence > 5) throw new Exception("occurrence is invalid"); DateTime firstDayOfMonth = new DateTime(year, month, 1); //Substract first day of the month with the required day of the week var daysneeded = (int)Day - (int)firstDayOfMonth.DayOfWeek; //if it is less than zero we need to get the next week day (add 7 days) if (daysneeded < 0) daysneeded = daysneeded + 7; //DayOfWeek is zero index based; multiply by the occurrence to get the day var resultedDay = (daysneeded + 1) + (7 * (occurrence - 1)); if (resultedDay > (firstDayOfMonth.AddMonths(1) - firstDayOfMonth).Days) throw new Exception(String.Format("No {0} occurrence(s) of {1} in the required month", occurrence, Day.ToString())); if (month == 2) { if (year == 2016 | year == 2020) { if (resultedDay > 29) { resultedDay = resultedDay - 29; month = 3; } } else { if (resultedDay > 28) { resultedDay = resultedDay - 28; month = 3; } } } try { return new DateTime(year, month, resultedDay); } catch { throw new Exception($"Invalid date: {year}/{month}/{resultedDay}"); } } // ********************** IterateChain ******************************************************* // *** Generalized function to iterate through and print members of an IEnumerable // *** This is used for debugging only // ******************************************************************************************** public void IterateChain(IEnumerable<Symbol> thisChain, string chainName) { int k = 1; Symbol optSymbol; var enumerator = thisChain.GetEnumerator(); Debug(" |||||||||||||||||||||||||||||||| NEW OPTION SYMBOL CHAIN |||||||||||||||||||||||||||||||"); Debug("There are " + thisChain.Count() + " options symbols in this " + chainName + ". "); while (enumerator.MoveNext()) { optSymbol = enumerator.Current; //Debug("Iterated " + k + " times"); //Debug(optSymbol.Value); Debug(optSymbol.Value + " " + optSymbol.ID.StrikePrice + " " + optSymbol.ID.Date + " " + optSymbol.ID.OptionRight); k++; } //Debug(" ---------------------------------------------------------------------------------------------"); } // ********************** IterateContracts ******************************************************* // *** Generalized function to iterate through and print members of an IEnumerable of Contracts // *** This is used for debugging only // ******************************************************************************************** public void IterateContracts(List<Option> thisOptionsList) { int k = 1; Option thisOption; var enumerator = thisOptionsList.GetEnumerator(); Debug(" |||||||||||||||||||||||||||||||| NEW OPTION CONTRACTS LIST |||||||||||||||||||||||||||||||"); Debug("There are " + thisOptionsList.Count() + " contracts in this options list."); while (enumerator.MoveNext()) { thisOption = enumerator.Current; //Debug("Iterated " + k + " times"); //Debug("Option Chain: " + thisOption.ToString()); //Debug(thisOption.StrikePrice + " " + thisOption.Expiry + " " + thisOption.Right + " " + thisOption.GetLastData()); Debug(thisOption.StrikePrice + " " + thisOption.Expiry + " " + thisOption.Right + " BID: " + thisOption.BidPrice + " ASK: " + thisOption.AskPrice); k++; } //Debug(" ---------------------------------------------------------------------------------------------"); } // ********************** Iterate Matrix ******************************************************* // *** Generalized function to iterate through and print members of an IEnumerable of Contracts // *** This is used for debugging only // ******************************************************************************************** public void IterateSSQRMatrix(List<SSQRColumn> thisMatrix) { int k = 1; SSQRColumn thisColumn; var matrixEnum = thisMatrix.GetEnumerator(); Debug(" |||||||||||||||||||||||||||||||| NEW OPTION SSQRMatrix |||||||||||||||||||||||||||||||"); Debug("There are " + thisMatrix.Count() + " columns in this SSQRMatrix."); // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Debug(",Ticker,Stock Price,Ex-Date,# Dividends,Dividend,Dollars,Days In,Interest,PExpiry, CExpiry, PutStrike,PutASK,CallStrike,CallBid, atmStrike, atmCallAsk, PutDelta, CallDelta, NetOptions,Net Income,Haircut,ROC,Upside,Downside,ROR,CCOR, wingFactor, PutSymb, CallSymb"); while (matrixEnum.MoveNext()) { thisColumn = matrixEnum.Current; //Debug("Iterated " + k + " times"); Debug(thisColumn.description2); k++; } Debug(" ---------------------------------------------------------------------------------------------"); } // ********************** Iterate Ordered Matrix *********************************************** // *** Generalized function to iterate through and print members of an IEnumerable of Contracts // *** This is used for debugging only tricky part is passing an IOrderedEnumerable into this // **************************************************************************************************** public void IterateOrderedSSQRMatrix(IOrderedEnumerable<SSQRColumn> thisOrdMatrix) { int k = 1; Debug(" |||||||||||||||||||||||||||||||| NEW TRADABLE SSQRMatrix |||||||||||||||||||||||||||||||"); Debug("There are " + thisOrdMatrix.Count() + " columns in this SSQRMatrix."); // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Debug(",Ticker,Stock Price,Ex-Date,# Dividends,Dividend,Dollars,Days In,Interest,PExpiry, CExpiry, PutStrike,PutASK,CallStrike,CallBid, atmStrike, atmCallAsk, PutDelta, CallDelta, NetOptions,Net Income,Haircut,ROC,Upside,Downside,ROR,CCOR, wingFactor, PutSymb, CallSymb"); foreach (SSQRColumn thisColumn in thisOrdMatrix) { //Debug("Iterated " + k + " times"); Debug(thisColumn.description2); //Debug(" "); k++; if (k == 21) break; } } // ********************** Iterate Ordered PutSpread ********************************************** // *** Generalized function to iterate through and print members of an IEnumerable of PutSpreads // *** This is used for debugging only tricky part is passing an IOrderedEnumerable into this // **************************************************************************************************** public void IterateOrderedPutSpreadList(IOrderedEnumerable<PutSpread> thisOrdSpreads) { string logLine = ""; // for writing the logs int k = 1; Debug(" |||||||||||||||||||||||||||||||| NEW TRADABLE PutSpreads List |||||||||||||||||||||||||||||||"); Debug(",¶¶,There are " + thisOrdSpreads.Count() + " PutSpreads in this List."); // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 //Debug("¶¶,Stock Price, Ex-Date, Trade Date, pExpiry, oldPutSymb, newPutSymb, oldBid, newAsk, oldStrike, newStrike, Open Interst, Div Amt, # Dividends, Div Dollars, stock Incr,Interest,DownSide, Upside, Net Income, NetOptions, Haircut, Descr logLine = ",¶¶"; foreach (PutSpread thisSpread in thisOrdSpreads) { if (k==1){ // iterate field names foreach (var fieldN in typeof(PutSpread).GetFields()) { logLine = logLine + "," + fieldN.Name; } Debug(logLine); logLine = ",¶¶"; //k = k + 1; } foreach (var fieldV in typeof(PutSpread).GetFields()) { if (fieldV.GetType() == typeof(decimal)) { logLine = logLine + "," + String.Format("{0:0.00}", fieldV.GetValue(thisSpread)); } else if (fieldV.GetType() == typeof(DateTime)) { logLine = logLine + "," + String.Format("{0:MM/dd/yy H:mm:ss}", fieldV.GetValue(thisSpread)); } else logLine = logLine + "," + fieldV.GetValue(thisSpread); } Debug(logLine); logLine = ",¶¶"; //Debug("Iterated " + k + " times"); //Debug(thisSpread.description1); //Debug(" "); k++; //if (k == 11) break; } } } }
using QuantConnect.Securities.Option; using System; using System.Collections.Generic; using System.Linq.Expressions; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { private bool goodThresh2 = false; /////////////////////////////////////////////////////////////////////////////////// // // ExecuteTrade // //////////////////////////////////////////////////////////////////////////////////// public void ExecuteTrade(Slice data, SSQRColumn bestSSQRColumn) { thisCCOR = bestSSQRColumn.CCOR; decimal maxWingFactor = 0; decimal thisWingFactor = 0; decimal wingPremium = 0; decimal thisNetOptions = bestSSQRColumn.netOptions; if (haltProcessing) { Debug(" HALTED IN ExecuteTheTrade() "); } //goodThresh = (thisCCOR >= CCORThresh); goodThresh = true; if (goodThresh) { //sharesToBuy = Math.Round(stockDollarValue/stockPrice/100, 0) * 100; // set in top of OnData() optionsToTrade = sharesToBuy/100; //callsToTrade = Decimal.Round(optionsToTrade * bestSSQRColumn.putPremium / bestSSQRColumn.callPremium); /// legacy VCCPTS code //Debug(tradableColumn.ToString()); tradablePut = bestSSQRColumn.putSymbol; tradableCall = bestSSQRColumn.callSymbol; tradableWCall = bestSSQRColumn.wCallSymbol; if (Securities[tradableCall].AskPrice + bestSSQRColumn.callStrike < stockPrice) // make sure that no one can buy the option for less than the stock { Debug($" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ EXERCISE PREVENTION FADE FOR {thisSymbol} @@@@@@@@@@@@"); Debug(" @@@@@@@@@@@@@@@@@@@ CALL ASK: " + Securities[tradableCall].AskPrice + " Strike: " + bestSSQRColumn.callStrike + " Stock Price: " + stockPrice +" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); Debug(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); return; } tradeRecCount = tradeRecCount + 1; // increment trade record count collarIndex = collarIndex + 1; doTheTrade = true; var stockTicket = MarketOrder(thisSymbol, sharesToBuy); if (stockTicket.Status == OrderStatus.Filled) { didTheTrade = true; Plot("Stock Chart", "Buys", stockTicket.AverageFillPrice + 5); // make a new TradePerfRec TradePerfRec thisNewCollar = new TradePerfRec(); thisNewCollar.strtngCndtn = "INITIAL COLLAR"; thisNewCollar.isOpen = true; thisNewCollar.isInitializer = true; thisNewCollar.tradeRecCount = tradeRecCount; thisNewCollar.index = collarIndex; thisNewCollar.startDate = data.Time; thisNewCollar.expDate = bestSSQRColumn.optExpiry; thisNewCollar.thetaExpiration = bestSSQRColumn.callExpiry; thisNewCollar.uSymbol = thisSymbol; thisNewCollar.cSymbol = tradableCall; thisNewCollar.pSymbol = tradablePut; thisNewCollar.wcSymbol = tradableWCall; thisNewCollar.uStartPrice = stockTicket.AverageFillPrice; thisNewCollar.pStrike = bestSSQRColumn.putStrike; thisNewCollar.cStrike = bestSSQRColumn.callStrike; thisNewCollar.wcStrike = bestSSQRColumn.wCallStrike; thisNewCollar.uQty = (int)stockTicket.QuantityFilled; thisNewCollar.ROR = thisROR; thisNewCollar.ROC = thisROC; thisNewCollar.CCOR = thisCCOR; thisNewCollar.RORThresh = RORThresh; thisNewCollar.ROCThresh = ROCThresh; thisNewCollar.CCORThresh = CCORThresh; //thisNewCollar.tradeCriteria = switchROC ? "ROC" : "ROR"; thisNewCollar.tradeCriteria = "Wing"; thisNewCollar.stockADX = lastAdx; thisNewCollar.stockADXR = lastAdxr; thisNewCollar.stockOBV = lastObv; //thisNewCollar.stockAD = lastAd; //thisNewCollar.stockADOSC = lastAdOsc; //thisNewCollar.stockSTO = lastSto; thisNewCollar.stockVariance = lastVariance; thisNewCollar.SSQRnetProfit = stockTicket.QuantityFilled * bestSSQRColumn.netIncome; Option logCorrespondingPut = AddCorrespondingPut(tradableCall); // Add the corresponding put here so system tracks its price for Ex-dividend approachment logCorrespondingPut = AddCorrespondingPut(bestSSQRColumn.wCallSymbol); // Add the wing call corresponding put here so system tracks its price for Ex-Dividend approachment doTheTrade = true; if (thisNewCollar.cStrike < thisNewCollar.uStartPrice) { var limitPrice = (Securities[tradableCall].AskPrice - Securities[tradableCall].BidPrice) / 2M; // get the mid point for the limit price var callTicket = LimitOrder(tradableCall, -optionsToTrade, limitPrice); // sell limit order thisNewCollar.cQty = -(int)optionsToTrade; OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = callTicket; oLO.tpr = thisNewCollar; oLO.oRight = OptionRight.Call; oLOs.Add(oLO); //if (closePutTicket.Status == OrderStatus.Submitted) oldTradeRec.pEndPrice = limitPrice; } else { var callTicket = MarketOrder(tradableCall, -optionsToTrade); if (callTicket.Status == OrderStatus.Filled) { thisNewCollar.cStartPrice = callTicket.AverageFillPrice; thisNewCollar.cQty = (int)callTicket.QuantityFilled; } } thisWingFactor = bestSSQRColumn.wingFactor; var putTicket = MarketOrder(tradablePut, (1 + thisWingFactor) * optionsToTrade); if (putTicket.Status == OrderStatus.Filled) { thisNewCollar.pStartPrice = putTicket.AverageFillPrice; thisNewCollar.pQty = (int)putTicket.QuantityFilled; } if (thisWingFactor > 0) { var wCallTicket = MarketOrder(tradableWCall, thisWingFactor * optionsToTrade); if (wCallTicket.Status == OrderStatus.Filled) { thisNewCollar.wcStartPrice = wCallTicket.AverageFillPrice; thisNewCollar.wcQty = (int)wCallTicket.QuantityFilled; } } doTheTrade = true; tradeRecs.Add(thisNewCollar); Debug("-"); } // marketOrder(thisSymbol) == filled } // goodThresh is TRUE return; } /////////////////////////////////////////////////////////////////////////////////// // Close2ndTPR //////////////////////////////////////////////////////////////////////////////////// public void Close2ndTPR (TradePerfRec closeRec, DateTime closeDate, string reason) { decimal limitPrice = 0; if (haltProcessing) { //Debug(" HALTED IN Close2ndTPR "); } doTheTrade = true; var stockTicket = MarketOrder(closeRec.uSymbol, -closeRec.uQty); // sell the stock Debug(" C2 ** MARKET ORDER TO SELL " + closeRec.uQty.ToString() + " shares of " + closeRec.uSymbol + " at the market."); if (doTracing) Debug(" C2 ** C2 ** STARTING CLOSE2ndTPR PROCESSING ** C2 ** C2 "); if (doTracing) Debug(" -- "); if (doTracing) { foreach(var kvp in Securities) /// make sure there's no leaking of abandoned stocks or options { var security = kvp.Value; if (security.Invested) { //saveString = "," + security.Symbol + ", " + security.Holdings.Quantity + Environment.NewLine; Debug($" |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } } if (stockTicket.Status == OrderStatus.Filled) { closeRec.isOpen = false; closeRec.uEndPrice = stockTicket.AverageFillPrice; Plot("Stock Chart", "Sells", stockTicket.AverageFillPrice + 1); Plot("Stock Chart", "PTSs", divPlotValue); tradeRecCount = 0; // reset trade record count //Plot("Stock Chart", "Sells", 40); // plot in OnOrder() ?? } doTheTrade = true; Debug(" C2 ** C2 ** C2 ** C2 ** KILLING 2nd TPR ** C2 ** C2 ** C2 ** C2 ** C2 ** "); //Debug(" C2 ** Stock Price: " + stockPrice.ToString() + " Call Bid/Offer: " + closeRec.cSymbol.BidPrice.ToString() + "/" + closeRec.cSymbol.AskPrice.ToString()); if (closeRec.pStrike >= stockPrice) /// ITM Put -- use limit order { limitPrice = closeRec.pStrike - stockPrice + 0.10M; closePutTicket = LimitOrder(closeRec.pSymbol, -closeRec.pQty, limitPrice); // sell the puts OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closePutTicket; oLO.tpr = closeRec; oLO.oRight = OptionRight.Put; oLOs.Add(oLO); Debug(" C2 ** LIMIT ORDER TO SELL " + closeRec.pQty.ToString() + " contracts of " + closeRec.pSymbol + " at " + limitPrice.ToString()); Debug("-"); } else { closePutTicket = MarketOrder(closeRec.pSymbol, -closeRec.pQty); // sell the puts Debug(" C2 ** MARKET ORDER TO SELL " + closeRec.pQty.ToString() + " contracts of " + closeRec.pSymbol + " at the market." ); Debug("-"); } if (closePutTicket.Status == OrderStatus.Filled) { closeRec.pEndPrice = closePutTicket.AverageFillPrice; } closeRec.reasonForClose = reason; closeRec.endDate = closeDate; // set the end date of this collar Debug(" C2 ** C2 ** C2 ** C2 ** CLOSED 2nd TPR ** C2 ** C2 ** C2 ** C2 ** C2 ** "); Debug("-"); } /////////////////////////////////////////////////////////////////////////////////// // KillTheCollar //////////////////////////////////////////////////////////////////////////////////// public void KillTheCollar(TradePerfRec killRec, DateTime killDate, string reason) { decimal limitPrice = 0; decimal currUPrice = Securities[killRec.uSymbol].Price; decimal currPPrice = killRec.pSymbol != null ? Securities[killRec.pSymbol].BidPrice : 0; decimal currCPrice = killRec.cSymbol != null ? Securities[killRec.cSymbol].AskPrice : 0; decimal currWCPrice = killRec.wcSymbol != null ? Securities[killRec.wcSymbol].BidPrice : 0; //decimal sellPnL = 0; //decimal exrcsPnL = 0; if (doTracing) Debug($" KK ** KK ** STARTING KILLTHECOLLAR PROCESSING FOR {thisSymbol} ** KK ** KK "); if (doTracing) Debug(" -- "); if (doTracing) { foreach(var kvp in Securities) /// make sure there's no leaking of abandoned stocks or options { var security = kvp.Value; if (security.Invested) { //saveString = "," + security.Symbol + ", " + security.Holdings.Quantity + Environment.NewLine; Debug($" |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } Debug($" |||| SELL OPTS P&L: " + String.Format("{0:0.00}", currSellPnL)); Debug($" |||| Exrcs PUT P&L: " + String.Format("{0:0.00}", currExrcsPutPnL)); Debug($" |||| Exrcs CALL P&L: " + String.Format("{0:0.00}", currExrcsCallPnL)); } if (haltProcessing) { Debug(" HALTED IN KILLTHECOLLAR "); } doTheTrade = true; // determine if this is an ITM call or ITM put and within 1 day of expiry if (killRec.pSymbol != null && stockPrice <= putStrike && expireDateDeltaP.Days <= 1) { // determine if it's more expensive to sell or exercise ***** remember, killRec.cQty is negative for collars (sold calls) if (currExrcsPutPnL > currSellPnL) { // for an ITM PUT, both costs should be negative if (doTracing) Debug($" KK ** KK ** EXERCISING PUTS AND CALLS THETA IN KILLTHECOLLAR FOR {thisSymbol} ** KK ** KK "); if (killRec.cSymbol != null) { // Exercise the PUTs. Let longer expiry calls ride to attempt theta decay //var shrtCall = (Option)Securities[killRec.cSymbol]; //TimeSpan daysToCallExpiry = shrtCall.Expiry.Subtract(killDate); /*if (daysToCallExpiry.Days > 10 ) { Debug(" OO CALL " + shrtCall + " EXPIRES IN " + daysToCallExpiry.Days + "DAYS. CREATING THETA TPR."); // create a thetaTPR to move the call data and track it. Buy it back when theta decays. TradePerfRec newThTPR = new TradePerfRec(); newThTPR.uSymbol = killRec.uSymbol; newThTPR.index = killRec.index; newThTPR.isOpen = true; newThTPR.isInitializer = true; newThTPR.isSecondary =false; newThTPR.isTheta = true; newThTPR.startDate = killRec.startDate; newThTPR.strtngCndtn = "SPINNING OFF THETA CALLS"; newThTPR.expDate = shrtCall.Expiry; newThTPR.cSymbol = killRec.cSymbol; newThTPR.cStrike = killRec.cStrike; newThTPR.cQty = killRec.cQty; newThTPR.cStartPrice = killRec.cStartPrice; newThTPR.tradeCriteria = killRec.tradeCriteria; tradeRecs.Add(newThTPR); killRec.cSymbol = null; // eliminate the call from the existint TPR killRec.cStartPrice = 0; killRec.cQty = 0; } else { */ if (doTracing) Debug(" KK ** KK ** BUYING BACK SHORT CALLS IN KILLTHECOLLAR ** KK ** KK "); closeCallTicket = MarketOrder(killRec.cSymbol, -killRec.cQty); // buy the calls Debug(" KK ** MARKET ORDER TO BUY " + killRec.cQty.ToString() + " contracts of " + killRec.cSymbol + " at the market."); Debug("-"); if (closeCallTicket.Status == OrderStatus.Filled) { killRec.cEndPrice = closeCallTicket.AverageFillPrice; } } if (doTracing) Debug(" ------- "); if (doTracing) Debug(" KK ** KK ** EXERCISING PUTS IN KILLTHECOLLAR ** KK ** KK "); closePutTicket = ExerciseOption(killRec.pSymbol, killRec.pQty); /// underlying and calls will be closed in onOrder() event killRec.grossPnL = currExrcsPutPnL; // log the PnL used in runtime decision potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return; } if (doTracing) Debug(" KK ** KK ** CLOSE POSITIONS IN KILLTHECOLLAR ** KK ** TT "); goto noExercise; } if (killRec.cSymbol != null && stockPrice >= callStrike && expireDateDeltaC.Days <= 1) { if (doTracing) Debug(" KK ** KK ** EXERCISING CALLS AND SELLING BACK PUTS IN KILLTHECOLLAR ** KK ** TT "); if (currExrcsCallPnL > currSellPnL) { // for an ITM CALL, both costs should be positive if (doTracing) Debug(" KK ** KK ** EXIT KILLTHECOLLAR AND AWAIT CALL EXERCISE** KK ** TT "); killRec.grossPnL = currExrcsCallPnL; // log the PnL used in runtime decision return; // get out of the OnData() and await LEAN-driven call exercise } if (doTracing) Debug(" KK ** KK ** CLOSING POSITIONS IN KILLTHECOLLAR ** KK ** TT "); } noExercise: //if OTM or it's less costly to execute orders, then do so here. var stockTicket = MarketOrder(killRec.uSymbol, -killRec.uQty); // sell the stock Debug(" KK ** MARKET ORDER TO SELL " + killRec.uQty.ToString() + " shares of " + killRec.uSymbol + " at the market."); if (stockTicket.Status == OrderStatus.Filled) { Debug(" KK ** KK ** UPDATING U END PRICE ** KK ** KK"); killRec.isOpen = false; killRec.uEndPrice = stockTicket.AverageFillPrice; Plot("Stock Chart", "Sells", stockTicket.AverageFillPrice + 1); tradeRecCount = 0; // reset trade record count //Plot("Stock Chart", "Sells", 40); // plot in OnOrder() ?? } doTheTrade = true; Debug(" KK ** KK ** KK ** KK ** KILLING COLLAR ** KK ** KK ** KK ** KK ** KK ** "); //Debug(" KK ** Stock Price: " + stockPrice.ToString() + " Call Bid/Offer: " + killRec.cSymbol.BidPrice.ToString() + "/" + killRec.cSymbol.AskPrice.ToString()); if (killRec.cSymbol != null) { // Exercise the PUTs. Buy back any calls if possible var shrtCall = (Option)Securities[killRec.cSymbol]; TimeSpan daysToCallExpiry = shrtCall.Expiry.Subtract(killDate); /*if (daysToCallExpiry.Days > 10 ) { Debug(" OO CALL " + shrtCall + " EXPIRES IN " + daysToCallExpiry.Days + ". CREATING THETA TPR."); // create a thetaTPR to move the call data and track it. Buy it back when theta decays. TradePerfRec newThTPR = new TradePerfRec(); newThTPR.uSymbol = killRec.uSymbol; newThTPR.index = killRec.index; newThTPR.isOpen = true; newThTPR.isInitializer = true; newThTPR.isSecondary = true; newThTPR.isTheta = true; newThTPR.startDate = killRec.startDate; newThTPR.strtngCndtn = "SPINNING OFF THETA CALLS"; newThTPR.expDate = shrtCall.Expiry; newThTPR.cSymbol = killRec.cSymbol; newThTPR.cQty = killRec.cQty; newThTPR.cStartPrice = killRec.cStartPrice; newThTPR.tradeCriteria = killRec.tradeCriteria; tradeRecs.Add(newThTPR); killRec.cSymbol = null; // eliminate the call from the existint TPR killRec.cStartPrice = 0; killRec.cQty = 0; } else */ if (killRec.cStrike <= stockPrice) { /// ITM Call -- use limit order limitPrice = stockPrice - killRec.cStrike + 0.10M; closeCallTicket = LimitOrder(killRec.cSymbol, -killRec.cQty, limitPrice); OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closeCallTicket; oLO.tpr = killRec; oLO.oRight = OptionRight.Call; oLOs.Add(oLO); Debug(" KK ** LIMIT ORDER TO BUY TO CLOSE SHORT CALL " + killRec.cQty.ToString() + " contracts of " + killRec.cSymbol + " at " + limitPrice.ToString()); } else { closeCallTicket = MarketOrder(killRec.cSymbol, -killRec.cQty); // buy the calls Debug(" KK ** MARKET ORDER TO BUY TO CLOSE SHORT CALL" + killRec.cQty.ToString() + " contracts of " + killRec.cSymbol + " at the market."); if (closeCallTicket.Status == OrderStatus.Filled) { killRec.cEndPrice = closeCallTicket.AverageFillPrice; } } } Debug("---------------------------------------"); if (killRec.pStrike >= stockPrice) /// ITM Put -- use limit order { limitPrice = killRec.pStrike - stockPrice + 0.10M; closePutTicket = LimitOrder(killRec.pSymbol, -killRec.pQty, limitPrice); // sell the puts OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closePutTicket; oLO.tpr = killRec; oLO.oRight = OptionRight.Put; oLOs.Add(oLO); Debug(" KK ** LIMIT ORDER TO SELL TO CLOSE " + killRec.pQty.ToString() + " contracts of " + killRec.pSymbol + " at " + limitPrice.ToString()); Debug("-"); } else { closePutTicket = MarketOrder(killRec.pSymbol, -killRec.pQty); // sell the puts Debug(" KK ** MARKET ORDER TO SELL TO CLOSE " + killRec.pQty.ToString() + " contracts of " + killRec.pSymbol + " at the market." ); Debug("-"); } if (closePutTicket.Status == OrderStatus.Filled) { killRec.pEndPrice = closePutTicket.AverageFillPrice; Debug(" KK ** UPDATING PUT PRICE TO " + killRec.pEndPrice + " ** KK ** KK"); } if (killRec.wcSymbol != null && killRec.wcQty != 0 && killRec.wcEndPrice == 0) { if (killRec.wcStrike < stockPrice) /// ITM Put -- use limit order { limitPrice = stockPrice - killRec.wcStrike + 0.10M; Debug(" KK ** LIMIT ORDER TO SELL TO CLOSE WING " + killRec.wcQty.ToString() + " contracts of " + killRec.wcSymbol + " at " + limitPrice.ToString()); closeWCallTicket = LimitOrder(killRec.wcSymbol, -killRec.wcQty, limitPrice); // sell the puts OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closeWCallTicket; oLO.tpr = killRec; oLO.oRight = OptionRight.Call; oLO.isWingCall = true; oLOs.Add(oLO); Debug("-"); } else { closeWCallTicket = MarketOrder(killRec.wcSymbol, -killRec.wcQty); // sell the puts Debug(" KK ** LIMIT ORDER TO SELL TO CLOSE WING " + killRec.wcQty.ToString() + " contracts of " + killRec.wcSymbol + " at " + limitPrice.ToString()); Debug("-"); } if (closeWCallTicket.Status == OrderStatus.Filled) { killRec.wcEndPrice = closePutTicket.AverageFillPrice; Debug(" KK ** UPDATING WING END PRICE TO " + killRec.wcEndPrice + " ** KK ** KK"); } } killRec.reasonForClose = reason; killRec.endDate = killDate; // set the end date of this collar killRec.grossPnL = currSellPnL; // for logging and analysis of runtime conditions Debug("-"); } /////////////////////////////////////////////////////////////////////////////////// // RollPut //////////////////////////////////////////////////////////////////////////////////// public void RollPut(Slice slcData, DateTime nExDvDt, TradePerfRec oldTPR, IEnumerable <Symbol> undrOptsSymbols, decimal sPrice, decimal incrAmt, string reason, bool forceAction){ int rollQty = 0; // change in qty, difference between total stock and covered stock = uncovered stock == amount to roll up. int findYear = slcData.Time.Year; int findMonth = slcData.Time.Month; PutSpread bestPutSpread; OrderTicket closePutTicket; // used to close the open puts OrderTicket openPutTicket; // used to open (roll up) new puts if (haltProcessing) { Debug(" RP ** RP ** RP ** HALTED IN ROLLPUT RR ** RR ** RR **"); } if (doTracing) Debug(" RP ** RP ** STARTING ROLL2NDPUT PROCESSING ** RP ** RP "); if (doTracing) Debug(" -- "); if (doTracing) { foreach(var kvp in Securities) /// make sure there's no leaking of abandoned stocks or options { var security = kvp.Value; if (security.Invested) { //saveString = "," + security.Symbol + ", " + security.Holdings.Quantity + Environment.NewLine; Debug($" |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } Debug($" |||| SELL OPTS P&L: " + String.Format("{0:0.00}", currSellPnL)); Debug($" |||| Exrcs PUT P&L: " + String.Format("{0:0.00}", currExrcsPutPnL)); Debug($" |||| Exrcs CALL P&L: " + String.Format("{0:0.00}", currExrcsCallPnL)); } // Compute the 3rd Friday of this month [options expiration] ---> do not adjust for potential holiday here DateTime thisMonthExpiry = FindDay(findYear, findMonth, DayOfWeek.Friday, 3); // Use the 3rd Friday of the current month to seed the function to return the next 4 ex-dividends expiries adjusted for holidays Dictionary<int, DateTime> expiries = GetOptionExpiries(slcData.Time, nExDvDt, thisMonthExpiry, false); List<PutSpread> pSpreads = AssemblePutSpreads(slcData, expiries, oldTPR, undrOptsSymbols, sPrice, incrAmt); if (pSpreads == null | pSpreads.Count() == 0) { if (doTracing) Debug(" RP RP RP RP FAILED ROLLPUT -- NO PSPREADS"); if (doTracing) Debug(" - "); return; // loop around and try again } bestPutSpread = GetBestPutSpread(pSpreads); if (bestPutSpread == null) { // if (haltProcessing) { /// 2021-02-17 removed this //if (doTracing) Debug(" HALTED IN ROLLPUT --NULL BESTPUTSPREAD -- ITERATE"); if (doTracing) Debug(" RP 2nd TPR PROCESSING CANNOT ROLL " + thisSymbol.ToString() + " CANNOT GET bestPutSpread -- FORCE PUT EXERCISE OOOOOOOOOOO"); // EXERCISE THE PUT removing PUTs and STOCK. Buy back calls in OnOrder() if (doTracing) Debug(" RP FORCE PUT EXERCISE OR CLOSE ON PUT: " + secondLongPutSymbol + " -- OOOOOOOOOOO OOOOOOOO OOOOOOOOOOO"); // EXERCISE THE PUT removing PUTs and STOCK. Buy back calls in OnOrder() if (doTracing){ var orderedPSpreads = pSpreads.Where(s => s.netIncome + s.netOptions > 0 ).OrderByDescending( s => (s.netIncome + s.netOptions)/Math.Abs(s.stockPrice - s.newPutStrike)); //var orderedPSpreads = pSpreads.OrderByDescending(s=>s.netIncome + s.netOptions); IterateOrderedPutSpreadList(orderedPSpreads); } if (oldTPR.isSecondary) { // close secondary tickets only if (oldTPR.pStrike > sPrice & forceAction) { oldTPR.reasonForClose = "FAILED TO OBTAIN PUT ROLL SPREAD"; var putExerciseTicket = ExerciseOption(oldTPR.pSymbol, oldTPR.pQty); } else if (oldTPR.pStrike < sPrice & forceAction) { Close2ndTPR(oldTPR, slcData.Time, " CLOSING 2nd TPR at Expiration with stock @: " + String.Format("{0:C2}", sPrice)); } if (doTracing) Debug(" ************** END 2nd TPR ITM PUT CALC ****************"); if (doTracing) Debug("-"); } Plot("Stock Chart", "PTSs", divPlotValue); return; // loop around and try again } if (doTracing){ var orderedPSpreads = pSpreads.OrderByDescending(s=>s.netIncome + s.netOptions); IterateOrderedPutSpreadList(orderedPSpreads); } /// Roll up the collar if (!oldTPR.isSecondary && oldTPR.pQty > oldTPR.cQty) { rollQty = oldTPR.pQty + oldTPR.cQty; // call Qty is stored as negative value. this is equivalent: abs(p)-abs(c) Debug(" RP ** RP ** RP ** RP ** ROLLING UP PUTS ** RP ** RP ** RP ** RP ** RP ** "); doTheTrade = true; Debug(" RP ** MARKET ORDER TO SELL " + rollQty + " contracts of " + oldTPR.pSymbol + " at market"); closePutTicket = MarketOrder(oldTPR.pSymbol, -rollQty); // sell the puts Debug(" RP ** MARKET ORDER TO BUY " + rollQty + " contracts of " + bestPutSpread.newPutSymb + " at market"); openPutTicket = MarketOrder(bestPutSpread.newPutSymb, rollQty); // buy the higher puts // first adjust the old tradePerfRec to decrement pQty and uQty. It remains open to be processed for the remaining covered, collared stock. oldTPR.pQty = oldTPR.pQty - rollQty; // decrement oldTPR to fully covered collar. Leave it open for future roll processing oldTPR.uQty = oldTPR.uQty - (100 * rollQty); TradePerfRec newTPR1 = new TradePerfRec(); // create a tradePerfRec #1 for the puts sold, solely to log their P/L (including underlying unrealized P/L). TradePerfRec newTPR2 = new TradePerfRec(); // create a TradePerfRec #2 for the new Synthetic Call (stock-covered puts) newTPR1.uSymbol = newTPR2.uSymbol = oldTPR.uSymbol; // newTPR1 for the uncovered synthetic call (put + stock) portion of the original collar newTPR1.index = newTPR2.index = oldTPR.index; // maintain collarIndex throughout the entire sequence of collars and synthCalls newTPR1.uQty = rollQty * 100; // log the starting and ending values and close the TradePerfRec newTPR1.uStartPrice = oldTPR.uStartPrice; newTPR1.uEndPrice = sPrice; newTPR1.pSymbol = oldTPR.pSymbol; newTPR1.pStrike = oldTPR.pStrike; newTPR1.expDate = oldTPR.expDate; newTPR1.pQty = rollQty; newTPR1.pStartPrice = oldTPR.pStartPrice; newTPR1.startDate = oldTPR.startDate; newTPR1.endDate = slcData.Time; newTPR1.isOpen = false; newTPR1.isInitializer = false; newTPR1.isSecondary = true; newTPR1.numDividends = oldTPR.numDividends; newTPR1.divIncome = oldTPR.divIncome; newTPR1.tradeRecCount = oldTPR.tradeRecCount; newTPR1.ROR = oldTPR.ROR; newTPR1.ROC = oldTPR.ROC; newTPR1.CCOR = oldTPR.CCOR; newTPR1.tradeCriteria = oldTPR.tradeCriteria; newTPR1.strtngCndtn = "OLD SYNTH CALL -- MAIN COLLAR SPLIT ON APPRECIATION"; newTPR1.reasonForClose = "P ROLL OLD FRAGMENT SPLIT-SOLD PUTS SPrice @ " + String.Format("{0:0.00}",sPrice) + " up " + String.Format("{0:0.00}", incrAmt) ; if (closePutTicket.Status == OrderStatus.Filled) { newTPR1.pEndPrice = closePutTicket.AverageFillPrice; } newTPR2.uQty = rollQty * 100; // newTPR2 for the new uncovered synthetic call (put + stock) newTPR2.uStartPrice = sPrice; // log the starting values for underlying and puts. newTPR2.pSymbol = bestPutSpread.newPutSymb; newTPR2.pStrike = bestPutSpread.newPutStrike; newTPR2.pQty = rollQty; newTPR2.expDate = bestPutSpread.putExpiry; newTPR2.startDate = slcData.Time; newTPR2.isOpen = true; newTPR2.isInitializer = true; // mark the first synthCall TPR as initializer newTPR2.isSecondary = true; //newTPR2. newTPR2.tradeRecCount = 1; newTPR2.ROR = oldTPR.ROR; newTPR2.ROC = oldTPR.ROC; newTPR2.CCOR = oldTPR.CCOR; newTPR2.tradeCriteria = oldTPR.tradeCriteria; newTPR2.strtngCndtn = "NEW SYNTH CALL -- COLLAR SPLIT ON APPRECIATION"; if (openPutTicket.Status == OrderStatus.Filled) { newTPR2.pStartPrice = openPutTicket.AverageFillPrice; } tradeRecs.Add(newTPR1); tradeRecs.Add(newTPR2); Plot("Stock Chart", "PTSs", divPlotValue); if (doTracing) Debug(" RP ** RP ** END OF ROLL PUT FROM PRIMARY TPR ** RP ** RP ** "); } else if (oldTPR.isSecondary) { // rollQty = oldTPR.pQty; // call Qty is stored as negative value. this is equivalent: abs(p)-abs(c) Debug(" RP ** RP ** RP ** RP ** ROLLING UP PUTS ** RP ** RP ** RP ** RP ** RP ** "); doTheTrade = true; Debug(" RP ** MARKET ORDER TO SELL " + rollQty + " contracts of " + oldTPR.pSymbol + " at market"); closePutTicket = MarketOrder(oldTPR.pSymbol, -rollQty); // sell the puts Debug(" RP ** MARKET ORDER TO BUY " + rollQty + " contracts of " + bestPutSpread.newPutSymb + " at market"); openPutTicket = MarketOrder(bestPutSpread.newPutSymb, rollQty); // buy the higher puts // first adjust the old tradePerfRec to decrement pQty and uQty. It remains open to be processed for the remaining covered, collared stock. //oldTPR.pQty = oldTPR.pQty - rollQty; // whether this is the first secondary TPR, or a subsequent, //oldTPR.uQty = 100M * rollQty; TradePerfRec newTPR1 = new TradePerfRec(); // create a tradePerfRec #1 for the puts sold, solely to log their P/L. //TradePerfRec newTPR2 = new TradePerfRec(); // create a TradePerfRec #2 for the new Synthetic Call (stock-covered puts) foreach (var field in typeof(TradePerfRec).GetFields()) // copy oldTPR to newTPR1 { field.SetValue(newTPR1, field.GetValue(oldTPR)); } oldTPR.uEndPrice = sPrice; if (closePutTicket.Status == OrderStatus.Filled) { oldTPR.pEndPrice = closePutTicket.AverageFillPrice; } oldTPR.endDate = slcData.Time; oldTPR.isOpen = false; oldTPR.grossPnL = closePutTicket.AverageFillPrice - oldTPR.pStartPrice; oldTPR.reasonForClose = "P ROLLUP " + oldTPR.uSymbol + " IS " + String.Format("{0:0.00}",sPrice) + "WHICH STARTED @ " + String.Format("{0:0.00}", oldTPR.uStartPrice); newTPR1.uStartPrice = sPrice; // set the newTPR.uPrice to 0-delta current sPrice if (openPutTicket.Status == OrderStatus.Filled) { newTPR1.pStartPrice = openPutTicket.AverageFillPrice; } newTPR1.pSymbol = bestPutSpread.newPutSymb; // set the pSymbol to the bestPutSpread put symbol newTPR1.pStrike = bestPutSpread.newPutStrike; newTPR1.expDate = bestPutSpread.putExpiry; newTPR1.startDate = slcData.Time; newTPR1.numDividends = 0; newTPR1.divIncome = 0; newTPR1.isInitializer = false; // tied to 1st synthCall TPR newTPR1.isSecondary = true; newTPR1.tradeRecCount = oldTPR.tradeRecCount + 1; // increment this iteration of TPR newTPR1.strtngCndtn = reason; //newTPR1.reasonForClose = ""; //newTPR1.ROR = oldTPR.ROR; //newTPR1.ROC = oldTPR.ROC; //newTPR1.CCOR = oldTPR.CCOR; //newTPR1.tradeCriteria = oldTPR.tradeCriteria; Plot("Stock Chart", "PTSs", divPlotValue); tradeRecs.Add(newTPR1); if (doTracing) Debug(" RP ** RP ** END OF ROLL PUT FROM SECONDARY TPR ** RP ** RP ** "); } } /////////////////////////////////////////////////////////////////////////////////// // // RollTheCollar // //////////////////////////////////////////////////////////////////////////////////// public void RollTheCollar(Slice data, Symbol thisSymbol, string reason) { thisCCOR = bestSSQRColumn.CCOR; decimal thisNetOptions = bestSSQRColumn.netOptions; decimal limitPrice = 0; decimal maxWingFactor = 0; decimal thisWingFactor = 0; decimal wingPremium = 0; OrderTicket closeCallTicket; OrderTicket closePutTicket; OrderTicket closeWCallTicket; if (haltProcessing) { Debug(" HALTED IN ROLL "); } oldRollDate = data.Time.Date; decimal stockPrice = data[thisSymbol].Price; Plot("Stock Chart", "Rolls", stockPrice + 5); // find the trade performance record for this collar and save its index for updating var oldTradeRec = tradeRecs.Where(t => t.isOpen && !t.isSecondary && t.uSymbol.Equals(thisSymbol)).FirstOrDefault(); //int oldTradeRecIndx = tradeRecs.IndexOf(oldTradeRec); Symbol oldShortCallSymb = oldTradeRec.cSymbol; Symbol oldLongPutSymb = oldTradeRec.pSymbol; Symbol oldWCCallSymb = oldTradeRec.wcSymbol; // Cannot execute options spread orders at this time in QuantConnect, so do the collar as // individual legs // 1st sell the long put Debug(" ROLLING ** STARTING ** ROLLING ** STARTING ** ROLLING ** STARTING ** ROLLING ** STARTING ** ROLLING ** STARTING ** "); doTheTrade = true; if (doTracing) Debug(" -- "); if (doTracing) { foreach(var kvp in Securities) /// make sure there's no leaking of abandoned stocks or options { var security = kvp.Value; if (security.Invested) { //saveString = "," + security.Symbol + ", " + security.Holdings.Quantity + Environment.NewLine; Debug($" |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } Debug($" |||| SELL OPTS P&L: " + String.Format("{0:0.00}", currSellPnL)); Debug($" |||| Exrcs PUT P&L: " + String.Format("{0:0.00}", currExrcsPutPnL)); Debug($" |||| Exrcs CALL P&L: " + String.Format("{0:0.00}", currExrcsCallPnL)); } if (oldTradeRec.pStrike >= stockPrice) /// ITM Put -- use limit order to close { limitPrice = oldTradeRec.pStrike - stockPrice + 0.10M; Debug(" ROLLING ** LIMIT ORDER TO SELL " + oldTradeRec.pQty.ToString() + " contracts of " + oldTradeRec.pSymbol + " at " + limitPrice.ToString()); closePutTicket = LimitOrder(oldTradeRec.pSymbol, -oldTradeRec.pQty, limitPrice); // sell the puts OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closePutTicket; oLO.tpr = oldTradeRec; oLO.oRight = OptionRight.Put; oLOs.Add(oLO); //if (closePutTicket.Status == OrderStatus.Submitted) oldTradeRec.pEndPrice = limitPrice; } else { Debug(" ROLLING ** MARKET ORDER TO SELL TO CLOSE " + oldTradeRec.pQty.ToString() + " contracts of " + oldTradeRec.pSymbol + " at market"); closePutTicket = MarketOrder(oldTradeRec.pSymbol, -oldTradeRec.pQty); // sell the puts } if (closePutTicket.Status == OrderStatus.Filled) { oldTradeRec.pEndPrice = closePutTicket.AverageFillPrice; Debug(" ROLLING ** UPDATING PUT " + oldTradeRec.pSymbol + " END PRICE @ " + oldTradeRec.pEndPrice ); } Debug("-"); // 2nd, buy back the long call doTheTrade = true; if (oldTradeRec.cStrike <= stockPrice) /// ITM Call -- use limit order { /// call QTY should be negative from the opening short trade limitPrice = stockPrice - oldTradeRec.cStrike + 0.10M; Debug(" ROLLING ** LIMIT ORDER TO BUY TO CLOSE " + oldTradeRec.cQty.ToString() + " contracts of " + oldTradeRec.cSymbol + " at " + limitPrice.ToString()); closeCallTicket = LimitOrder(oldTradeRec.cSymbol, -oldTradeRec.cQty, limitPrice); //if (closeCallTicket.Status == OrderStatus.Submitted) oldTradeRec.cEndPrice = limitPrice; OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closeCallTicket; oLO.tpr = oldTradeRec; oLO.oRight = OptionRight.Call; oLOs.Add(oLO); } else { Debug(" ROLLING ** MARKET ORDER TO BUY TO CLOSE " + oldTradeRec.cQty.ToString() + " contracts of " + oldTradeRec.cSymbol + " at market"); closeCallTicket = MarketOrder(oldTradeRec.cSymbol, -oldTradeRec.cQty); // buy the calls Debug(" ROLLING ** UPDATING CALL " + oldTradeRec.cSymbol + "END PRICE @ " + oldTradeRec.cEndPrice ); } Debug("-"); if (closeCallTicket.Status == OrderStatus.Filled) { oldTradeRec.cEndPrice = closeCallTicket.AverageFillPrice; Debug(" ROLLING ** UPDATING CALL END PRICE @ " + oldTradeRec.cEndPrice ); } // 3rd, buy back the long call doTheTrade = true; if (oldTradeRec.wcSymbol != null && oldTradeRec.wcQty != 0 && oldTradeRec.wcEndPrice == 0) { if (oldTradeRec.wcStrike <= stockPrice) /// ITM aCall -- use limit order { /// call QTY should be negative from the opening short trade limitPrice = stockPrice - oldTradeRec.wcStrike + 0.10M; Debug(" ROLLING ** LIMIT ORDER TO SELL TO CLOSE WING CALL " + oldTradeRec.wcQty.ToString() + " contracts of " + oldTradeRec.wcSymbol + " at " + limitPrice.ToString()); closeWCallTicket = LimitOrder(oldTradeRec.wcSymbol, -oldTradeRec.wcQty, limitPrice); //if (closeCallTicket.Status == OrderStatus.Submitted) oldTradeRec.cEndPrice = limitPrice; oldTradeRec.wcEndPrice = limitPrice; // set the wc Call End Price here bc finding this record in OnOrder() will be very difficult OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closeWCallTicket; oLO.tpr = oldTradeRec; oLO.oRight = OptionRight.Call; oLO.isWingCall = true; oLOs.Add(oLO); } else { Debug(" ROLLING ** MARKET ORDER TO SELL TO CLOSE WING CALL " + oldTradeRec.wcQty.ToString() + " contracts of " + oldTradeRec.wcSymbol + " at market"); closeWCallTicket = MarketOrder(oldTradeRec.wcSymbol, -oldTradeRec.wcQty); // buy the calls } Debug("-"); if (closeCallTicket.Status == OrderStatus.Filled) { oldTradeRec.wcEndPrice = closeWCallTicket.AverageFillPrice; Debug(" ROLLING ** UPDATING WING CALL " + oldTradeRec.wcSymbol + "END PRICE @ " + oldTradeRec.wcEndPrice ); } } // Keep the stock, but close this trade performance record. Debug(" ROLLING ** ROLLING ** ROLLING ** ROLLING ** ROLLING ** SELL NEW COLLAR ** ROLLING ** ROLLING ** ROLLING ** ROLLING ** ROLLING ** "); oldTradeRec.uEndPrice = stockPrice; oldTradeRec.reasonForClose = reason; oldTradeRec.isOpen = false; oldTradeRec.endDate = data.Time; oldTradeRec.grossPnL = currSellPnL; // rolling essentially sells the existing options. Log the currSellPnL for analysis purposes oldTradeRec.SSQRnetProfit = oldTradeRec.uQty*bestSSQRColumn.netIncome; // log the best SSQRColumn.netIncome for tracking purposes // Put on a new collar and start a new trade performance record // make a new TradePerfRec tradeRecCount = oldTradeRec.tradeRecCount + 1; // increment trade record count TradePerfRec thisNewTPRec = new TradePerfRec(); thisNewTPRec.uSymbol = thisSymbol; // keep the underlying symbol thisNewTPRec.cSymbol = bestSSQRColumn.callSymbol; thisNewTPRec.pSymbol = bestSSQRColumn.putSymbol; thisNewTPRec.wcSymbol = bestSSQRColumn.wCallSymbol; thisNewTPRec.uStartPrice = stockPrice; // log the current slice stock price thisNewTPRec.uQty = oldTradeRec.uQty; // maintain the same quantity thisNewTPRec.isOpen = true; // this new trade performance record is open thisNewTPRec.isInitializer = false; // this is a continuation Collar thisNewTPRec.strtngCndtn = "MAIN COLLAR ROLL / " + reason; thisNewTPRec.index = oldTradeRec.index; // maintain the collarIndex through the entire sequence of collars thisNewTPRec.tradeRecCount = tradeRecCount; // count the trades thisNewTPRec.startDate = data.Time; // set the start date thisNewTPRec.pStrike = bestSSQRColumn.putStrike; thisNewTPRec.cStrike = bestSSQRColumn.callStrike; thisNewTPRec.wcStrike = bestSSQRColumn.wCallStrike; thisNewTPRec.expDate = bestSSQRColumn.optExpiry; // set the options Expiry thisNewTPRec.thetaExpiration = bestSSQRColumn.callExpiry; // set the theta Expiry thisNewTPRec.ROC = bestSSQRColumn.ROC; thisNewTPRec.ROR = bestSSQRColumn.ROR; thisNewTPRec.CCOR = bestSSQRColumn.CCOR; thisNewTPRec.RORThresh = RORThresh; thisNewTPRec.ROCThresh = ROCThresh; thisNewTPRec.CCORThresh = CCORThresh; //thisNewTPRec.tradeCriteria = switchROC ? "ROC" : "ROR"; thisNewTPRec.tradeCriteria = "Wing"; thisNewTPRec.stockADX = lastAdx; thisNewTPRec.stockADXR = lastAdxr; thisNewTPRec.stockOBV = lastObv; //thisNewTPRec.stockAD = lastAd; //thisNewTPRec.stockADOSC = lastAdOsc; //thisNewTPRec.stockSTO = lastSto; thisNewTPRec.stockVariance = lastVariance; //Debug(tradableColumn.ToString()); var tradablePut = bestSSQRColumn.putSymbol; // retrieve the put to buy var tradableCall = bestSSQRColumn.callSymbol; // retrieve the call to sell var tradableWCall = bestSSQRColumn.wCallSymbol; // retrievce wc call to sell Option logCorrespondingPut = AddCorrespondingPut(tradableCall); // Add the corresponding put here so system tracks its price for Ex-dividend approachment logCorrespondingPut = AddCorrespondingPut(bestSSQRColumn.wCallSymbol); // Add the wing call corresponding put here so system tracks its price for Ex-Dividend approachment // netOptions should be greater than the put premium + wc call premium. Figure out how many wings can be bought. //wingPremium = bestSSQRColumn.wingFactor; thisWingFactor = bestSSQRColumn.wingFactor; doTheTrade = true; //calculate the # of call Options to sell in $-Neutral Variable Call Coverage model: optionsToTrade = oldTradeRec.uQty/100; //callsToTrade = Decimal.Round(optionsToTrade * bestSSQRColumn.putPremium / bestSSQRColumn.callPremium); /// VCCPTS legacy code doTheTrade = true; Debug(" ROLLING ** EXECUTING PUT BUY MARKET ORDER TO OPEN " + ((1 + thisWingFactor) * optionsToTrade) + " contracts of " + tradablePut ); var putTicket = MarketOrder(tradablePut, (1 + thisWingFactor) * optionsToTrade); if (putTicket.Status == OrderStatus.Filled) { thisNewTPRec.pSymbol = tradablePut; thisNewTPRec.pStartPrice = putTicket.AverageFillPrice; thisNewTPRec.pQty = (int)putTicket.QuantityFilled; Debug(" ROLLING ** UPDATING PUT START PRICE TO " + thisNewTPRec.pStartPrice + " FOR " + thisNewTPRec.pQty + " CONTRACTS" ); } doTheTrade = true; Debug(" ROLLING ** EXECUTING CALL SELL MARKET ORDER TO OPEN " + optionsToTrade + " contracts of " + tradableCall ); var callTicket = MarketOrder(tradableCall, -optionsToTrade); //var callTicket = MarketOrder(tradableCall, -callsToTrade); if (callTicket.Status == OrderStatus.Filled) { thisNewTPRec.cSymbol = tradableCall; thisNewTPRec.cStartPrice = callTicket.AverageFillPrice; thisNewTPRec.cQty = (int)callTicket.QuantityFilled; Debug(" ROLLING ** UPDATING SHORT CALL START PRICE TO " + thisNewTPRec.cStartPrice + " FOR " + thisNewTPRec.cQty + " CONTRACTS" ); } doTheTrade = true; if (thisWingFactor > 0) { Debug(" ROLLING ** EXECUTING WING CALL BUY MARKET ORDER TO OPEN " + (thisWingFactor*optionsToTrade) + " contracts of " + tradableWCall ); var wCallTicket = MarketOrder(tradableWCall, thisWingFactor * optionsToTrade); if (wCallTicket.Status == OrderStatus.Filled) { thisNewTPRec.wcSymbol = tradableWCall; thisNewTPRec.wcStartPrice = wCallTicket.AverageFillPrice; thisNewTPRec.wcQty = (int)wCallTicket.QuantityFilled; Debug(" ROLLING ** UPDATING WING CALL START PRICE TO " + thisNewTPRec.wcStartPrice + " FOR " + thisNewTPRec.wcQty + " CONTRACTS" ); } else { Debug(" ROLLING ** WING FACTOR IS 0 -- NO WINGS ADDED"); } } didTheTrade = true; /// Roll is done. save the new trade performance record // IterateTradeRecord(thisNewTPRec); tradeRecs.Add(thisNewTPRec); } /////////////////////////////////////////////////////////////////////////////////// // // GetPotentialCollars // //////////////////////////////////////////////////////////////////////////////////// public List<SSQRColumn> GetPotentialCollars(Slice thisSlice, Symbol thisStock, DateTime nextExDivDate) { if (haltProcessing) { Debug(" @@@@@@ HALTED IN GetPotentialCollars"); } // First get the underlying stock price in this Slice decimal stockPrice = thisSlice[thisStock].Price; // Second get its options symbols var allUnderlyingOptionsSymbols = OptionChainProvider.GetOptionContractList(thisStock, thisSlice.Time); if (allUnderlyingOptionsSymbols.Count() == 0) // missing data at this time { Debug(" DDDDDDDDDDDDDDDDDDDDD Missing Data at " + thisSlice.Time + " no options for " + thisStock); List<SSQRColumn> blankCollarsList = new List<SSQRColumn>(); return blankCollarsList; } int findYear = Time.Year; int findMonth = Time.Month; // Compute the 3rd Friday of this month [options expiration] ---> do not adjust for potential holiday here DateTime thisMonthExpiry = FindDay(findYear, findMonth, DayOfWeek.Friday, 3); // Use the 3rd Friday of the current month to seed the function to return the next 4 ex-dividends expiries adjusted for holidays Dictionary<int, DateTime> expiries = GetOptionExpiries(thisSlice.Time, nextExDivDate, thisMonthExpiry, true); // now assemble the SSQR matrix using the expiries dictionary and the contracts lists List<SSQRColumn> ssqrMatrix = assembleSSQRMatrix(expiries, allUnderlyingOptionsSymbols, stockPrice, stockDividendAmount, thisSlice.Time, nextExDivDate); return ssqrMatrix; } /////////////////////////////////////////////////////////////////////////////////// // // GetBestSSQRFromPotentialCollars // //////////////////////////////////////////////////////////////////////////////////// public SSQRColumn GetBestSSQRFromPotentialCollars(Slice thisSlice, Symbol thisStock, DateTime nextExDivDate, List<SSQRColumn> passedMatrix) { OptionChain ssqrPutChain; // instantiate an OptionChain var for updating SSQRMatrix with slice data OptionChain ssqrCallChain; // Symbol ssqrPutSymbol; // instantiate a Symbol var for updating SSQRMatrix with slice Data Symbol ssqrCallSymbol; // if (haltProcessing) { Debug(" @@@@@@ HALTED IN GetBestSSQRFromPotentialCollars"); } /*foreach (SSQRColumn ssqrC in passedMatrix) /// loop through the SSQRMatris to update the deltas and open interest { ssqrPutSymbol = ssqrC.putSymbol; ssqrCallSymbol = ssqrC.callSymbol; if (thisSlice.OptionChains.TryGetValue(ssqrPutSymbol, out ssqrPutChain)) { Debug(" HEY THE ssqrPutChain count is " + ssqrPutChain + " AT " + thisSlice.Time + " in FROM COLLARS."); } else { Debug(" HEY NO OPTIONS IN THIS SLICE in FROM COLLARS" + thisSlice.Time); } if (thisSlice.OptionChains.TryGetValue(ssqrCallSymbol, out ssqrCallChain)) { // Moved this code to Main.cs *** ssqrCallChain will not be included in the same Slice // where the options contracts are added. } } */ // Get the SSQRColumn with the best reward to risk SSQRColumn bestTradableColumn = new SSQRColumn(); if (passedMatrix.Count == 0) return bestTradableColumn; /// found it's possible to have no SSQRs, if so, pass the empty/null SSQRColumn to calling routine var qualifyingCollars = passedMatrix.Where(s=>s.putPremium!=0 & s.putPremium<=s.callPremium).Count(); if (qualifyingCollars == 0) return bestTradableColumn; // bestTradableColumn = passedMatrix.OrderByDescending(p => p.CCOR).FirstOrDefault(); bestTradableColumn = passedMatrix.OrderBy(bTC => bTC.CCOR).FirstOrDefault(); /// 2021-03-21 -- changed from OrderedByDescending ..... using downsideRisk/upsidePotential return bestTradableColumn; } } }
using QuantConnect.Securities.Option; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public Dictionary<decimal, decimal> InitializeHaircuts() { Dictionary<decimal, decimal> ibkrHC = new Dictionary<decimal, decimal>(); ibkrHC.Add(0M, .75M); ibkrHC.Add(0.5M, .75M); ibkrHC.Add(1M, .75M); ibkrHC.Add(1.5M, .75M); ibkrHC.Add(2M, .75M); ibkrHC.Add(2.5M, .85M); ibkrHC.Add(3M, 1M); ibkrHC.Add(3.5M, 1.15M); ibkrHC.Add(4M, 1.3M); ibkrHC.Add(4.5M, 1.65M); ibkrHC.Add(5M, 2M); ibkrHC.Add(5.5M, 2.2M); ibkrHC.Add(6M, 2.4M); ibkrHC.Add(6.5M, 2.6M); ibkrHC.Add(7M, 2.8M); ibkrHC.Add(7.5M, 3M); ibkrHC.Add(8M, 3.5M); ibkrHC.Add(8.5M, 3.8M); ibkrHC.Add(9M, 4M); ibkrHC.Add(9.5M, 4.3M); ibkrHC.Add(10M, 4.5M); ibkrHC.Add(10.5M, 4.8M); ibkrHC.Add(11M, 5M); ibkrHC.Add(11.5M, 5.3M); ibkrHC.Add(12M, 5.5M); ibkrHC.Add(12.5M, 5.7M); ibkrHC.Add(13M, 6M); ibkrHC.Add(13.5M, 6.2M); ibkrHC.Add(14M, 6.6M); ibkrHC.Add(14.5M, 6.8M); ibkrHC.Add(15M, 7M); ibkrHC.Add(15.5M, 7.2M); ibkrHC.Add(16M, 7.4M); ibkrHC.Add(16.5M, 7.6M); ibkrHC.Add(17M, 7.8M); ibkrHC.Add(17.5M, 8.1M); ibkrHC.Add(18M, 8.2M); ibkrHC.Add(18.5M, 8.4M); ibkrHC.Add(19M, 8.6M); ibkrHC.Add(19.5M, 8.8M); ibkrHC.Add(20M, 9M); ibkrHC.Add(20.5M, 9.2M); ibkrHC.Add(21M, 9.4M); ibkrHC.Add(21.5M, 9.6M); ibkrHC.Add(22M, 9.8M); ibkrHC.Add(22.5M, 10.1M); ibkrHC.Add(23M, 10.4M); ibkrHC.Add(23.5M, 10.7M); ibkrHC.Add(24M, 11M); ibkrHC.Add(24.5M, 11.4M); ibkrHC.Add(25M, 11.8M); ibkrHC.Add(25.5M, 12.3M); ibkrHC.Add(26M, 12.8M); ibkrHC.Add(26.5M, 13.2M); ibkrHC.Add(27M, 13.7M); ibkrHC.Add(27.5M, 14.2M); ibkrHC.Add(28M, 14.7M); ibkrHC.Add(28.5M, 15.2M); ibkrHC.Add(29M, 15.6M); ibkrHC.Add(29.5M, 16.1M); ibkrHC.Add(30M, 16.6M); ibkrHC.Add(30.5M, 17M); ibkrHC.Add(31M, 17.4M); ibkrHC.Add(31.5M, 17.8M); ibkrHC.Add(32M, 13.4M); ibkrHC.Add(32.5M, 18.2M); ibkrHC.Add(33M, 18.6M); ibkrHC.Add(33.5M, 19M); ibkrHC.Add(34M, 19.4M); ibkrHC.Add(34.5M, 19.8M); ibkrHC.Add(35M, 20.2M); return ibkrHC; } } }
using QuantConnect.Securities.Option; using Newtonsoft.Json; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public class optGrksRec { //Debug(thisContract.Symbol.Value + ", " + thisContract.BidPrice + ", " + thisContract.AskPrice + ", " + thisContract.LastPrice + ", " + //thisContract.OpenInterest + ", "+ testVol + ", " + thisContract.TheoreticalPrice + ", " + thisContract.Greeks.Delta + ", " + thisContract.ImpliedVolatility); // "Gamma: " + thisContract.Greeks.Gamma + "Vega: " + thisContract.Greeks.Vega + "Rho: " + thisContract.Greeks.Rho + "Theta: " + thisContract.Greeks.Theta / 365 +4 public string uSymbol; // Underlying Symbol public string BidPrice; // Bid Price public string AskPrice; // Ask Price public string LastPrice; // Last Price public string OpenInterest; // Open Interest public string TheoreticalPrice; // Theoretical Price public string Delta; // Delta public string ImpliedVolatility; // Implied Vol public string Gamma; // Gamma public string Vega; // Vega public string Rho; // Rho public string Theta; // Theta public string ToJson() { string json = JsonConvert.SerializeObject(this, Formatting.Indented); return json; } } public class TradePerfRec { public Symbol uSymbol; // 1 Underlying Symbol public int index; // 2 Index to trace the trade and all offspring P&L public bool isOpen; // 3 Is the trade ongoing (open)? public bool isInitializer = false; // 4 Is this the collar-initializing trade public bool isSecondary = false; // 5 Is this a put roll up public bool isTheta = false; // 6 Is this a solely-call TPR public int tradeRecCount; // 7 counter for trade records -- use in the single-stock use case public DateTime startDate; // 8 starting date for collar public DateTime endDate; // 9 ending date for the collar public string strtngCndtn; // 10 for 2nd TPRs, record the starting conditions public string reasonForClose; // 11 reason why collar was killed (ITM options roll, etc.) public DateTime expDate; // 12 expiration date for collar public DateTime thetaExpiration; // 13 expiration date for the short call public Symbol pSymbol; // 14 Put Symbol public Symbol cSymbol; // 15 Call Symbol public Symbol wcSymbol; // 16 Wing Call Symbol public decimal pStrike; // 17 put strike public decimal cStrike; // 18 call strike public decimal wcStrike; // 19 ATM Call Strike public decimal pDelta; // 20 put Delta public decimal cDelta; // 21 call Delta public decimal wcDelta; // 22 atm Call Delta public int uQty; // 23 number of underlying shares public int pQty; // 24 number of put contracts public int cQty; // 25 number of call contracts public int wcQty; // 26 number of wing call contracts public decimal uStartPrice; // 27 Underlying Price when trade put on public decimal pStartPrice; // 28 Put Price when trade put on public decimal cStartPrice; // 29 Call Price when trade put on public decimal wcStartPrice; // 30 ATM Call Price when trade put on public decimal uEndPrice; // 31 Underlying Price when trade taken off public decimal pEndPrice; // 32 Put Price when trade taken off public decimal cEndPrice; // 33 Call Price when trade taken off public decimal wcEndPrice; // 34 ATM Call Price when trade taken off public int numDividends; // 35 # of dividends collected during the trade public decimal divIncome; // 36 $'s collected in Dividend income during the trade public decimal betaValue; // 37 beta value of underlying when trade put on public decimal RORThresh; // 38 Threshold for ROR public decimal ROCThresh; // 39 Threshold for ROC public decimal CCORThresh; // 40 Threshold for CCOR public string tradeCriteria; // 41 ROR or ROC or CCOR public decimal ROR; // 42 ROR calculation from SSQR Matrix public decimal ROC; // 43 ROC calculation from SSQR Matrix public decimal CCOR; // 44 CCOR calculation from SSQR Matrix public decimal stockADX; // 45 Average Directional Index Value public decimal stockADXR; // 46 Average Directional Index Rating public decimal stockOBV; // 47 On Balance Volume public decimal stockAD; // 48 Accumulation/Distribution public decimal stockADOSC; // 49 Accumulation/Distribution Oscillator public decimal stockSTO; // 50 Stochastic value public decimal stockVariance; // 51 Variance of underlying stock public decimal grossPnL; // 52 runtime calculation of PnL at close; public decimal SSQRnetProfit; // 53 runtime calculation of replacement bestSSQR net Profit // **** put class methods here to use collection of TradePerfRecs as basis to examine positions for expirations and assignments } public class OpenLimitOrder { public OrderTicket oTicket; // order ticket public TradePerfRec tpr; // Trade performance record for transaction public OptionRight oRight; // Option Right (OptionRight.Call or OptionRight.Put) public bool isWingCall = false; // is this oLO a Wing Call } public string ConvertTradePerfRec(List<TradePerfRec> tPR) { string tPRString = ""; string jasonString = ""; jasonString = "{"; tPRString = ",^^^"; foreach (var field in typeof(TradePerfRec).GetFields()) { tPRString = tPRString + ", " + field.Name; } Debug(tPRString); var tPREnum = tPR.GetEnumerator(); /////// NOTE: Have to get the JASON formatted correctly. Need one long string. CHECK THIS while (tPREnum.MoveNext()) { TradePerfRec thisPerfRec = tPREnum.Current; jasonString = "{"; tPRString = ",^^^"; tPRString = tPRString + ", " + thisPerfRec.uSymbol; tPRString = tPRString + ", " + thisPerfRec.index; tPRString = tPRString + ", " + thisPerfRec.isOpen; tPRString = tPRString + ", " + thisPerfRec.isInitializer; tPRString = tPRString + ", " + thisPerfRec.isSecondary; tPRString = tPRString + ", " + thisPerfRec.isTheta; tPRString = tPRString + ", " + thisPerfRec.tradeRecCount; tPRString = tPRString + ", " + String.Format("{0:MM/dd/yy H:mm:ss}", thisPerfRec.startDate); tPRString = tPRString + ", " + String.Format("{0:MM/dd/yy H:mm:ss}", thisPerfRec.endDate); tPRString = tPRString + ", " + thisPerfRec.strtngCndtn; tPRString = tPRString + ", " + thisPerfRec.reasonForClose; tPRString = tPRString + ", " + String.Format("{0:MM/dd/yy H:mm:ss}", thisPerfRec.expDate); tPRString = tPRString + ", " + String.Format("{0:MM/dd/yy H:mm:ss}", thisPerfRec.thetaExpiration); tPRString = tPRString + ", " + thisPerfRec.pSymbol.Value; tPRString = tPRString + ", " + thisPerfRec.cSymbol.Value; tPRString = tPRString + ", " + thisPerfRec.wcSymbol.Value; tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.pStrike); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.cStrike); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.wcStrike); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.pDelta); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.cDelta); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.wcDelta); tPRString = tPRString + ", " + thisPerfRec.uQty; tPRString = tPRString + ", " + thisPerfRec.pQty; tPRString = tPRString + ", " + thisPerfRec.cQty; tPRString = tPRString + ", " + thisPerfRec.wcQty; tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.uStartPrice); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.pStartPrice); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.cStartPrice); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.wcStartPrice); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.uEndPrice); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.pEndPrice); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.cEndPrice); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.wcEndPrice); tPRString = tPRString + ", " + thisPerfRec.numDividends; tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.divIncome); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.betaValue); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.RORThresh); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.ROCThresh); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.CCORThresh); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.tradeCriteria); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.ROR); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.ROC); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.CCOR); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.stockADX); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.stockADXR); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.stockOBV); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.stockAD); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.stockADOSC); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.stockSTO); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.stockVariance); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.grossPnL); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.SSQRnetProfit); /*foreach (var field in typeof(TradePerfRec).GetFields()) { if (field is decimal) { //tPRString = tPRString + "," + String.Format("{0:0.00}", field.GetValue(thisPerfRec)); tPRString = tPRString + "," + String.Format("{0:0.00}", field.GetValue(thisPerfRec)); } else if (field is int) { tPRString = tPRString + "," + String.Format("{0}", field.GetValue(thisPerfRec)); } else if (field is DateTime) { tPRString = tPRString + "," + String.Format("{0:MM/dd/yy H:mm:ss}", field.GetValue(thisPerfRec)); } else if (field is bool) { tPRString = tPRString + ", " + field.GetValue(thisPerfRec); } else { //Console.WriteLine("{0} = {1}", field.Name, field.GetValue(thisPerfRec)); tPRString = tPRString + ", " + field.GetValue(thisPerfRec).ToString(); } jasonString = jasonString + "\"" + field.Name + "\":\"" + field.GetValue(thisPerfRec) + "\""; } ^/ /*foreach (var field in typeof(TradePerfRec).GetFields()) { if (field.GetType() == typeof(decimal)) { //tPRString = tPRString + "," + String.Format("{0:0.00}", field.GetValue(thisPerfRec)); tPRString = tPRString + "," + String.Format("{0:0.00}", field); } else if (field.GetType() == typeof(DateTime)) { tPRString = tPRString + "," + String.Format("{0:MM/dd/yy H:mm:ss}", field.GetValue(thisPerfRec)); } else if (field.GetType() == typeof(Symbol)) { tPRString = tPRString + ", " + field; } else { //Console.WriteLine("{0} = {1}", field.Name, field.GetValue(thisPerfRec)); tPRString = tPRString + ", " + field.GetValue(thisPerfRec); } jasonString = jasonString + "\"" + field.Name + "\":\"" + field.GetValue(thisPerfRec) + "\""; } */ jasonString = jasonString + "}," + Environment.NewLine; Debug(tPRString); } return jasonString; } } }
using QuantConnect.Securities.Option; using System; using System.Collections.Generic; using System.Linq.Expressions; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { //************************************************************************************************* //************** CheckDividendRoll ******************************************************* //************************************************************************************************* public bool CheckDividendRoll (Slice slD, DateTime nxtExDate, TradePerfRec currTPRec, Symbol sCall, Symbol lPut, decimal sPrice, int daysRemaining) //*** First look at potential call assigments for dividend approachment beginning 4 days before ex dividend //*** the stock may be called if the extrinsic value is less than the dividend ***// //*** in this case we have to roll the calls by buying them and selling new calls ***// //*** About differentiating calls shorted for Theta Harvesting, ask John if those are weekly options?? //*** Should we, and if so, how do we differentiate those in the context of call assignment during //*** ex-dividend approachment //*** -------------------------------------------------------------------------------------- // 1st, get the price of the corresponding put. Its premium equals the extrinsic value of the short call. // If the corresponding put premium is less than the dividend, the stock may be assigned. { if (haltProcessing) { Debug(" HALTED IN DIVIDEND APPROACHMENT --"); } string correspondingPutString = GetCorrespondingPut(sCall); Symbol putSymb = Securities[correspondingPutString].Symbol; //decimal cpPrice = (Securities[correspondingPutString].AskPrice + Securities[correspondingPutString].BidPrice) / 2M; decimal cpPrice = Securities[correspondingPutString].AskPrice; // If putPremium (price) is less than the dividend, the calls might be exercised, so figure out if we should roll the options if (cpPrice < stockDividendAmount) { if (doTracing) Debug(" ************** BEGIN APPROACHMENT CALC FOR " + thisSymbol + " priced @" + sPrice ); if (doTracing) Debug(" ************** EX-Date: " + nxtExDate.ToString() ); if (doTracing) Debug(" ************** DIVIDEND " + stockDividendAmount + " Extrinsic Value: " + cpPrice ); if (useDeltas) // if Greeks.Delta will be used to calculate most optimal trade { if(slD.Time.Minute == 0 | slD.Time.Minute == 15 | slD.Time.Minute == 30 | slD.Time.Minute == 45) { potentialCollars = GetPotentialCollars(slD, thisSymbol, nxtExDate); // get a list of potential collars and evaluate deltas in next minute return true; // gert out of this slice and come back the next minute to evaluate } else if ((potentialCollars.Count != 0) && (slD.Time.Minute == 1 | slD.Time.Minute == 16 | slD.Time.Minute == 31 | slD.Time.Minute == 46) ) { bestSSQRColumn = GetBestSSQRFromPotentialCollars(slD, thisSymbol, nxtExDate, potentialCollars); potentialCollars.Clear(); // Clear the PotentialCollars list. NOTE: this will obviate subsequent "next minute" evaluations } else return true; } else { if(slD.Time.Minute != 0 & slD.Time.Minute != 15 & slD.Time.Minute != 30 & slD.Time.Minute != 45) return true; //bestSSQRColumn = GetBestSSQR(data, thisSymbol, nextExDate); // this is the normal route of non-delta execution potentialCollars = GetPotentialCollars(slD, thisSymbol, nxtExDate); // get a list of potential collars if (potentialCollars.Count == 0) { if (doTracing) Debug($" OOOOOOOOOOOO NO potentialCollars IN {thisSymbol} POTENTIAL DIVIDEND ASSIGNMENT"); if (daysRemaining <= 1) // Risk of Dividend Assignment too high at Ex-Dividend Date so if haven't been able to get at RollTheCollar, kill it here { if (doTracing) Debug($" OOOOOOOOOOOO NO bestSSQR FOR {thisSymbol} ON LAST DAY OF DIVIDEND-FORCED EXERCISE -- KILL THE COLLAR OOOOOOOOOO"); KillTheCollar(currTPRec, slD.Time, "NO bestSSQR ON LAST DAY OF DIVIDEND-APPROACHMENT -- KILL" ); } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($"************** END APPROACHMENT PROCESSING FOR {thisSymbol} ******************"); if (doTracing) Debug("----------"); return true; // Don't execute further processing in this slice if rolled due to dividend approachment } bestSSQRColumn = GetBestSSQRFromPotentialCollars(slD, thisSymbol, nxtExDate, potentialCollars); } // Trap the synchronous processing when not evaluating deltas -- if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty()) { if (daysRemaining <= 1) // Risk of Dividend Assignment too high at Ex-Dividend Date so if haven't been able to get at RollTheCollar, kill it here { if (doTracing) Debug($" OOOOOOOOOOOO NO bestSSQR ON LAST DAY OF DIVIDEND-FORCED EXERCISE FOR {thisSymbol} -- KILL THE COLLAR OOOOOOOOOO"); KillTheCollar(currTPRec, slD.Time, "NO bestSSQR ON LAST DAY OF DIVIDEND-APPROACHMENT -- KILL" ); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($"************** END APPROACHMENT PROCESSING FOR {thisSymbol} ******************"); if (doTracing) Debug("-"); return true; // Don't execute further processing in this slice if rolled due to dividend approachment } else { if (doTracing) Debug($"************** END DIV APPROACHMENT PROCESSING FOR {thisSymbol} -- NULL bestSSQR -- TRY AGAIN ******************"); if (doTracing) Debug("----"); return true; // Exit OnData if there's no SSQR Column to process } } /// ----------------------------------------------------------------------------------------------------------------------------------------------- /// useDeltas //// useDeltas //// useDeltas ///////////////// Check for empty bestSSQRColum in useDeltas /// useDeltas /// useDeltas /// useDeltas /// useDeltas /// useDeltas if (useDeltas && bestSSQRColumn.IsEmpty() && (potentialCollars.Count != 0) && (slD.Time.Minute == 1 | slD.Time.Minute == 16 | slD.Time.Minute == 31 | slD.Time.Minute == 46) ) // found that OptionChainProvider.GetOptionContractList(thisStock, thisSlice.Time) { // sometimes returns nothing // THIS IS A useDeltas branch of code if (doTracing) Debug($" ************* DIV APPROACHMENT RETURNED NO bestSSQR Column FOR {thisSymbol} *******"); if (doTracing) Debug($"************** END DIV APPROACHMENT PROCESSING FOR {thisSymbol} ******************"); if (doTracing) Debug(" ----"); return true; } /// useDeltas //// useDeltas //// useDeltas ///////////////// Check for empty bestSSQRColum in useDeltas /// useDeltas /// useDeltas /// useDeltas /// useDeltas /// useDeltas /// ----------------------------------------------------------------------------------------------------------------------------------------------- if (!bestSSQRColumn.IsEmpty() ) { TimeSpan expireDateDeltaSSQR = bestSSQRColumn.optExpiry.Subtract(slD.Time); //goodThresh = (bestSSQRColumn.CCOR >= CCORThresh); goodThresh = true; if (goodThresh) // roll the position forward { newRollDate = slD.Time.Date; // don't do the roll if one has just been done -- // sometimes ex-dividend dates are within 10 days of options expiration and a roll has already been done if (newRollDate.Equals(oldRollDate)) { /////// THIS SHOULD NOT HAPPEN IN v17 AND BEYOND BECAUSE LINQ WAS AMENDED TO PREVENT THESE OPTIONS if (doTracing) Debug($" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ DIVIDEND EXERCISE ROLL ABORT -- DUPLICATION {thisSymbol} @@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ OLD DATE: " + oldRollDate.ToString("MM/dd/yyyy HH:mm:ss") + " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ NEW DATE: " + newRollDate.ToString("MM/dd/yyyy HH:mm:ss") + " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug("-"); if (doTracing) Debug($" ************** END APPROACHMENT PROCESSING FOR {thisSymbol} ******************"); if (doTracing) Debug("------"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return true; } else if ((bestSSQRColumn.callStrike < stockPrice) && expireDateDeltaSSQR.Days <= 10 ) // make sure that the collar won't be assigned because we're in the options danger zone { /////// THIS SHOULD NOT HAPPEN IN v17 AND BEYOND BECAUSE LINQ WAS AMENDED TO PREVENT THESE OPTIONS if (doTracing) Debug($" @@@@@@@@@@@@@@@@@@@@@@@ DIVIDEND EXERCISE ROLL ABORT -- CALL PREVENTION FOR {thisSymbol} @@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ CALL ASK: " + Securities[tradableCall].AskPrice + " Strike: " + bestSSQRColumn.callStrike + " Stock Price: " + stockPrice +" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug("-"); if (doTracing) Debug($" ************** END APPROACHMENT PROCESSING FOR {thisSymbol} ******************"); if (doTracing) Debug("-"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); // DO NOT KILL THE COLLAR HERE. return true; } if (doTracing) Debug($" ************** BEGIN DIV APPROACHMENT ROLL FOR {thisSymbol} ****************"); //iterate potetialCollars here solely when executing a trade if (currSellPnL > 0 | currTPRec.uQty * bestSSQRColumn.netIncome > Math.Abs(currSellPnL)) { // Roll solely if we can sell the current collar profitably //if (currSellPnL > 0 ) { // Roll solely if we can sell the current collar profitably RollTheCollar(slD, thisSymbol, "DIVIDEND APPROACHMENT ROLL"); if (didTheTrade) { oldRollDate = slD.Time.Date; // set the oldRollDate to Date of Roll if (doTracing) Debug(" ************** SUCCESSFUL DIV APPROACHMENT ROLL WITH SSQR: "); var orderedSSQRMatrix = potentialCollars.OrderBy(p => p.CCOR); IterateOrderedSSQRMatrix(orderedSSQRMatrix); //didTheTrade = false; } else if (daysRemaining <= 1) // Risk of Dividend Assignment too high at Ex-Dividend Date so if haven't been able to get at RollTheCollar, kill it here { KillTheCollar(currTPRec, slD.Time, "KILL- FAILED ROLL 1ST TPR IN DIVIDEND-FORCED EXERCISE ON LAST DAY" ); // KillTheCollar may return to try again as well } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return true; } else { Debug(" 00 Some code"); if (daysRemaining <= 1) // Risk of Dividend Assignment too high at Ex-Dividend Date so if haven't been able to get at RollTheCollar, kill it here { KillTheCollar(currTPRec, slD.Time, "KILL- LOSS ON 1ST TPR IN DIVIDEND-FORCED EXERCISE ON LAST DAY" ); // KillTheCollar may return to try again as well } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($"************** END APPROACHMENT PROCESSING FOR {thisSymbol} ******************"); if (doTracing) Debug("-------"); return true; // Don't execute further processing in this slice if rolled due to dividend approachment } if (doTracing) Debug($" ************** END DIV APPROACHMENT ROLL FOR {thisSymbol} ****************"); if (doTracing) Debug("----"); if (doTracing) Debug($"************** END DIV APPROACHMENT PROCESSING FOR {thisSymbol} ******************"); if (doTracing) Debug("-"); return true; // get out of this Slice // prevent immediate call assignment } else { // NOT goodThresh --- kill the collar if (daysRemaining <= 1) // Risk of Dividend Assignment too high at Ex-Dividend Date so if haven't been able to get at RollTheCollar, kill it here { if (doTracing) Debug($" OOOOOOOOOOOO BAD THRESH ON DIVIDEND-FORCED EXERCISE FOR {thisSymbol}-- KILL THE COLLAR ON LAST DAY OOOOOOOOOO"); KillTheCollar(currTPRec, slD.Time, "KILL- LAST DAY BAD THRESH ON DIVIDEND-FORCED EXERCISE" ); // KillTheCollar may return to try again as well } else { if (doTracing) Debug($" OOOOOOOOOOOO BAD THRESH ON DIVIDEND-FORCED EXERCISE FOR {thisSymbol} -- RETURN AND TRY AGAIN"); } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($"************** END APPROACHMENT PROCESSING FOR {thisSymbol} ******************"); if (doTracing) Debug("-----"); return true; // Don't execute further processing in this slice if rolled due to dividend approachment } // not goodThresh return true; } // !bestSSQRColumn /// there was no bestSSQRColumn } // correspondingPutPremium < Dividend /// no danger of dividend-call assignment return false; } // end of CheckDividendRoll () funtion //************************************************************************************************* //************** CheckCallRoll ******************************************************* //************************************************************************************************* public bool CheckCallRoll (Slice slD, DateTime nxtExDate, TradePerfRec currTPRec, Symbol sCall, Symbol lPut, decimal sPrice, int daysRemaining) { // Determine if it should be rolled forward. if (doTracing) Debug($" ************** BEGIN ITM CALL CALC FOR {thisSymbol} ****************"); //bestSSQRColumn = GetBestSSQR(data, thisSymbol, nextExDate); potentialCollars = GetPotentialCollars(slD, thisSymbol, nxtExDate); // get a list of potential collars here to iterate if there's a good threshold if (potentialCollars.Count == 0) { if (daysRemaining <= 1) { // if at the last day of call expiration and haven't yet rolled, kill the collar. if (doTracing) Debug($" ********* END ITM CALL FORCED ASSIGNMENT PROCESSING FOR {thisSymbol} -- NO POTENTIAL COLLARS ON LAST DAY *************"); KillTheCollar(currTPRec, slD.Time, "KILL ITM CALL ASSIGNMENT -- NO POTENTIAL COLLARS ON LAST DAY"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" OOOOOOOOO TT END CHECK IMPLICIT CALL ASSIGNMENT FOR {thisSymbol} OOOOOOOOO"); if (doTracing) Debug("-----"); } Debug($" ************** END ITM CALL CALC PROCESSING FOR {thisSymbol} -- NO POTCOLS ***"); return true; // if no collars then return and loop around again } bestSSQRColumn = GetBestSSQRFromPotentialCollars(slD, thisSymbol, nxtExDate, potentialCollars); if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty() ) { if (daysRemaining <= 1) { // if at the last day of call expiration and haven't yet rolled, kill the collar. if (doTracing) Debug($" ********* END ITM CALL FORCED ASSIGNMENT PROCESSING FOR {thisSymbol} -- bestSSQR null or empty ON LAST DAY"); KillTheCollar(currTPRec, slD.Time, "KILL ITM CALL ASSIGNMENT -- NO POTENTIAL COLLARS ON LAST DAY"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" TT END CHECK IMPLICIT CALL ASSIGNMENT FOR {thisSymbol} OOOOOOOOO"); if (doTracing) Debug("-"); } else { if (doTracing) Debug($" ************** END ITM CALL CALC FOR {thisSymbol} -- null bestSSQRColumn *************"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); } return true; // return and exit OnData() } goodThresh = bestSSQRColumn.CCOR >= CCORThresh; if (goodThresh) // roll the position forward { if (doTracing) Debug($" ************** BEGIN ITM CALL ROLL FOR {thisSymbol} ****************"); newRollDate = slD.Time.Date; if (!newRollDate.Equals(oldRollDate)) { // check to make sure we don't roll into a collar that will be exercised // SHOULD NOT EXECUTE IN v17+ BECAUSE LINQ MODIDFIED TO PREVENT SUCH OPTIONS if (Securities[tradableCall].AskPrice + bestSSQRColumn.callStrike < stockPrice) // make sure that no one can buy the option for less than the stock { if (doTracing) Debug($" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ABORT ITM CALL ROLL TO PREVENT EXERCISE FOR {thisSymbol} @@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ CALL ASK: " + Securities[tradableCall].AskPrice + " Strike: " + bestSSQRColumn.callStrike + " Stock Price: " + stockPrice +" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug("-"); if (daysRemaining <= 1) // Risk of Dividend Assignment too high at Ex-Dividend Date so if haven't been able to get at RollTheCollar, kill it here { KillTheCollar(currTPRec, slD.Time, "ABORT ITM CALL ROLL TO PREVENT EXERCISE ON LAST DAY" ); // KillTheCollar may return to try again as well } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return true; } if (currSellPnL > 0 | currTPRec.uQty * bestSSQRColumn.netIncome > Math.Abs(currSellPnL)) // only roll the collar if the current record may be closed profitably-- otherwise seek exercise in kill //if (currSellPnL > 0 ) { /// Roll the Collar if the bestSSQRColumn won't be subsequently exercised. RollTheCollar(slD, thisSymbol, "ROLL--ITM CALL EXPIRATION APPROACHMENT"); if (didTheTrade) { oldRollDate = slD.Time.Date; // set the oldRollDate to Date of Roll if (doTracing) Debug($" ************** ROLLED ITM CALLS COMPLETED FOR {thisSymbol}*************"); var orderedSSQRMatrix = potentialCollars.OrderBy(p => p.CCOR); IterateOrderedSSQRMatrix(orderedSSQRMatrix); //didTheTrade = false; } else if (daysRemaining <= 1) { if (doTracing) Debug($" ************** UNSUCCESSFUL ROLL FOR {thisSymbol} -- KILL ITM PUT COLLAR ON LAST DAY **************"); KillTheCollar(currTPRec, slD.Time, "KILL- LOSS IN 1st TPR IN ITM CALL ROLL" ); // Goto KillTheCollar and determine whether to close or allow call assignment there potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug("-"); return true; } } else if (daysRemaining <= 1) { Debug($" ************** UNPROFITABLE ROLL FOR {thisSymbol} -- KILL ITM PUT COLLAR ON LAST DAY **************"); KillTheCollar(currTPRec, slD.Time, "KILL- LOSS IN 1st TPR IN ITM CALL ROLL" ); // Goto KillTheCollar and determine whether to close or allow call assignment there potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug("-"); return true; } Debug($" ************** UNPROFITABLE ROLL FOR {thisSymbol} -- LOOP AROUND AND TRY AGAIN **************"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug("-"); return true; } else { /// -- newRollDate == oldRollDate -- >> exit OnData() and loop back again if (doTracing) Debug($" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ABORT ITM CALL EXERCISE -- {thisSymbol} DUPLICATION @@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ OLD DATE: " + oldRollDate.ToString("MM/dd/yyyy HH:mm:ss") + " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ NEW DATE: " + newRollDate.ToString("MM/dd/yyyy HH:mm:ss") + " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug("-"); // goodThresh, but already rolled today. NOTE: still using prior variables in Slice go past goodThresh potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return true; } } else { // If not goodThresh, but expireDateDelta<=1, may get assigned on ITM calls // This programmatic flow allows days expireDateDelta -9 through -2 to be evaluated sequentially // because stock price fluctuations may trigger assignment in that date range. But on the last day // and the call is ITM, assignment will probably happen. If goodThresh above, RollTheCollar was called // Put options should expire without value but sell them and capture whatever value possible // exercise the calls here while puts may be sold for some value, even a penny. <4¢ can be sold for 0 commission // capture the ending prices and close the TradePerformanceRecord by removing the old instance and inserting the updated copy if (doTracing) Debug($" ************** END ITM CALL FORCED ASSIGNMENT PROCESSING FOR {thisSymbol}-- BAD THRESH ****************"); if (daysRemaining <= 1) { if (doTracing) Debug($" ************** KILL COLLAR IN ITM CALL FORCED ASSIGNMENT PROCESSING FOR {thisSymbol} -- BAD THRESH ON LAST DAY"); KillTheCollar(currTPRec, slD.Time, "KILL ITM CALL -- PREVENT ASSIGNMENT"); } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" TT END CHECK IMPLICIT CALL ASSIGNMENT FOR {thisSymbol} OOOOOOOOO"); if (doTracing) Debug($" ------------------------------------------------ "); return true; // exit OnData() and loop around again until last day. May get assigned! //ExerciseOption(shortedCallSymbol, Decimal.ToInt32(currTPRec.cQty)); // LEAN error, cannot exercise short options } return false; // endif ITM calls -10 -> Expiry. Probably do an elseif {ITM puts here to definitively trap all assignments } // end CheckCallRoll function //************************************************************************************************* //************** CheckPutRoll ******************************************************* //************************************************************************************************* public bool CheckPutRoll (Slice slD, DateTime nxtExDate, TradePerfRec currTPRec, Symbol sCall, Symbol lPut, decimal sPrice, int daysRemaining) { // Determine if it should be rolled forward. if (doTracing) Debug($" ************** BEGIN ITM PUT CALC FOR {thisSymbol} ****************"); //bestSSQRColumn = GetBestSSQR(data, thisSymbol, nextExDate); potentialCollars = GetPotentialCollars(slD, thisSymbol, nxtExDate); // get a list of potential collars if (potentialCollars.Count == 0) { if (daysRemaining <= 1) { if (doTracing) Debug($" ********* END ITM PUT FORCED ASSIGNMENT PROCESSING FOR {thisSymbol} -- NO POTENTIAL COLLARS ON LAST DAY *************"); KillTheCollar(currTPRec, slD.Time, "KILL ITM PUT ASSIGNMENT -- NO POTENTIAL COLLARS ON LAST DAY"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" TT END CHECK IMPLICIT PUT ASSIGNMENT FOR {thisSymbol} OOOOOOOOO"); if (doTracing) Debug("-----"); } Debug($" ************** END ITM PUT CALC FOR {thisSymbol} -- NO POTCOLS ***"); return true; // if no collars then return and loop around again } bestSSQRColumn = GetBestSSQRFromPotentialCollars(slD, thisSymbol, nxtExDate, potentialCollars); if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty() ) { if (daysRemaining <= 1) { // if at the last day of put expiration and haven't yet rolled, kill the collar. if (doTracing) Debug($" ********* KILL 1st TPR ON LAST DAY OF ITM PUT PROCESSING FOR {thisSymbol} *************"); KillTheCollar(currTPRec, slD.Time, "KILL ITM PUT ASSIGNMENT -- NO POTENTIAL COLLARS ON LAST DAY"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" TT END CHECK IMPLICIT PUT ASSIGNMENT FOR {thisSymbol} OOOOOOOOO"); if (doTracing) Debug("--------"); } else { if (doTracing) Debug($" ********* END ITM PUT FORCED ASSIGNMENT PROCESSING FOR {thisSymbol} -- bestSSQR null or empty LOOPING TO TRY AGAIN"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return true; // loop around and try again } } tradablePut = bestSSQRColumn.putSymbol; tradableCall = bestSSQRColumn.callSymbol; fTPRPutPrice = Securities[tradablePut].BidPrice; // determine if the loss on the put leg is greater than the intial "real potential loss". If it is, exercise the position /*if ((currTPRec.pStartPrice - fTPRPutPrice) > (currTPRec.uStartPrice + currTPRec.pStartPrice - currTPRec.cStartPrice) ) { if (doTracing) Debug(" TT ITM PUT EXPIRATION -- FORCE PUT ASSIGNMENT CHEAPER OOOOOOOOOOO"); // EXERCISE THE PUT removing PUTs and STOCK. Buy back calls in OnOrder() var closeCallTicket = MarketOrder(shortedCallSymbol, -currTPRec.cQty); if (closeCallTicket.Status == OrderStatus.Filled) { currTPRec.cEndPrice = closeCallTicket.AverageFillPrice; } var putExerciseTicket = ExerciseOption(longPutSymbol, currTPRec.pQty); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug(" ************** END ITM PUT CALC -- EXERCISED PUTS ******"); return true; } */ goodThresh = bestSSQRColumn.CCOR >= CCORThresh; if (goodThresh) // roll the position forward { // check bestSSQRColumn to make sure we don't roll into a collar that will be subsequently exercised // this was fixed in v17+ by adding condition to .where() of LINQ to prevent such options from being returned if (Securities[tradableCall].AskPrice + bestSSQRColumn.callStrike < stockPrice) // make sure that no one can buy the option for less than the stock { if (doTracing) Debug($" @@@@@@@@@@@@@@@@@@@ ITM PUT ROLL ABORT FOR {thisSymbol} -- IMMEDIATE CALL-EXERCISE PREVENTION FADE @@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ CALL ASK: " + Securities[tradableCall].AskPrice + " Strike: " + bestSSQRColumn.callStrike + " Stock Price: " + stockPrice +" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug("------"); if (daysRemaining <= 1) { KillTheCollar(currTPRec, slD.Time, "ABORT ITM PUT ROLL TO PREVENT SUBSEQUENT CALL ASSIGNMENT"); } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return true; } if (doTracing) Debug($" ************** BEGIN ITM PUT ROLL FOR {thisSymbol} ****************"); newRollDate = slD.Time.Date; if (!newRollDate.Equals(oldRollDate)) { if (currSellPnL > 0 | currTPRec.uQty * bestSSQRColumn.netIncome > Math.Abs(currSellPnL)) { // Roll solely if we can sell the current collar profitably //if (currSellPnL > 0 ) { // Roll solely if we can sell the current collar profitably RollTheCollar(slD, thisSymbol, "ROLL -- ITM PUT NEAR EXPIRATION"); if (didTheTrade) { oldRollDate = slD.Time.Date; // set the oldRollDate to Date of Roll if (doTracing) Debug($" ************** ROLLED ITM PUTS COMPLETED FOR {thisSymbol} ****************"); var orderedSSQRMatrix = potentialCollars.OrderBy(p => p.CCOR); // 2021-03-21 -- changed from OrderedByDescending IterateOrderedSSQRMatrix(orderedSSQRMatrix); didTheTrade = false; } else { if (daysRemaining <= 1) { KillTheCollar(currTPRec, slD.Time, "ITM PUT ROLL FAILED"); } } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); } else { // un profitable roll if (daysRemaining <= 1) { if (doTracing) Debug($" ************** UNPROFITABLE ITM PUT ROLL FOR {thisSymbol} ON LAST DAY -- ATTEMPT KILL"); KillTheCollar(currTPRec, slD.Time, "KILL- LOSS IN 1st TPR IN ITM PUT ROLL" ); } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" TT END CHECK IMPLICIT PUT ASSIGNMENT FOR {thisSymbol} OOOOOOOOO"); if (doTracing) Debug("-----"); } return true; // exit OnData and try again until last day } else { if (doTracing) Debug("************ FADE ITM PUT ROLL DUE PRIOR DIVIDEND ROLL ****************"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ABORT ITM PUT PRE-EXERCISE ROLL FOR {thisSymbol} --DUPLICATION @@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ OLD DATE: " + oldRollDate.ToString("MM/dd/yyyy HH:mm:ss") + " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@ NEW DATE: " + newRollDate.ToString("MM/dd/yyyy HH:mm:ss") + " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (doTracing) Debug($" ************** END ITM PUT ROLL - FADED FOR {thisSymbol} ****************"); if (doTracing) Debug($" ************** END ITM PUT CALC FOR {thisSymbol} ****************"); if (doTracing) Debug("------"); return true; } } else { // bad threshhold on ITM PUT ROLL -- EXERCISE IT if (daysRemaining <= 1) { if (doTracing) Debug($" ************** BAD SSQR THRESHOLD IN ITM PUT ROLL FOR {thisSymbol} ON LAST DAY -- ATTEMPT KILL"); KillTheCollar(currTPRec, slD.Time, "KILL ON LAST DAY OF ITM PUT "); } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END ITM PUT CALC FOR {thisSymbol} ****************"); if (doTracing) Debug("---------"); return true; // roll around and try again } return true; } // end CheckPutRoll //************************************************************************************************* //************** CheckOTMRoll ******************************************************* //************************************************************************************************* public bool CheckOTMRoll(Slice slD, DateTime nxtExDate, TradePerfRec currTPRec, Symbol sCall, Symbol lPut, decimal sPrice, int daysRemainingC, int daysRemainingP) { // risk of options expiration WITHOUT EXERCISE if (doTracing) Debug($" ************** BEGIN OTM OPTIONS CALC FOR {thisSymbol} ****************"); if (haltProcessing) { Debug(" HALTED OTM OPTIONS CALC "); } potentialCollars = GetPotentialCollars(slD, thisSymbol, nxtExDate); // get a list of potential collars if (potentialCollars.Count == 0) { if (daysRemainingC <= 1 | daysRemainingP <= 1) { KillTheCollar(currTPRec, slD.Time, "ABORT OTM ROLL -- NO POT COLLARS FOR " + thisSymbol ); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END OTM OPTIONS KILL FOR {thisSymbol} ****************"); if (doTracing) Debug("-"); return true; } else { potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); Debug($" ************** END OTM OPTIONS CALC -- NO POTCOLS FOR {thisSymbol} -- LOOP AND TRY AGAIN LATER ***"); return true; // if no collars then return and loop around again } } bestSSQRColumn = GetBestSSQRFromPotentialCollars(slD, thisSymbol, nxtExDate, potentialCollars); if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty()) { if (doTracing) Debug($" ************** null bestSSQRColumn in OTM Expiry Approachment FOR {thisSymbol} *************"); if (doTracing) Debug($" ************** END OTM OPTIONS CALC FOR {thisSymbol} ****************"); if (daysRemainingC <= 1 | daysRemainingP <= 1) { KillTheCollar(currTPRec, slD.Time, "END OTM PROCESSING -- NO VIABLE SSQRS FOR " + thisSymbol ); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END OTM OPTIONS KILL FOR {thisSymbol} LOOP AND TRY AGAIN LATER ****************"); if (doTracing) Debug("-----"); return true; } else { potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); Debug($" ************** END OTM OPTIONS CALC FOR {thisSymbol} -- bestSSQRColumn NULL or EMPTY ***"); return true; // exit OnData() and loop around and try again } } // no bestSSQRColumn // IS IT NECESSARY TO SET THESE HERE tradablePut = bestSSQRColumn.putSymbol; tradableCall = bestSSQRColumn.callSymbol; //goodThresh = bestSSQRColumn.CCOR >= CCORThresh; goodThresh = true; if (goodThresh) // roll the position forward { if (doTracing) Debug($" ************** BEGIN OTM OPTIONS ROLL FOR {thisSymbol} ****************"); newRollDate = slD.Time.Date; if (!newRollDate.Equals(oldRollDate)) { if (currSellPnL > 0 | currTPRec.uQty * bestSSQRColumn.netIncome > Math.Abs(currSellPnL)){ // only roll the collar if the current record may be closed profitably-- otherwise seek exercise in kill //if (currSellPnL > 0) { RollTheCollar(slD, thisSymbol, "OTM OPTIONS EXPIRATION ROLL"); if (didTheTrade) { oldRollDate = slD.Time.Date; // set the oldRollDate to Date of Roll if (doTracing) Debug($" ************** ROLLED OTM OPTIONS FOR {thisSymbol} COMPLETED WITH SSQR: ****************"); if (doTracing) Debug("-"); var orderedSSQRMatrix = potentialCollars.OrderBy(p => p.CCOR); IterateOrderedSSQRMatrix(orderedSSQRMatrix); //didTheTrade = false; potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END SUCCESSFUL OTM OPTIONS ROLL FOR {thisSymbol} ****************"); if (doTracing) Debug("-"); return true; } else { if (daysRemainingC <= 1 | daysRemainingP <= 1) { if (doTracing) Debug($" ************** KILLING OTM OPTIONS COLLAR FOR {thisSymbol} ON LAST DAY - FAILED ROLL ****************"); KillTheCollar(currTPRec, slD.Time, "END OTM PROCESSING -- FAILED ROLL FOR " + thisSymbol ); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END OTM OPTIONS KILL FOR {thisSymbol} ON LAST DAY - FAILED ROLL ****************"); if (doTracing) Debug("-"); return true; } potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END OTM OPTIONS ROLL FOR {thisSymbol} -- FAILED ROLL ****************"); if (doTracing) Debug("-"); return true; } } else if (daysRemainingC <= 1 | daysRemainingP <= 1) { // CANNOT EXECUTE ROLL PROFITABLY SO KILL THE COLLAR IF ON LAST DAY KillTheCollar(currTPRec, slD.Time, "END OTM PROCESSING -- UNPROFITABLE ROLL FOR " + thisSymbol + " ON THE LAST DAY" ); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END OTM OPTIONS ROLL FOR {thisSymbol} WITH KILL ****************"); if (doTracing) Debug("-"); return true; } } // new roll date != old roll date if (doTracing) Debug($" ************** END OTM OPTIONS ROLL PROCSSING FOR {thisSymbol} ****************"); if (doTracing) Debug("------------------------------------"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); return true; } else if (daysRemainingC <= 1 | daysRemainingP <= 1) { // IF BADTHRESH if (doTracing) Debug($" ************** BEGIN OTM OPTIONS COLLAR KILL FOR {thisSymbol} ****************"); // kill the collar KillTheCollar(currTPRec, slD.Time, "BAD THRESH ON OTM OPTIONS ROLL"); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END OTM OPTIONS ROLL WITH KILL ON BAD THRESH FOR {thisSymbol} ****************"); if (doTracing) Debug("-------"); return true; } // goodThresh on rolling OTM Options potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (doTracing) Debug($" ************** END OTM OPTIONS ROLL PROCESSING FOR {thisSymbol} ****************"); if (doTracing) Debug("-"); return true; } } }