Overall Statistics |
Total Trades 567 Average Win 0.03% Average Loss -0.04% Compounding Annual Return -2.166% Drawdown 2.900% Expectancy -0.289 Net Profit -2.152% Sharpe Ratio -7.355 Sortino Ratio -10.223 Probabilistic Sharpe Ratio 0.130% Loss Rate 63% Win Rate 37% Profit-Loss Ratio 0.94 Alpha -0.065 Beta -0.018 Annual Standard Deviation 0.009 Annual Variance 0 Information Ratio -1.778 Tracking Error 0.111 Treynor Ratio 3.65 Total Fees $9056.15 Estimated Strategy Capacity $950000.00 Lowest Capacity Asset SITM X9PDY4DEHQ3P Portfolio Turnover 0.72% |
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Drawing; using QuantConnect.Util; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Data.Consolidators; using QuantConnect.Orders; using QuantConnect.Securities; using QuantConnect.Securities.Option; using QuantConnect.Interfaces; #endregion namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public class ColumnSD { public SSQRColumn col; public SymbolData sd; public LookupData ld; public ColumnSD(SSQRColumn c, ref SymbolData s, LookupData l) { this.col = c; this.sd = s; this.ld = l; } } /// Calculates number of business days, taking into account: /// - weekends (Saturdays and Sundays) /// - bank holidays in the middle of the week public int BusinessDaysUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays) { firstDay = firstDay.Date; lastDay = lastDay.Date; if (firstDay > lastDay) throw new ArgumentException("Incorrect last day " + lastDay); TimeSpan span = lastDay - firstDay; int businessDays = span.Days + 1; int fullWeekCount = businessDays / 7; // find out if there are weekends during the time exceedng the full weeks if (businessDays > fullWeekCount * 7) { // we are here to find out if there is a 1-day or 2-days weekend // in the time interval remaining after subtracting the complete weeks int firstDayOfWeek = (int)firstDay.DayOfWeek; int lastDayOfWeek = (int)lastDay.DayOfWeek; if (lastDayOfWeek < firstDayOfWeek) lastDayOfWeek += 7; if (firstDayOfWeek <= 6) { if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval businessDays -= 2; else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval businessDays -= 1; } else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval businessDays -= 1; } // subtract the weekends during the full weeks in the interval businessDays -= fullWeekCount + fullWeekCount; // subtract the number of bank holidays during the time interval foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; if (firstDay <= bh && bh <= lastDay) --businessDays; } return businessDays; } public class LookupData { public struct DividendRecord { public DateTime VEDate; public string ticker; public decimal divAmt; public DateTime exDate; public string frequency; // public string MOS; public int VERating; public decimal marketPrice; public decimal momentum; public decimal oneMonthForecast; public decimal oneYearPriceTarget; public int momentumRank; } public struct IEXRecord { public string ticker; public decimal divAmt; public DateTime exDate; public string frequency; } public List<SSQRColumn> SSQRMatrix = new List<SSQRColumn>(); public Symbol uSymbol; // underlying symbol in current processing public decimal stockPrice; public decimal unitRiskAmt; // amount of risk per unit public bool doTracing = false; public bool doDeepTracing = false; public bool haltProcessing = false; public decimal workingDays = 365M; public decimal thisFFRate = 0M; public decimal ibkrRateAdj = .006M; // IBKR adds 60bps to FFR (blended over $3,000,000) public int maxPutOTM = 0; // maximum Put OTM depth public int maxCallOTM = 0; // maximum Put OTM depth public int intTPRIndex = 0; public string VECase = ""; public int intType = 0; public DateTime lastUpdated; public List<DividendRecord> exDividendDates = new List<DividendRecord>(); public List<IEXRecord> IExDates = new List<IEXRecord>(); public Dictionary<DateTime, decimal> fedFundsRates = new Dictionary<DateTime, decimal>(); public Dictionary<decimal, decimal> ibkrHairCuts = new Dictionary<decimal, decimal>(); public Dictionary<int, string> tickers = new Dictionary<int, string>(); public decimal divdndAmt = 0; public string divdnFrequency = ""; public DateTime exDivdnDate; public DateTime dtTst; // used for current date time in methods public int tprCounter = 0; public int daysRemainingDiv; // use vars for checking days before expiration public int daysRemainingC; // use vars for checking days before expiration public int daysRemainingP; public int daysRemaining2P; public int daysRemainingWC; public int intVERating; public decimal decMomentum; public decimal decOneMonthForecast; public decimal decOneYearPriceTarget; public decimal decTotalGain; public int intMomentumRank; public DateTime initialTargetEndDate; public void InitializeData(QCAlgorithm algo) { this.exDividendDates = this.GetDividendDates(algo); if (exDividendDates == null) algo.Log("|||||||||||||||||| MISSING DIV DATES |||||||||||||||"); this.IExDates = this.getIEXDivData(algo); if (IExDates == null) algo.Log("|||||||||||||||||| MISSING IEX DATES |||||||||||||||"); this.fedFundsRates = this.GetFedFundsRates(algo); if (fedFundsRates == null) algo.Log("|||||||||||||||||| MISSING FED FUNDS |||||||||||||||"); this.ibkrHairCuts = this.InitializeHaircuts(algo); //this.tickers = this.GetTickers(algo); //// //// //// REMOVED CODE FROM THIS VERSION OF THE CODE } // ********************** loadVEData ************************************** // *** Use this to find and return the current month's VE Ranking // *** and 1-Yr Price Target from using a Symbol and // *** the list exDividendDates given a Slice.DateTime. // *** Search for the nearest past VE record and retrieve the VE data // *********************************************************************************** public void loadVEData(QCAlgorithm algo) { //algo.Log(" ---- *^*^*^*^*^* loadVEData *^*^*^*^*^* ---- "); DateTime sliceTime = algo.CurrentSlice.Time; //DateTime sliceTime = algo.CurrentSlice.Time; string tickStr = this.uSymbol.Value; var biibDates = this.exDividendDates.Where(d => DateTime.Compare(sliceTime.Date, d.VEDate.Date) >= 0 && d.ticker == tickStr) .OrderByDescending(d => d.VEDate); var DRs = this.exDividendDates.Where(d => DateTime.Compare(sliceTime.Date, d.VEDate.Date) >= 0 && d.ticker == tickStr) .OrderByDescending(d => d.VEDate); DividendRecord nextExDateRec = DRs.FirstOrDefault(); /* foreach(DividendRecord d in DRs.Take(10)){ algo.Log($" -- *** -- *** {d.ticker}: {d.VEDate.ToShortDateString()}."); } foreach (var divRec in biibDates){ algo.Log($" {divRec.ticker} : {divRec.VEDate.ToShortDateString()} : {divRec.oneYearPriceTarget.ToString()}"); } */ /// if (nextExDateRec.ticker == "") { algo.Log(" ------------- MISSING TICKER IN VEData: " + tickStr); return; } //algo.Debug($" --- ---- ---- *^*^*^*^*^* Getting closest VE Entry for {this.uSymbol.Value} on {sliceTime.ToShortDateString()}, next VE-Date is {nextExDateRec.VEDate.ToShortDateString()}"); // this.exDivdnDate = default(DateTime); // this.divdndAmt = 0m; // this.divdnFrequency = nextExDateRec.frequency; this.dtTst = nextExDateRec.VEDate; this.intVERating = nextExDateRec.VERating; this.decMomentum = nextExDateRec.momentum; this.decOneMonthForecast = nextExDateRec.oneMonthForecast; this.decOneYearPriceTarget = nextExDateRec.oneYearPriceTarget; this.intMomentumRank = nextExDateRec.momentumRank; this.initialTargetEndDate = nextExDateRec.VEDate.AddMonths(9); return; } // ********************** clearLD ************************************** // *** Use this to find and return the current month's VE Ranking // *** and 1-Yr Price Target from using a Symbol and // *** the list exDividendDates given a Slice.DateTime. // *** Search for the nearest past VE record and retrieve the VE data // *********************************************************************************** public void clearLD(QCAlgorithm algo) { if (haltProcessing) { //algo.Log(" HALTED IN clearLD"); } this.uSymbol = null; this.intVERating = 0; this.decMomentum = 0; this.decOneMonthForecast = 0; this.decOneYearPriceTarget = 0; this.intMomentumRank = 0; this.initialTargetEndDate = default(DateTime); this.dtTst = default(DateTime); this.exDivdnDate = default(DateTime); /// this.divdndAmt = 0m; this.decTotalGain = 0m; this.intType = 0; /// reset to 0 ... Stock this.daysRemaining2P = 100; this.daysRemainingC = 100; this.daysRemainingDiv = 100; this.daysRemainingP = 100; this.daysRemainingWC = 100; //this.SSQRMatrix.Clear(); //this.divdnFrequency = nextExDateRec.frequency; } //DateTime sliceTime = algo.CurrentSlice.Time; public List<IEXRecord> getIEXDivData(QCAlgorithm algo) { // SevenYearDividends.csv :: //2023-11-19 var csvFile = algo.Download("https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/EZ5Nh1XZwyhIj_jaPTgbvFYBRx1cl8HdtbjksKSNVI09Sg?e=M2AQa7&download=1"); if (csvFile == null) return null; decimal lastDiv = 0; bool parsed; DateTime exDateResult; List<IEXRecord> exRecords = new List<IEXRecord>(); // 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 IEXRecord IEXRec = new IEXRecord(); try { IEXRec.ticker = values[0]; if (values[1] == "Null" | values[1] == "") { IEXRec.divAmt = 0; } else { IEXRec.divAmt = Convert.ToDecimal(values[1]); } parsed = DateTime.TryParse(values[2], out exDateResult); if (!parsed) { IEXRec.exDate = default(DateTime); // continue; } else { IEXRec.exDate = exDateResult; } IEXRec.frequency = values[3]; exRecords.Add(IEXRec); i++; //algo.Log("i: " + i.ToString()); } catch (Exception e) { algo.Debug($" --- ---- ERROR IN PARSING IEX-VE DATA FILE {e.Message}"); } } return exRecords; } // ********************** getNextIExDate ************************************** // *** Use this to find and return the next ex-dividend date from // *** the list exDividendDates given a Slice.DateTime // *********************************************************************************** public void getNxtIExDt(string tickStr, QCAlgorithm algo) { if (haltProcessing) { algo.Log(" HALTED IN getNxtIExDt"); } DateTime sliceTime = algo.CurrentSlice.Time; bool missingIEXRec = this.IExDates.Any(d => d.ticker == tickStr); IEXRecord nextIExDateRec = this.IExDates.Where(d => DateTime.Compare(sliceTime.Date, d.exDate.Date) <= 0 && d.ticker == tickStr) .OrderByDescending(d => d.exDate) .FirstOrDefault(); //algo.Debug($" --- ---- ---- *^*^*^*^*^* Getting Next Ex-Date for {this.uSymbol.Value} on {sliceTime.ToShortDateString()}, next Ex-Date is {nextExDateRec.exDate.ToShortDateString()}"); if (nextIExDateRec.ticker == "") { algo.Log(" ------------- MISSING DIVIDEND TICKER: " + tickStr); return; } this.exDivdnDate = nextIExDateRec.exDate; this.divdndAmt = nextIExDateRec.divAmt; this.divdnFrequency = nextIExDateRec.frequency; // this.intVERating = nextExDateRec.VERating; // this.decMomentum = nextExDateRec.momentum; // this.decOneYearPriceTarget = nextExDateRec.oneYearPriceTarget; // this.intMomentumRank = nextExDateRec.momentumRank; 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 private Dictionary<DateTime, decimal> GetFedFundsRates(QCAlgorithm algo) { //var ffFile = algo.Download("https://www.dropbox.com/s/s25jzi5ng47wv4k/DFF.csv?dl=1"); var ffFile = algo.Download("https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/EfUwqG_Y-etNpNTJCtvhW9QBv20Edx88CTUOUF57gAkPAw?e=cEAOvh&download=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 private List<DividendRecord> GetDividendDates(QCAlgorithm algo) { // 2023-07-02 -- FIRST BACK TEST WITH TRUELY COMPLETE VE DATA -- ROWS MISSING DIVIDENDS HAVE MOST RECENT PREVIOUS DIVIDEND PULLED FORWARD -- MS SQL SERVER // 2023-08-09 -- FIRST BACK TEST WITH TRUELY COMPLETE VE DATA -- ROWS MISSING DIVIDENDS HAVE MOST RECENT PREVIOUS DIVIDEND PULLED FORWARD -- MS SQL SERVER // var csvFile = algo.Download("https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/ERxfG-pnpPJMgqNnxn5dEMkBCU41MvJhspCc9hnBQYeltg?e=rIUaoE&download=1"); // 2023-11-19 :: REAL 2016-2023 LOOKUP DATA with dividends pulled forward to get accurately updated TotalGain values var csvFile = algo.Download("https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/EZ7RJcMfBUtGpum1dLHo37oBXQBr9FHTS7hT69Ml-HYajA?e=tb2n8G&download=1"); //algo.Debug("theis"); if (csvFile == null) return null; decimal lastDiv = 0; bool parsed; decimal VERateResult; DateTime exDateResult; DateTime VEDateResult; 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 DividendRecord divRec = new DividendRecord(); try { /* parsed = DateTime.TryParse(values[0], out exDateResult); if (!parsed) { divRec.exDate = default(DateTime); // continue; } else { divRec.exDate = exDateResult; } */ parsed = DateTime.TryParse(values[0], out VEDateResult); if (!parsed) { continue; } else { divRec.VEDate = VEDateResult; } divRec.ticker = values[1]; if (values[2] == "NULL" | values[2] == "") { divRec.divAmt = 0; } else { divRec.divAmt = Convert.ToDecimal(values[2]); } if (values[3] == "NULL" | values[3] == "") { divRec.marketPrice = 0; } else { divRec.marketPrice = Convert.ToDecimal(values[3]); } if (values[4] == "NULL" | values[4] == "") { divRec.VERating = 0; } else { divRec.VERating = Convert.ToInt32(values[4]); //divRec.VERating = Int32.TryParse(values[5], out VERateResult); } if (values[5] == "NULL" | values[5] == "") { divRec.momentum = 0; } else { parsed = Decimal.TryParse(values[5], out VERateResult); if (!parsed) { divRec.momentum = 0; } else { divRec.momentum = VERateResult; } } if (values[6] == "NULL" | values[6] == "") { divRec.momentumRank = 0; } else { divRec.momentumRank = Convert.ToInt32(values[6]); } /*if (string.IsNullOrEmpty(values[7])) { divRec.oneMonthForecat = 0; } else { divRec.oneMonthForecat = Convert.ToDecimal(values[7]); } */ if (values[8] == "NULL" | values[8] == "") { divRec.oneYearPriceTarget = 0; } else { divRec.oneYearPriceTarget = Convert.ToDecimal(values[8]); } dividendDates.Add(divRec); /* if (i == 3 | i == 2) { algo.Debug("RealDate |Ticker|DIV |Mrkt Prc|VE Rat| MOM |Mom Rnk|1 M |1 Yr |1yrFor|Mrkt Cap|TotalGain}"); algo.Debug( $"{divRec.VEDate.ToShortDateString()} | {divRec.ticker} | {divRec.divAmt.ToString()} | {divRec.marketPrice.ToString()} | {divRec.VERating.ToString()} | {divRec.momentum.ToString()} | {divRec.momentumRank.ToString()} | {divRec.oneMonthForecast.ToString()} | {divRec.oneYearPriceTarget.ToString()}"); } */ i++; //algo.Log("i: " + i.ToString()); } catch (Exception e) { algo.Debug($" --- ---- ERROR IN PARSING IEX-VE DATA FILE {e.Message}"); } } return dividendDates; } private Dictionary<decimal, decimal> InitializeHaircuts(QCAlgorithm algo) { 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; } // end initializeIBKR // ********************** getNextExDate ************************************** // *** Use this to find and return the next ex-dividend date from // *** the list exDividendDates given a Slice.DateTime // *********************************************************************************** public void GetNextExDate(QCAlgorithm algo) { // // /// /// 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) { algo.Log(" HALTED IN getNextExDate"); } DateTime sliceTime = algo.CurrentSlice.Time; string tickStr = this.uSymbol.Value; DividendRecord nextExDateRec = this.exDividendDates.Where(d => DateTime.Compare(sliceTime.Date, d.VEDate.Date) <= 0 && d.ticker == tickStr) .OrderByDescending(d => d.VEDate) .FirstOrDefault(); //algo.Debug($" --- ---- ---- *^*^*^*^*^* Getting Next Ex-Date for {this.uSymbol.Value} on {sliceTime.ToShortDateString()}, next Ex-Date is {nextExDateRec.exDate.ToShortDateString()}"); if (nextExDateRec.ticker == "") { algo.Log(" ------------- MISSING DIVIDEND TICKER: " + tickStr); return; } this.exDivdnDate = nextExDateRec.exDate; this.divdndAmt = nextExDateRec.divAmt; this.divdnFrequency = nextExDateRec.frequency; // this.intVERating = nextExDateRec.VERating; // this.decMomentum = nextExDateRec.momentum; // this.decOneYearPriceTarget = nextExDateRec.oneYearPriceTarget; // this.intMomentumRank = nextExDateRec.momentumRank; return; } public void GetNextExDate(string tickStr, QCAlgorithm algo) { // // /// /// 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) { algo.Log(" HALTED IN getNextExDate"); } DateTime sliceTime = algo.CurrentSlice.Time; DividendRecord nextExDateRec = exDividendDates.Where(d => DateTime.Compare(sliceTime.Date, d.VEDate.Date) >= 0 && d.ticker == tickStr) .OrderByDescending(d => d.VEDate) .FirstOrDefault(); if (nextExDateRec.ticker == "") { algo.Log(" ------------- MISSING DIVIDEND TICKER: " + tickStr); return; } this.exDivdnDate = nextExDateRec.exDate; this.divdndAmt = nextExDateRec.divAmt; this.divdnFrequency = nextExDateRec.frequency; return; } public void GetTotalGain(decimal mltplr, decimal stkPrce) { this.decTotalGain = (this.divdndAmt * mltplr) + this.decOneYearPriceTarget - stkPrce; return; /// formula for shorts } } /// end class lookupData public class SymbolData { public readonly Symbol symbol; public Decimal lastEODPrice; private QCAlgorithm _algo; public SecurityExchangeHours SEH; public MomentumPercent thisMOMP_1; public MomentumPercent thisMOMP_3; public MomentumPercent thisMOMP_6; public Dictionary<String, RollingWindow<IndicatorDataPoint>> IndicatorsWindows = new Dictionary<String, RollingWindow<IndicatorDataPoint>>(); public SimpleMovingAverage smaVolume; // Volume Divergence Indicator Inputs public StandardDeviation stdVolume; // "" public BollingerBands bb; // Bollinger Bands Indicator public ParabolicStopAndReverse psar; // Parabolic Stop and Reverse indicator public TrueStrengthIndex tsi; // True Strength Index public ArnaudLegouxMovingAverage ALMA_fast; // ALMA 9 period public ArnaudLegouxMovingAverage ALMA_slow; // ALMA 18 period public decimal bbSignal = 0; // running bbSignal for SuperBollingerBand public int bbTrigger = 0; // episodic/transient bb trigger for trading public decimal psarSignal = 0; public decimal tsi_trigger = 0; public decimal tsi_weak_trigger = 0; public decimal tsi_strong_trigger = 0; public decimal almas_crossing = 0; public bool smaVolumeWasUpdated = false; public bool stdVolumeWasUpdated = false; public RollingWindow<IndicatorDataPoint> Closes; // Rolling window of closes used for BB_Crossover and Trigger Calculation public int VDI_Divergence = 0; // Volume Divergence Indicator Signals public int VDI_Signal = 0; // record last non-zero Bearish or Bullish Divergence public bool VDI_VolumeSpike = false; // "" public bool VDI_VolumeCollapse = false; // "" public bool VDI_IncreasingVolumeTrend = false; // "" public bool VDI_DecreasingVolumeTrend = false; // "" public decimal VDI_Volume_Bar_0 = 0; // for diagnostics public OnBalanceVolume obv; // OBV Osc Indicator Inputs public bool OBVOsc_Bear = false; // OBV Osc Indicator Signals public bool OBVOsc_Bull = false; // "" public bool OBVOsc_HiddenBear = false; // "" public bool OBVOsc_HiddenBull = false; // "" public bool OBVOsc_PivotLowFound = false; // "" public bool OBVOsc_PivotHighFound = false; // "" public decimal obv_osc = 0; public ExponentialMovingAverage ema_obv_short; // "" public ExponentialMovingAverage ema_obv_long; // "" public PivotPointsHighLow pphl_lbR; // "" public RelativeMovingAverage rma_max_change_close; // "" public RelativeMovingAverage rma_min_change_close; // "" //public RateOfChange roc_close; // "" public Beta thisBeta; public TradeBarConsolidator consolidator = new TradeBarConsolidator(390); private RollingWindow<TradeBar> _window; public decimal decMOMP; public bool isRollable; public bool isTriggered; public bool continueTrade; public bool fadeShort; public bool killShort; public bool targetReversalKill; public bool currentPosition; public Symbol optSymbol; public int intTPRCntr = 0; public int intType = 0; /// 0=stock, 1=ETF, 2=Bond public bool openInterestCheck = false; //public List<Symbol> currentOptions = new List<Symbol>(); public int SSQRFailCnt = 0; public decimal divdndAmt = 0; public decimal decOneYearPriceTarget_Initial = 0; public decimal decOneYearPriceTarget_Current = 0; public decimal decOneMonthForecast = 0; public DateTime initialTargetEndDate; public string VECase = ""; public System.Data.DataTable VDIData; public bool IsTriggered(QCAlgorithm algo) { bool Type1 = (this.bbTrigger == -1 | this.VDI_Signal == -1) & (this.tsi_trigger == -1 & this.almas_crossing == -1); bool Type2 = (this.bbTrigger == -1 & this.VDI_Signal == -1) & (this.tsi_trigger == -1 | this.almas_crossing == -1); bool Type3 = (this.tsi > 0 & this.tsi_trigger == -1 & this.almas_crossing == -1); this.isTriggered = (this.psarSignal == -1 && (Type1 | Type2 | Type3)); //algo.Log($" -- -- -- -- Check Triggered PSAR: {this.psarSignal} TSI: {this.tsi} TSITrigger: {this.tsi_trigger} SBB: {this.bbTrigger} VDI: {this.VDI_Signal} ALMA-X {this.almas_crossing} Triggered: {this.isTriggered}"); return this.isTriggered; } public bool ContinueTrade(QCAlgorithm algo) { this.continueTrade = (this.tsi_trigger == -1 && (this.almas_crossing == -1 | this.psarSignal == -1)); return this.continueTrade; } public bool FadeShort(QCAlgorithm algo, decimal stkPrc) { this.fadeShort = stkPrc < 100 || (this.psarSignal == 1 | (this.bbTrigger == 1 && this.VDI_Signal == 1)); /// 2024-01-14 stkPrice < 120 return this.fadeShort; } public bool TargetReversalKill(QCAlgorithm algo, LookupData thisLD) { decimal stkPrc = algo.Securities[this.symbol].Price; decimal calc = 0; thisLD.uSymbol = this.symbol; thisLD.loadVEData(algo); this.decOneYearPriceTarget_Current = thisLD.decOneYearPriceTarget; if (stkPrc == null || stkPrc == 0) return false; calc = ((this.decOneYearPriceTarget_Current-this.decOneYearPriceTarget_Initial)/algo.Securities[this.symbol].Price); if ( calc >= .1m) this.targetReversalKill = true; else this.targetReversalKill = false; if(thisLD.doDeepTracing) algo.Log($" -- trgtrt -- trgtrt -- trgtrt -- at {algo.Time.ToString("hh:mm:ss.ffffff")} CurrentTrgt: {this.decOneYearPriceTarget_Current.ToString("0.00")} InitialTrgt: {this.decOneYearPriceTarget_Initial.ToString("0.00")} Percent: {calc.ToString("0.00")}"); return this.targetReversalKill; } public bool KillShort(QCAlgorithm algo) { // algo.Log($" --- KS--- KS at {algo.Time.ToString("hh:mm:ss.ffffff")} The Target Reversal Kill is {this.targetReversalKill}"); if (this.targetReversalKill) { this.killShort = true; } else if ((this.bbTrigger == 1 && !this.continueTrade)) { this.killShort = true; } else { this.killShort = false; } return this.killShort; } public RollingWindow<TradeBar> Bars; public SymbolData(Symbol passedSymbol, bool rollable, bool position, Symbol symbOpt, Symbol spy) { symbol = passedSymbol; isRollable = rollable; currentPosition = position; optSymbol = symbOpt; intTPRCntr = 0; thisBeta = new Beta("wee", 20, symbol, spy); this.consolidator = new TradeBarConsolidator(390); // 390 minutes per day this.Bars = new RollingWindow<TradeBar>(100); } public void PostOptionsFilteringCreateIndicators() { this.Bars = new RollingWindow<TradeBar>(100); this.Closes = new RollingWindow<IndicatorDataPoint>(270); //this.thisMOMP_1 = new MomentumPercent(21); //this.thisMOMP_3 = new MomentumPercent(63); //this.thisMOMP_6 = new MomentumPercent(126); //this.smaVolume = new SimpleMovingAverage(20); //this.stdVolume = new StandardDeviation(20); this.VDIData = new System.Data.DataTable(); this.VDIData.Columns.Add("DateTime", typeof(DateTime)); this.VDIData.Columns.Add("LowPrice0", typeof(double)); this.VDIData.Columns.Add("LowPrice1", typeof(double)); this.VDIData.Columns.Add("HighPrice0", typeof(double)); this.VDIData.Columns.Add("HighPrice1", typeof(double)); this.VDIData.Columns.Add("LowVolume0", typeof(double)); this.VDIData.Columns.Add("LowVolume1", typeof(double)); this.VDIData.Columns.Add("HighVolume0", typeof(double)); this.VDIData.Columns.Add("HighVolume1", typeof(double)); this.VDIData.Columns.Add("LowerLowPrice", typeof(String)); this.VDIData.Columns.Add("HigherLowVolume", typeof(String)); this.VDIData.Columns.Add("HigherHighPrice", typeof(String)); this.VDIData.Columns.Add("LowerHighVolume", typeof(String)); this.VDIData.Columns.Add("BearishDivergence", typeof(String)); this.VDIData.Columns.Add("BullishDivergence", typeof(String)); this.IndicatorsWindows.Add("SMA_VOL", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("SMA_STD_VOL", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("OBV_Osc", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("VDI_Divergence", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("VDI_Signals", new RollingWindow<IndicatorDataPoint>(270)); //this.IndicatorsWindows.Add("VDI_Volume_Bar_0", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("BB_Crossover", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("BB_Signals", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("BB_UpperValues", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("BB_LowerValues", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("BB_KillShorts", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("PSAR", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("TSI", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("TSI_Signal", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("TSI_Weak_Trigger", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("TSI_Strong_Trigger", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("ALMA_fast", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("ALMA_slow", new RollingWindow<IndicatorDataPoint>(270)); this.IndicatorsWindows.Add("ALMAS_crosses", new RollingWindow<IndicatorDataPoint>(270)); return; } public void CheckSMAs(DateTime slcTime, QCAlgorithm algo) { //DateTime slcTime = algo.CurrentSlice.Time; this.IndicatorsWindows["SMA_VOL"].Add(new IndicatorDataPoint(slcTime, this.smaVolume)); this.IndicatorsWindows["SMA_STD_VOL"].Add(new IndicatorDataPoint(slcTime, this.stdVolume)); this.Closes.Add(new IndicatorDataPoint(slcTime, this.Bars[0].Close)); this.CalculateVDI_Stats(slcTime, algo, false); this.stdVolumeWasUpdated = false; this.stdVolumeWasUpdated = false; return; } public void CheckOBV(DateTime slcTime, QCAlgorithm algo) { // DateTime slcTime = algo.CurrentSlice.Time; try { this.ema_obv_long.Update(this.obv.Current); this.ema_obv_short.Update(this.obv.Current); this.obv_osc = this.ema_obv_short.Current - this.ema_obv_long; this.IndicatorsWindows["OBV_Osc"].Add(new IndicatorDataPoint(slcTime, this.obv_osc)); } catch (Exception e) { algo.Log($" CheckOBV Error: {e}"); } } public void Calculate_BB_Crossovers(DateTime barTime, QCAlgorithm algo) { //DateTime slcTime = algo.CurrentSlice.Time; bool crosses = false; if (this.IndicatorsWindows["BB_Signals"].Count() < 2) { this.IndicatorsWindows["BB_Crossover"].Add(new IndicatorDataPoint(barTime, 0)); this.IndicatorsWindows["BB_KillShorts"].Add(new IndicatorDataPoint(barTime, 0)); this.bbSignal = (this.bb.UpperBand + this.bb.LowerBand) / 2m; this.IndicatorsWindows["BB_Signals"].Add(new IndicatorDataPoint(barTime, this.bbSignal)); this.IndicatorsWindows["BB_UpperValues"].Add(new IndicatorDataPoint(barTime, 0)); this.IndicatorsWindows["BB_LowerValues"].Add(new IndicatorDataPoint(barTime, 0)); return; } try { if (this.Bars[0].Close > this.bbSignal) { this.bbSignal = Math.Max(this.bbSignal, this.bb.LowerBand); } else if (this.Bars[0].Close < this.bbSignal) { this.bbSignal = Math.Min(this.bbSignal, this.bb.UpperBand); } this.IndicatorsWindows["BB_Signals"].Add(new IndicatorDataPoint(barTime, this.bbSignal)); if (this.Bars[1].Close > this.IndicatorsWindows["BB_Signals"][1] & this.Bars[0].Close < this.bbSignal) { this.bbTrigger = -1; //algo.Log($" -- BBB -- BBB -- BBB -- BBB -- BBTrigger: {this.bbTrigger} Close: {this.Bars[0].Close.ToString("0.00")} Signal: {this.bbSignal.ToString("0.00")} "); this.IndicatorsWindows["BB_Crossover"].Add(new IndicatorDataPoint(barTime, this.bbTrigger)); } else if ( this.Bars[1].Close < this.IndicatorsWindows["BB_Signals"][1] & this.Bars[0].Close > this.bbSignal) { this.bbTrigger = 1; //algo.Log($" -- BBB -- BBB -- BBB -- BBB -- BBTrigger: {this.bbTrigger} Close: {this.Bars[0].Close.ToString("0.00")} Signal: {this.bbSignal.ToString("0.00")} "); this.IndicatorsWindows["BB_Crossover"].Add(new IndicatorDataPoint(barTime, this.bbTrigger)); } else { //this.bbTrigger = 0; //algo.Log($" -- BBB -- BBB -- BBB -- BBB -- BBTrigger: {this.bbTrigger} Close: {this.Bars[0].Close.ToString("0.00")} Signal: {this.bbSignal.ToString("0.00")} "); this.IndicatorsWindows["BB_Crossover"].Add(new IndicatorDataPoint(barTime, 0)); } this.IndicatorsWindows["BB_UpperValues"].Add(new IndicatorDataPoint(barTime, this.bb.UpperBand)); this.IndicatorsWindows["BB_LowerValues"].Add(new IndicatorDataPoint(barTime, this.bb.LowerBand)); bool foo = this.ContinueTrade(algo); foo = this.KillShort(algo); this.IndicatorsWindows["BB_KillShorts"].Add(new IndicatorDataPoint(barTime, this.killShort ? 1 : 0 )); } catch (Exception e) { algo.Debug($" Calculate_BB_Crossover Error {e}"); return; } } public void Calculate_TSI_Crossovers(DateTime slcTime, QCAlgorithm algo) { try { this.IndicatorsWindows["TSI"].Add(new IndicatorDataPoint(slcTime, this.tsi.Current)); this.IndicatorsWindows["TSI_Signal"].Add(new IndicatorDataPoint(slcTime, this.tsi.Signal)); if (this.IndicatorsWindows["TSI_Signal"].Count < 2) { this.IndicatorsWindows["TSI_Weak_Trigger"].Add(new IndicatorDataPoint(slcTime, 0m)); this.IndicatorsWindows["TSI_Strong_Trigger"].Add(new IndicatorDataPoint(slcTime, 0m)); return; } RollingWindow<IndicatorDataPoint> TSIWin = this.IndicatorsWindows["TSI"]; RollingWindow<IndicatorDataPoint> TSISigWin = this.IndicatorsWindows["TSI_Signal"]; var TSI_Sig_Crossings = TSIWin.Skip(1).Zip(TSIWin, (aPrev, a) => new { Prev = aPrev, Current = a }) .Zip(TSISigWin.Skip(1).Zip(TSISigWin, (bPrev, b) => new { Prev = bPrev, Current = b }), (a, b) => new { TSIWin = a, TSISigWin = b }) .Where(pair => (pair.TSIWin.Current > pair.TSISigWin.Current && pair.TSIWin.Prev < pair.TSISigWin.Prev) || (pair.TSIWin.Current < pair.TSISigWin.Current && pair.TSIWin.Prev > pair.TSISigWin.Prev)) .Select((pair, index) => { int crossingValue = (pair.TSIWin.Current > pair.TSISigWin.Current) ? 1 : -1; return new { Index = index, CrossingMSValues = crossingValue, IndyValue = pair.TSIWin.Current }; }) .OrderBy(item => item.Index); var mostRecentTSI_SigCrossing = TSI_Sig_Crossings.FirstOrDefault(); if (!TSI_Sig_Crossings.IsNullOrEmpty()) { int TSI_Sig_CrossingValue = mostRecentTSI_SigCrossing.CrossingMSValues; decimal TSI_SIG_IndyValue = mostRecentTSI_SigCrossing.IndyValue; this.tsi_trigger = TSI_Sig_CrossingValue == -1 ? -1 : TSI_Sig_CrossingValue == 1 ? 1 : 0; } if (this.IndicatorsWindows["TSI"][1] > 0) { if (this.IndicatorsWindows["TSI"][1] > this.IndicatorsWindows["TSI_Signal"][1] & this.IndicatorsWindows["TSI"][0] < this.IndicatorsWindows["TSI_Signal"][0]) this.tsi_weak_trigger = -1; else this.tsi_weak_trigger = 0; if (this.IndicatorsWindows["TSI"][0] < 0) this.tsi_strong_trigger = -2; else this.tsi_strong_trigger = 0; } else if (this.IndicatorsWindows["TSI"][1] < 0) { if (this.IndicatorsWindows["TSI"][1] < this.IndicatorsWindows["TSI_Signal"][1] & this.IndicatorsWindows["TSI"][0] > this.IndicatorsWindows["TSI_Signal"][0]) this.tsi_weak_trigger = 1; else this.tsi_weak_trigger = 0; if (this.IndicatorsWindows["TSI"][0] > 0) this.tsi_strong_trigger = 2; else this.tsi_strong_trigger = 0; } this.IndicatorsWindows["TSI_Weak_Trigger"].Add(new IndicatorDataPoint(slcTime, this.tsi_weak_trigger)); this.IndicatorsWindows["TSI_Strong_Trigger"].Add(new IndicatorDataPoint(slcTime, this.tsi_strong_trigger)); } catch (Exception e) { algo.Debug($" Calculate_TSI_Crossover Error {e}"); return; } } public void Calculate_ALMA_Crossovers(DateTime slcTime, QCAlgorithm algo) { try { this.IndicatorsWindows["ALMA_fast"].Add(new IndicatorDataPoint(slcTime, this.ALMA_fast.Current.Price)); this.IndicatorsWindows["ALMA_slow"].Add(new IndicatorDataPoint(slcTime, this.ALMA_slow.Current.Price)); if (this.IndicatorsWindows["ALMA_slow"].Count < 2) { this.IndicatorsWindows["ALMAS_crosses"].Add(new IndicatorDataPoint(slcTime, 0m)); return; } RollingWindow<IndicatorDataPoint> ALMAFastWin = this.IndicatorsWindows["ALMA_fast"]; RollingWindow<IndicatorDataPoint> ALMASlowWin = this.IndicatorsWindows["ALMA_slow"]; var ALMA_fast_slow_Crossings = ALMAFastWin.Skip(1).Zip(ALMAFastWin, (aPrev, a) => new { Prev = aPrev, Current = a }) .Zip(ALMASlowWin.Skip(1).Zip(ALMASlowWin, (bPrev, b) => new { Prev = bPrev, Current = b }), (a, b) => new { ALMAFastWin = a, ALMASlowWin = b }) .Where(pair => (pair.ALMAFastWin.Current > pair.ALMASlowWin.Current && pair.ALMAFastWin.Prev < pair.ALMASlowWin.Prev) || (pair.ALMAFastWin.Current < pair.ALMASlowWin.Current && pair.ALMAFastWin.Prev > pair.ALMASlowWin.Prev)) .Select((pair, index) => { int crossingValue = (pair.ALMAFastWin.Current > pair.ALMASlowWin.Current) ? 1 : -1; return new { Index = index, CrossingMSValues = crossingValue, IndyValue = pair.ALMAFastWin.Current }; }) .OrderBy(item => item.Index); var mostRecentALMA_fast_slow_Crossing = ALMA_fast_slow_Crossings.FirstOrDefault(); if (!ALMA_fast_slow_Crossings.IsNullOrEmpty()) { int ALMA_fast_slow_CrossingValue = mostRecentALMA_fast_slow_Crossing.CrossingMSValues; this.almas_crossing = ALMA_fast_slow_CrossingValue; } int ALMACross = 0; if(ALMAFastWin[1] < ALMASlowWin[1] && ALMAFastWin[0] > ALMASlowWin[0]) ALMACross = 1; if(ALMAFastWin[1] > ALMASlowWin[1] && ALMAFastWin[0] < ALMASlowWin[0]) ALMACross = -1; this.IndicatorsWindows["ALMAS_crosses"].Add(new IndicatorDataPoint(slcTime, (decimal)ALMACross)); this.continueTrade = this.ContinueTrade(algo); } catch (Exception e) { algo.Debug($" Calculate_ALMA_Crossover Error {e}"); return; } } public void Calculate_PSAR(DateTime slcTime, QCAlgorithm algo) { try { decimal avgLast5Closes = this.Bars.Take(5).Average(b => b.Close); this.psarSignal = this.psar > avgLast5Closes ? -1 : this.psar < avgLast5Closes ? 1 : 0; /// PSAR > avgLast5Closes == Bearish this.IndicatorsWindows["PSAR"].Add(new IndicatorDataPoint(slcTime, this.psar)); } catch (Exception e) { algo.Debug($" Calculate_PSAR Error {3}"); return; } } public void CalculateVDI_Stats(DateTime slcTime, QCAlgorithm algo, bool halt) { if (halt) { int i = 0; } bool BullishDivergence = false; bool BearishDivergence = false; decimal VDI_Value = 0; int cntr = 0; bool VolIncreasingTrend = false; bool VolDecreasingTrend = false; int _bullSensitivity = 3; int _bearSensitivity = 4; int _maLen = 20; decimal _spikeMultiplier = 3m; decimal _contractionMultiplier = 1m; int _trndSensitivity = 5; if (this.smaVolume != null & this.stdVolume != null) { decimal upperDeviationVolume = this.smaVolume + this.stdVolume; decimal lowerDeviationVolume = this.smaVolume - this.stdVolume; } else { return; } int priceLookback = 20; if (this.Bars.Count < priceLookback * _bullSensitivity) { return; } decimal LowPrice0 = this.Bars.Take(priceLookback).Min(p => p.Low); decimal LowPrice1 = this.Bars.Take(priceLookback * _bullSensitivity).Min(p => p.Low); bool LowerLowPrice_1 = this.Bars.Take(priceLookback + 1).Skip(1).Min(p => p.Low) == this.Bars.Take((priceLookback * _bullSensitivity) + 1).Skip(1).Min(p => p.Low); bool LowerLowPrice = LowPrice0 == LowPrice1; decimal HighPrice0 = this.Bars.Take(priceLookback).Max(p => p.High); decimal HighPrice1 = this.Bars.Take(priceLookback * _bearSensitivity).Max(p => p.High); bool HigherHighPrice = HighPrice0 == HighPrice1; bool HigherHighPrice_1 = this.Bars.Take(priceLookback + 1).Skip(1).Max(p => p.High) == this.Bars.Take((priceLookback * _bearSensitivity) + 1).Skip(1).Max(p => p.High); try { decimal highVol0 = this.Bars.Take(priceLookback).Skip(0).Max(p => p.Volume); decimal highVol1 = this.Bars.Take(priceLookback * _bearSensitivity).Skip(0).Max(p => p.Volume); decimal highVol0_1 = this.Bars.Take(priceLookback + 1).Skip(1).Max(p => p.Volume); decimal highVol1_1 = this.Bars.Take((priceLookback * _bearSensitivity) + 1).Skip(1).Max(p => p.Volume); bool volumeLowerHighs = highVol0 < highVol1; bool volumeLowerHighs_1 = highVol0_1 < highVol1_1; BearishDivergence = HigherHighPrice & volumeLowerHighs & !(HigherHighPrice_1 & volumeLowerHighs_1); decimal lowVol0 = this.Bars.Take(priceLookback).Skip(0).Min(p => p.Volume); decimal lowVol1 = this.Bars.Take(priceLookback * _bullSensitivity).Skip(0).Min(p => p.Volume); decimal lowVol0_1 = this.Bars.Take(priceLookback + 1).Skip(1).Min(p => p.Volume); decimal lowVol1_1 = this.Bars.Take((priceLookback * _bullSensitivity) + 1).Skip(1).Min(p => p.Volume); bool volumeHigherLows = lowVol0 > lowVol1; bool volumeHigherLows_1 = lowVol0_1 > lowVol1_1; BullishDivergence = LowerLowPrice & volumeHigherLows & !(LowerLowPrice_1 & volumeHigherLows_1); this.VDI_Divergence = BullishDivergence ? 1 : BearishDivergence ? -1 : 0; this.IndicatorsWindows["VDI_Divergence"].Add(new IndicatorDataPoint(slcTime, this.VDI_Divergence)); if (this.VDI_Divergence != 0) { if (BullishDivergence) VDI_Value = this.bb.UpperBand; this.VDI_Signal = 1; if (BearishDivergence) VDI_Value = this.bb.LowerBand; this.VDI_Signal = -1; // maintain VDI_Signal as either 1 or -1 } this.IndicatorsWindows["VDI_Signals"].Add(new IndicatorDataPoint(slcTime, VDI_Value)); /// record when VDI_Signals occur this.VDI_Volume_Bar_0 = this.Bars[0].Volume; /* cntr = 0; this.VDI_IncreasingVolumeTrend = false; if (this.Bars.Any(tb => tb.Volume < this.smaVolume)) { foreach (TradeBar tb in this.Bars) { if (tb.Volume > this.smaVolume) { cntr += 1; if (cntr > _trndSensitivity) { this.VDI_IncreasingVolumeTrend = true; break; } } } } this.VDI_DecreasingVolumeTrend = false; if (!VolIncreasingTrend & this.Bars.Any(tb => tb.Volume > this.smaVolume) ) { cntr = 0; foreach (TradeBar tb in this.Bars) { if (tb.Volume < this.smaVolume) { cntr += 1; if(cntr < _trndSensitivity) { this.VDI_DecreasingVolumeTrend = true; break; } } } } */ System.Data.DataRow dr = this.VDIData.NewRow(); dr["DateTime"] = slcTime; dr["LowPrice0"] = LowPrice0; dr["LowPrice1"] = LowPrice1; dr["HighPrice0"] = HighPrice0; dr["HighPrice1"] = HighPrice1; dr["LowVolume0"] = lowVol0; dr["LowVolume1"] = lowVol1; dr["HighVolume0"] = highVol0; dr["HighVolume1"] = highVol1; dr["LowerLowPrice"] = LowerLowPrice ? "Yes" : "No"; ; /// bullish dr["HigherLowVolume"] = HigherHighPrice ? "Yes" : "No"; /// bearish dr["HigherHighPrice"] = volumeHigherLows ? "Yes" : "No"; ; /// bullish dr["LowerHighVolume"] = volumeLowerHighs ? "Yes" : "No"; ; /// bearish dr["BullishDivergence"] = BullishDivergence ? "YES" : "NO"; dr["BearishDivergence"] = BearishDivergence ? "YES" : "NO"; this.VDIData.Rows.Add(dr); } catch (Exception e) { algo.Log($" CalculateVDI_Stats Error {e}"); return; } } public decimal GetAvgMomP(decimal stkPrc) { this.decMOMP = (this.thisMOMP_1.Current + this.thisMOMP_3.Current + this.thisMOMP_6.Current) / 3m; this.decOneMonthForecast = this.decOneYearPriceTarget_Current = this.decOneYearPriceTarget_Initial = (1m + this.decMOMP / 100m) * stkPrc; return this.decMOMP; } public void UpdateBeta(TradeBar symbolTradeBar, TradeBar spyTradeBar) { thisBeta.Update(symbolTradeBar); thisBeta.Update(spyTradeBar); //_window.Add(symbolTradeBar); } } // end class SymbolData } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { // ********************** GetETFMatrix ******************************************* // *** This code will buy the ETF and the 10-15%OTM Put and Sell the Call // *** closest to the current Stock Price plus the Avgerage MOMP // ****************************************************************************************** public bool GetETFMatrix(ref LookupData LUD, bool firstTime){ try{ LUD.clearLD(this); LUD.SSQRMatrix.Clear(); LUD.intType = 1; //LUD.uSymbol = SD.symbol; OrderTicket etfTicket; OrderTicket putTicket; OrderTicket callTicket; Slice slc = CurrentSlice; if (LUD.doTracing) Log($" -- PUTETFON PUTETFON {LUD.uSymbol.Value} PUTETFON PUTETFON --"); decimal stockPrice = LUD.stockPrice; OptionChain allUnderlyingOptions = null; // chain opbjec to get all contracts OptionContract putContract; // contract object to collect put greeks OptionContract callContract; // contract object to collect call greeks //OptionContract wcContract; // contract object to collect wing call greeks Greeks putGreeks; Greeks callGreeks; Greeks wcGreeks; DateTime tradeDate = slc.Time; // current date, presumed date of trade //DateTime targetExpiration = GetLastOptionsExpiry(tradeDate.AddMonths(1)); DateTime targetExpiration = FindDay(tradeDate.AddMonths(1).Year, tradeDate.AddMonths(1).Month, DayOfWeek.Friday, 4); if (targetExpiration.Month == targetExpiration.AddDays(7).Month) targetExpiration = targetExpiration.AddDays(7); LUD.decOneYearPriceTarget = etfDataBySymbol[LUD.uSymbol].decOneYearPriceTarget_Initial; decimal decMOMP = etfDataBySymbol[LUD.uSymbol].decMOMP; Debug($" ETF ETF ETF -- The 1Yr Price for {LUD.uSymbol.Value} is {stockPrice.ToString("0.00")} with MOMP: {decMOMP.ToString("0.00")} yielding a target of {LUD.decOneYearPriceTarget.ToString("0.00")}"); decimal estTrgtPutStrk = .95M * stockPrice; decimal estTrgtCallStrk = .95M * LUD.decOneYearPriceTarget; foreach(var chain in slc.OptionChains.Values){ if (!chain.Underlying.Symbol.Equals(LUD.uSymbol)) { continue; } allUnderlyingOptions = chain; break; } if (allUnderlyingOptions == null) { if (LUD.doTracing) Debug($" -- PUTETFON PUTETFON PUTETFON No options returned at {slc.Time} for {LUD.uSymbol.Value} "); return false; // return null SSQRMatrix and pass control back to OnData() } var putChains = allUnderlyingOptions.Where(o=>o.Right == OptionRight.Put & DateTime.Compare(o.Expiry,targetExpiration)<=0 & o.Strike <= estTrgtPutStrk) .OrderByDescending(o=>o.Expiry) .ThenByDescending(o=>Math.Abs(estTrgtPutStrk-o.Strike)); var callChains = allUnderlyingOptions.Where(o=>o.Right == OptionRight.Call & DateTime.Compare(o.Expiry,targetExpiration)<=0 & o.Strike >= estTrgtCallStrk & o.BidPrice >= 0.20m) .OrderByDescending(o=>o.Expiry) .ThenByDescending(o=>Math.Abs(estTrgtCallStrk-o.Strike)); //Debug($" -- PUTETFON PUTETFON -- TARGET EXPIRATION IS {targetExpiration.ToString()}. There are {putChains.Count()} puts and {callChains.Count()} calls."); //foreach( var chain in allUnderlyingOptions){ // Debug($" -- PUTETFON PUTETFON -- OPTION IS: {chain.Symbol.Value}"); //} putContract = putChains.FirstOrDefault(); callContract = callChains.FirstOrDefault(); //if (putContract == null | callContract == null) { if (putContract == null) { if(LUD.doTracing) Debug($" -- PUTETFON Failed to get PUT for {LUD.uSymbol.Value} @ {stockPrice.ToString()} with Put Target={estTrgtPutStrk.ToString()}"); //and Call Target={estTrgtCallStrk.ToString()}."); return false; } if (LUD.uSymbol.Value=="SH") { foreach( var chain in putChains){ if(LUD.doTracing) Debug($" -- PUTETFON PUTETFON -- PUTETFON PUTETFON PUT IS: {chain.Symbol.Value}"); } if(LUD.doTracing) Debug("-- PUTETFON PUTETFON -- PUTETFON PUTETFON -- PUTETFON PUTETFON -- PUTETFON PUTETFON "); foreach( var chain in callChains){ if(LUD.doTracing) Debug($" -- PUTETFON PUTETFON -- PUTETFON PUTETFON CALL IS: {chain.Symbol.Value}"); } } if(LUD.doTracing) Debug($" -- PUTETFON PUTETFON -- PUTETFON PUTETFON PUT: {putContract.Symbol.Value}."); if(LUD.doTracing && callContract!=null) Debug($" -- PUTETFON PUTETFON -- PUTETFON PUTETFON CALL: {callContract.Symbol.Value}."); SSQRColumn thisSSQRColumn = buildSSQRColumn(putContract, callContract, this, LUD); if (thisSSQRColumn != null) { LUD.SSQRMatrix.Add(thisSSQRColumn); } else if(LUD.doTracing) Debug($" -- PUTETFON PUTETFON PUTETFON Failed to get ETF SSSQRColumn for {LUD.uSymbol.Value}."); var orderedSSQRMatrix = LUD.SSQRMatrix.OrderBy(s => s.stockPrice); IterateOrderedSSQRMatrix(orderedSSQRMatrix); if (!firstTime) return !firstTime; // if not first time, then return TRUE so the LUD.SSQRMatrix will be used in subsequent procdessing TradePerfRec thisNewCollar = new TradePerfRec(); // create a tradePerfRec #1 for the puts sold, solely to log their P/L (including underlying unrealized P/L). etfTicket = MarketOrder(LUD.uSymbol, 1000); if (etfTicket.Status == OrderStatus.Filled) { didTheTrade = true; thisNewCollar.strtngCndtn = "INITIAL COLLAR"; thisNewCollar.isOpen = true; thisNewCollar.isInitializer = true; thisNewCollar.tradeRecCount = 0;; thisNewCollar.index = etfDataBySymbol[LUD.uSymbol].intTPRCntr += 1; thisNewCollar.startDate = slc.Time; thisNewCollar.expDate = putContract.Expiry; thisNewCollar.thetaExpiration = callContract!=null ? callContract.Expiry : default; thisNewCollar.uSymbol = LUD.uSymbol; thisNewCollar.cSymbol = callContract.Symbol; thisNewCollar.pSymbol = putContract.Symbol; //thisNewCollar.wcSymbol = tradableWCall; thisNewCollar.uStartPrice = etfTicket.AverageFillPrice; thisNewCollar.pStrike = putContract.Strike; thisNewCollar.cStrike = callContract!=null ? callContract.Strike : 0; //thisNewCollar.wcStrike = thisSSQRColumn.wCallStrike; thisNewCollar.uQty = (int)etfTicket.QuantityFilled; thisNewCollar.ROR = thisSSQRColumn.ROR; thisNewCollar.ROC = thisSSQRColumn.ROC; thisNewCollar.CCOR = thisSSQRColumn.CCOR; thisNewCollar.RORThresh = RORThresh; thisNewCollar.ROCThresh = ROCThresh; thisNewCollar.CCORThresh = CCORThresh; thisNewCollar.tradeCriteria = "ETF"; //thisNewCollar.stockADX = 0; //lastAdx; //thisNewCollar.stockADXR = 0; //lastAdxr; //thisNewCollar.stockOBV = 0; //lastObv; //thisNewCollar.stockAD = lastAd; //thisNewCollar.stockADOSC = lastAdOsc; //thisNewCollar.stockSTO = lastSto; //thisNewCollar.stockVariance = lastVariance; thisNewCollar.SSQRnetProfit = etfTicket.QuantityFilled * thisSSQRColumn.netIncome; //thisNewCollar.VERating = LUD.intVERating; //thisNewCollar.momentum = LUD.decMomentum; //thisNewCollar.oneYearPriceTarget = LUD.decOneYearPriceTarget; //thisNewCollar.momentumRank = LUD.intMomentumRank; 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 { */ if (callContract!=null ) { callTicket = MarketOrder(callContract.Symbol, -10); if (callTicket.Status == OrderStatus.Filled) { thisNewCollar.cStartPrice = callTicket.AverageFillPrice; thisNewCollar.cQty = (int)callTicket.QuantityFilled; } } putTicket = MarketOrder(putContract.Symbol, 10); if (putTicket.Status == OrderStatus.Filled) { thisNewCollar.pStartPrice = putTicket.AverageFillPrice; thisNewCollar.pQty = (int)putTicket.QuantityFilled; } doTheTrade = true; ETFRecs.Add(thisNewCollar); if (doTracing) Log("-- PUTETFON PUTETFON PUTETFON -- ADDING AN ETFPR "); return true; } } catch (Exception excp){ Debug($" PUTETFON ERROR {excp}"); } return false; } } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Drawing; using QuantConnect.Util; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Data.Consolidators; using QuantConnect.Orders; using QuantConnect.Securities; using QuantConnect.Securities.Option; #endregion namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { int SecAddedCnt = 0; int SecRemvdCnt = 0; static Universe ourUniverse; public bool badDtParameter; // get this from the parameters for debugging public bool haltProcessing = false; // use this to trap ERROR public bool doTracing = false; // turn //if (doTracing) Log() process tracing on/off public bool logPortfolio = false; // for tracking portfolio changes public bool doDeepTracing = false; // turn //if (doDeepTracing) Log() process tracing on/off public bool addedStocks = false; 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 List<Symbol> SymbolsToRemove = new List<Symbol>(); decimal stockDollarValue; // get the dollar value for the position decimal sharesToBuy = 1000; // 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 decOIThresh = 0; // Threshold for Open Interest used to eliminate stocks decimal decThisOI = 0; // this underlying's options OI for ATM front month decimal vix; // used to track and log vix values bool doTheTrade = false; // Used to allow trades the algorithm initiates bool didTheTrade = false; // used to toggle iterating SSQRMatrix bool useDeltas = false; // used to turn use of deltas in trade determination on or off 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; public Dictionary<Symbol, decimal> lastClosePrice; LookupData LUD = new LookupData(); // repository of system-wide and common data List<TradePerfRec> tradeRecs = new List<TradePerfRec>(); // used to track P&L of trades List<TradePerfRec> tprsToClose = new List<TradePerfRec>(); // List of TPRs to Close. // use this in OnData TPR-driven position updating List<TradePerfRec> tprsToOpen = new List<TradePerfRec>(); // List of TPRs to Open. List<TradePerfRec> secTPRs = new List<TradePerfRec>(); List<TradePerfRec> thetaTPRs = new List<TradePerfRec>(); List<TradePerfRec> ETFRecs = new List<TradePerfRec>(); // LIst of ETF Packages 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; int curr2ndTPR = 0; // Used to store index int curr1stTPR = 0; // used to store index of 1st TPR int CountTPRs = 0; // Use this to filter FineFilterSelection to 1 stock as specified by Algorithm Parameter. static string strFilterTkr = ""; Symbol thisSymbol; // Initialize Symbol as class variable //decimal incrPrice = 0; // check for underlying price appreciation 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<ColumnSD> CSDsToDo = new List<ColumnSD>(); List<SymbolData> SDsThatCanRoll = new List<SymbolData>(); decimal stockDividendAmount = 0M; string divFrequency = "Quarterly"; decimal divPlotValue = 0M; DateTime lastDate = DateTime.MinValue; DateTime newDate = DateTime.MinValue; Symbol symbSPY; 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 // Holds multi ticker data Dictionary<Symbol, SymbolData> symbolDataBySymbol = new Dictionary<Symbol, SymbolData>(); Dictionary<Symbol, SymbolData> etfDataBySymbol = new Dictionary<Symbol, SymbolData>(); /// SDBS for ETFs bool doETF; // flag to limit ETF processing to 1X on last day public override void Initialize() { var keys = ObjectStore.Keys; foreach (var key in keys){ ObjectStore.Delete(key); } ObjectStore.Save("Test Entry", "nothing to see here"); 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(30000000); /// Set Strategy Cash $100,000,000 for ~50 positions @ $30,000 risk per strFilterTkr = GetParameter("stockTicker"); if (strFilterTkr == "-"){ strFilterTkr = ""; ourUniverse = AddUniverse<StockDataSource>("VE-IEX-Combo", stockDataSource => { return stockDataSource.SelectMany(x => x.Symbols).Take(35); // take 30 for 2022 for 2023 }); } else { AddEquity(strFilterTkr, Resolution.Minute, Market.USA, true, 3, false, DataNormalizationMode.Raw); } badDtParameter = GetParameter("CheckBadDate") == "true" ? true : false; // get this from parameters stockDollarValue = Convert.ToDecimal(GetParameter("StockDollarValue")); LUD.unitRiskAmt = stockDollarValue; // package dollar amount of risk per package at start useDeltas = GetParameter("UseDeltas") == "true" ? true : false; // get this from parameters decOIThresh = Convert.ToDecimal(GetParameter("OpenIntrstThresh")); // get the threshold of open interest to eliminate thinly-traded securities LUD.InitializeData(this); LUD.maxPutOTM = Convert.ToInt16(GetParameter("MaxOTMPutDepth")); // get and set the Maximum OTM Put Depth LUD.maxCallOTM = Convert.ToInt16(GetParameter("MaxOTMCallDepth")); // get and set the Maximum OTM Put Depth doTracing = LUD.doTracing = GetParameter("LogTrace") == "true" ? true : false; // get this from paramters to turn //if (doTracing) Log() tracing on/off doDeepTracing = LUD.doDeepTracing = GetParameter("LogTraceDeeper") == "true" ? true : false; // get this from paramters to turn //if (doTracing) Log() tracing on/off //symbSPY = AddEquity("SPY", Resolution.Daily, Market.USA, true, 0, false, DataNormalizationMode.Adjusted).Symbol; //BuildETFDBS(); } // // // /// /// /// /// /// Initialize() public void OnData(Dividends dData) { try{ if (Portfolio.Invested) { int k = 0; // counter for updates if (tradeRecs.Any(tpr=> tpr!=null && tpr.isOpen & !tpr.isSecondary & !tpr.isTheta)) { foreach(var tprec in tradeRecs.Where(tpr=> tpr.isOpen && !tpr.isSecondary && !tpr.isTheta)) { if (tprec.uSymbol != null) { if(dData.ContainsKey(tprec.uSymbol)) { var paymentAmount = dData[tprec.uSymbol].Distribution; tprec.numDividends = tprec.numDividends + 1; tprec.divIncome = tprec.divIncome + paymentAmount; k = k + 1; //if (doDeepTracing) Log(" DDDDDDDDDD DDDDDDDDDDD DIVIDENDS FOR " + tprec.uSymbol + " ARE " + paymentAmount); } } } } } } catch (Exception errMsg) { if (doTracing) Log(" DIV ERROR DIV ERROR DIV ERROR " + errMsg); return; } } public override void OnData(Slice data) { bool processClosedTPRs = false; logPortfolio = true; SecAddedCnt = SecRemvdCnt = 0; haltProcessing = false; if (CheckBadDate(data.Time)) { haltProcessing = true; Log($" --- --- === HALT PROCESSING"); } if (IsMarketOpen("IBM") & data.Time.Hour == 9 & data.Time.Minute == 41) { if (tradeRecs.Any(tpr=> tpr!=null && tpr.isOpen && (Portfolio[tpr.uSymbol].Invested && tpr.cSymbol!=null && !Portfolio[tpr.cSymbol].Invested)) ) { //// careful here. only primary and secondary (non-theta) tprs have uSymbols foreach(TradePerfRec tprec in tradeRecs.Where(tpr=> tpr.isOpen && (Portfolio[tpr.uSymbol].Invested && tpr.cSymbol!=null))) { if (IsOrphaned(tprec.uSymbol)) { LUD.clearLD(this); LUD.uSymbol = tprec.uSymbol; tprec.CleanUp(this); } } } if (tprsToClose.Count != 0) { foreach (TradePerfRec rec in tprsToClose) { if (Portfolio[rec.uSymbol].Invested){ OrderTicket liquidTix = MarketOrder(rec.uSymbol, -rec.uQty); if (liquidTix.Status == OrderStatus.Filled) rec.uEndPrice = liquidTix.AverageFillPrice; if (doTracing) Log($" |||| |||| LIQUIDATING ORPHAN STOCK {rec.uSymbol.Value} ON {data.Time.ToShortDateString()} AT {data.Time.ToShortTimeString()} SETTING PRICE TO {liquidTix.AverageFillPrice.ToString("0.00")}"); } rec.isOpen = false; if (symbolDataBySymbol.ContainsKey(rec.uSymbol)) { symbolDataBySymbol.Remove(rec.uSymbol); } } tprsToClose.Clear(); } if(symbolDataBySymbol.Any(sdbs => !Portfolio[sdbs.Key].Invested && sdbs.Value.openInterestCheck && (!sdbs.Value.isRollable | !sdbs.Value.isTriggered))) { foreach(var pair in symbolDataBySymbol.Where(sdbs=> !Portfolio[sdbs.Key].Invested && sdbs.Value.openInterestCheck && (!sdbs.Value.isRollable | !sdbs.Value.isTriggered))){ LUD.clearLD(this); LUD.uSymbol = pair.Key; LUD.getNxtIExDt(pair.Key.Value, this); LUD.loadVEData(this); LUD.intType = 0; /// stock LUD.GetTotalGain(0, Securities[pair.Key].Price); if (LUD.decTotalGain <= -.08M * Securities[pair.Key].Price ) { /// **** **** 2024-01 ... ensure 10% loss or greater 2023-11-23 adjust filter to total yield > 8 or 10% SDsThatCanRoll.Add(pair.Value); } } } if (SDsThatCanRoll.Count!=0){ foreach(SymbolData sd in SDsThatCanRoll){ if(doDeepTracing) Log($" -- --- --- recapture prospects that may now be tradable {sd.symbol.Value}"); sd.isRollable = true; bool placeholder =sd.IsTriggered(this); } SDsThatCanRoll.Clear(); } foreach(var sd in symbolDataBySymbol){ if (tradeRecs.Any(tpr => tpr!=null && tpr.uSymbol.Equals(sd.Key) /*&& tpr.isOpen && Portfolio[sd.Key].Invested*/)) { if (haltProcessing) Log($" -- %%%%%%%%%% => ASSESS KILL SHORT"); if (IsFirstTradingDay(data.Time, sd.Key)) sd.Value.TargetReversalKill(this, LUD); ///| sd.Value.killShort) { // on FDOM check if target increased by 10& or more or if on last consolidted bar, killShort triggere bool dummy = sd.Value.KillShort(this); if(doDeepTracing) Log($" tprtprtprtprtprtprtpr --- ContTrad: {sd.Value.continueTrade} KillShort: {sd.Value.killShort} SBB: {sd.Value.bbTrigger} VDI: {sd.Value.VDI_Signal} TSI: {sd.Value.tsi_trigger} ALMA-X: {sd.Value.almas_crossing} "); if (sd.Value.killShort) { LUD.clearLD(this); LUD.uSymbol = sd.Key; LUD.stockPrice = Securities[sd.Key].Price; LUD.loadVEData(this); TradePerfRec thistpr = tradeRecs.Where(tpr => tpr!=null && tpr.uSymbol.Equals(sd.Key) && tpr.isOpen).FirstOrDefault(); if (thistpr != null) { if (doDeepTracing) Log($" tprtprtprtprtprtprtpr --- ---- Called KillTheCollar for {thistpr.uSymbol.Value} because TargetReversalKill: {sd.Value.targetReversalKill.ToString()}"); if (!KillTheCollar(thistpr, ref LUD, "GREEN SBB or VDI", true, true) ) continue; tprsToClose.Add(thistpr); } } } } } if (data.Time.Hour < 10) return; /*if( doDeepTracing){ if (data.Time.Hour == 10 && data.Time.Minute == 1) { string sSBB = ""; string sVDI = ""; string sKill = ""; string sCont = ""; foreach(var pair in symbolDataBySymbol){ if (doDeepTracing) { sSBB = pair.Value.bbTrigger.ToString(); sVDI = pair.Value.VDI_Signal.ToString(); sKill = pair.Value.killShort.ToString(); sCont = pair.Value.continueTrade.ToString(); // Log($" -- TRiGGERS -- TRiGGERS -- TRiGGERS -- TRiGGERS SBB: {sSBB} VDI: {sVDI} Kill: {sKill} Cont: {sCont} "); } } } } */ if (IsMarketOpen("IBM")) { if (oLOs != null && oLOs.Count > 0) ProcessOpenLimitOrders(data); if(SymbolsToRemove.Count != 0) ProcessRemoveSecurities(); if (data.Time.DayOfYear != newDate.DayOfYear) { newDate = data.Time; if (lastDate.Equals(DateTime.MinValue)){ lastDate = newDate.AddDays(-1); } } } ////////////////////////////////////////////////////////////////////// // FIRST -- CHECK FOR ADDED STOCKS // UNIVERSE IS EVALUATED/LOADED MONTHLY AT MIDNIGHT ON 1ST DAY OF MONTH. if (IsMarketOpen("IBM") && addedStocks) { addedStocks = false; if (doTracing) Log($"- --- --- Got to addedstocks"); foreach(var sdbs in symbolDataBySymbol) { Symbol symbOIChk = sdbs.Key; //if(!data.ContainsKey(symbOIChk)) continue; // **** **** 2023-03-15 good candidates may not be traded every minute SymbolData symbDat = sdbs.Value; LUD.clearLD(this); LUD.uSymbol = symbOIChk; LUD.loadVEData(this); /// **** **** 2023-03-15 commented out because LUD is not used in processing CheckOI //LUD.GetNextExDate(this); LUD.getNxtIExDt(symbOIChk.Value, this); OptionChain chnOICheck; if (!symbDat.openInterestCheck & (data.OptionChains.TryGetValue(symbDat.optSymbol, out chnOICheck))) { //if(LUD.decOneYearPriceTarget < chnOICheck.Underlying.Price) /// **** **** 2023-03-19 Try using total yield var atmPutContract_r = chnOICheck.Where(x => x.Right == OptionRight.Put) .OrderBy(x => x.Expiry) .ThenBy(x => Math.Abs(chnOICheck.Underlying.Price - x.Strike)) .FirstOrDefault(); var atmCallContract_r = chnOICheck.Where(x => x.Right == OptionRight.Call) .OrderBy(x => x.Expiry) .ThenBy(x => Math.Abs(chnOICheck.Underlying.Price - x.Strike)) .FirstOrDefault(); if(atmCallContract_r!=null) decThisOI = atmCallContract_r.OpenInterest; if(atmPutContract_r!=null) decThisOI = decThisOI + atmPutContract_r.OpenInterest; LUD.GetTotalGain(0, chnOICheck.Underlying.Price); if (doTracing) Debug($"--- {symbOIChk.Value} has Open Interest of: {decThisOI.ToString()} contracts. At {chnOICheck.Underlying.Price.ToString("0.00")} and projected total annual gain of {LUD.decTotalGain.ToString("0.00")} with a target price of {LUD.decOneYearPriceTarget.ToString()}"); if((decThisOI < decOIThresh)) { SymbolsToRemove.Add(atmCallContract_r.UnderlyingSymbol); continue; } symbDat.openInterestCheck = true; //if(!(Math.Abs(LUD.decTotalGain) >= Securities[symbOIChk].Price * .1m | LUD.decTotalGain <= -8m)) /// Total gain should be greater than 10% of current stock price (i.e. price target is less than 90% of current price) if(LUD.decTotalGain > -.08m * Securities[symbOIChk].Price) { symbDat.isRollable = false; /// eliminate VECase1 trades if(doDeepTracing) Log($" --- ---- MAIN --- ---- {symbDat.symbol.Value} has total gain of {LUD.decTotalGain.ToString("0.00")} and yield of {(LUD.decTotalGain/Securities[symbOIChk].Price).ToString("P2")} and is faded"); } else symbDat.isRollable = true; symbDat.PostOptionsFilteringCreateIndicators(); int daysSinceBeginning = data.Time.DayOfYear - StartDate.DayOfYear; if (daysSinceBeginning < 160) daysSinceBeginning = 160; var historyBars = History<TradeBar>(symbDat.symbol, daysSinceBeginning, Resolution.Daily); symbDat.smaVolume = new SimpleMovingAverage(20); symbDat.stdVolume = new StandardDeviation(20); symbDat.bb = new BollingerBands(12, 2); // using 12,2 (period, stdDev Multiplier) from SuperBollingerTrend (Expo) in TradingView symbDat.psar = new ParabolicStopAndReverse(); // PSAR signal symbDat.tsi = new TrueStrengthIndex(40, 10, 10, MovingAverageType.Exponential); // using 40, 10, 10 from Charley's TradingView observations symbDat.ALMA_fast = new ArnaudLegouxMovingAverage("afast", 9, 6, 0.85m); symbDat.ALMA_slow = new ArnaudLegouxMovingAverage("aslow", 18, 6, 0.85m); symbDat.obv = new OnBalanceVolume(); // Obv Osc component symbDat.ema_obv_long = new ExponentialMovingAverage(20); // Obv Osc component symbDat.ema_obv_short = new ExponentialMovingAverage(1); // OBV Osc component foreach(TradeBar tb in historyBars){ /// warm up historyBars and Indicator components symbDat.Bars.Add(tb); symbDat.smaVolume.Update(tb.Time, tb.Volume); symbDat.stdVolume.Update(tb.Time, tb.Volume); symbDat.CheckSMAs(tb.Time, this); symbDat.bb.Update(tb.Time, tb.Close); symbDat.Calculate_BB_Crossovers(tb.Time, this); symbDat.psar.Update(tb); symbDat.Calculate_PSAR(tb.Time, this); symbDat.tsi.Update(tb.Time, tb.Close); symbDat.Calculate_TSI_Crossovers(tb.Time, this); symbDat.ALMA_fast.Update(tb.Time, tb.Close); symbDat.ALMA_slow.Update(tb.Time, tb.Close); symbDat.Calculate_ALMA_Crossovers(tb.Time, this); symbDat.obv.Update(tb); symbDat.ema_obv_long.Update(tb.Time, tb.Close); symbDat.ema_obv_short.Update(tb.Time, tb.Close); symbDat.CheckOBV(tb.Time, this); } // wire up consolidator to update the indicator try{ symbDat.consolidator.DataConsolidated += (sender, baseData) => { // 'bar' here is our newly consolidated data var bar = (TradeBar)baseData; // update indicator //sd.thisMOMP_1.Update(bar.Time, bar.Close); //sd.thisMOMP_3.Update(bar.Time, bar.Close); //sd.thisMOMP_6.Update(bar.Time, bar.Close); symbDat.Bars.Add(bar); symbDat.smaVolume.Update(bar.Time, bar.Volume); symbDat.stdVolume.Update(bar.Time, bar.Volume); symbDat.bb.Update(bar.Time, bar.Close); symbDat.psar.Update(bar); symbDat.tsi.Update(bar.Time, bar.Close); symbDat.ALMA_fast.Update(bar.Time, bar.Close); symbDat.ALMA_slow.Update(bar.Time, bar.Close); symbDat.ema_obv_long.Update(bar.Time, bar.Close); symbDat.ema_obv_short.Update(bar.Time, bar.Close); symbDat.obv.Update(bar); }; SubscriptionManager.AddConsolidator(symbDat.symbol, symbDat.consolidator); symbDat.smaVolume.Updated += (sender, baseData) => { symbDat.smaVolumeWasUpdated = true; //symbDat.CheckSMAs(this); //symbDat.CalculateVDI_Stats(this, false); }; symbDat.stdVolume.Updated += (sender, baseData) => { symbDat.stdVolumeWasUpdated = true; symbDat.CheckSMAs(baseData.Time, this); //Log($" -- -- VDI: {symbDat.VDI_Signal.ToString()} | VDI Divergence: {symbDat.VDI_Divergence.ToString()} "); //Log($" ----- VDIwin: {symbDat.IndicatorsWindows["VDI_Signals"][0].ToString()} | VDI_divergence: {symbDat.IndicatorsWindows["VDI_Divergence"][0].ToString()} "); }; symbDat.bb.Updated += (sender, baseData) => { symbDat.Calculate_BB_Crossovers(baseData.Time, this); //Log($" -- -- SBB: {symbDat.bb.Current.ToString()} | with trigger = {symbDat.bbTrigger.ToString()}"); }; symbDat.psar.Updated += (sender, baseData) => { symbDat.Calculate_PSAR(baseData.Time, this); }; symbDat.tsi.Updated += (sender, baseData) => { symbDat.Calculate_TSI_Crossovers(baseData.Time, this); }; symbDat.ALMA_slow.Updated += (sender, baseData) => { symbDat.Calculate_ALMA_Crossovers(baseData.Time, this); bool placeHolder = symbDat.IsTriggered(this); /// ESSENTIAL UPDATE placeHolder = symbDat.ContinueTrade(this); /// }; symbDat.obv.Updated += (sender, baseData) => { //symbDat.OBVWasUpdated = true; symbDat.CheckOBV(baseData.Time, this); }; symbDat.isTriggered = symbDat.IsTriggered(this); } catch (Exception excp) { Log($"Error wiring up Consolidator for {symbDat.symbol} is {excp.Message}"); } } } } if (SymbolsToRemove.Count != 0 ) ProcessRemoveSecurities(); if (data.Time.Minute % 10 !=0) return; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // THIRD -- CHECK TPRS TO MANAGE EXISTING PACKAGES // ITERATE THROUGH TPRS AND EVALUATE OPPORTUNITIES EVERY 5 MINUTES AFTER 10:00 EST // THIRD A -- PROCESS STOCK MELTUP. Will not roll package, only kill it int k = 0; if (tradeRecs.Any(tpr=> tpr!=null && tpr.isOpen && (Securities[tpr.uSymbol].Price > 1.15M * tpr.cStrike) ) ){ //// careful here. only primary and secondary (non-theta) tprs have uSymbols foreach(var tprec in tradeRecs.Where(tpr=> tpr.isOpen && (Securities[tpr.uSymbol].Price > 1.15M * tpr.cStrike ))) { if(!symbolDataBySymbol.ContainsKey(tprec.uSymbol)) { tprsToClose.Add(tprec); processClosedTPRs = true; continue; } LUD.clearLD(this); LUD.uSymbol = tprec.uSymbol; LUD.stockPrice = Securities[tprec.uSymbol].Price; if (haltProcessing) Log($" -- %%%%%%%%%% => ASSESSING HANDLE COLLAPSE"); if (doDeepTracing) Log($" tprtprtprtprtprtprtpr --- ---- Calling HandleCollapse for {tprec.uSymbol.Value} because the current price {Securities[tprec.uSymbol].Price.ToString()} is more than 115% Callstrike: {tprec.cStrike.ToString()}"); SymbolData sdThis = symbolDataBySymbol[tprec.uSymbol]; if (!tprec.HandleCollapse(this, LUD, sdThis)) continue; k = k + 1; } } if (SymbolsToRemove.Count != 0 ) ProcessRemoveSecurities(); if (tprsToClose.Any(tpr=> tpr!=null)) { /// 2021-10-18 -- close TPRs here tprsToClose.ForEach(tpr=>tpr.CloseTPR()); } tprsToClose.Clear(); LUD.clearLD(this); k = 0; //////////////////////////////////////////////////////////////////// // THIRD B -- PROCESS ROLL DOWNS k = 0; if (tradeRecs.Any(tpr=> tpr!=null && tpr.isOpen && tpr.expDate.Subtract(data.Time).Days>1 && RollCriterium(tpr.uStartPrice, Securities[tpr.uSymbol].Price))) { //// careful here. only primary and secondary (non-theta) tprs have uSymbols foreach(var tprec in tradeRecs.Where(tpr=> tpr.isOpen && RollCriterium(tpr.uStartPrice, Securities[tpr.uSymbol].Price) )) { if(!symbolDataBySymbol.ContainsKey(tprec.uSymbol)) { tprsToClose.Add(tprec); processClosedTPRs = true; continue; } LUD.clearLD(this); LUD.uSymbol = tprec.uSymbol; LUD.loadVEData(this); LUD.SSQRMatrix.Clear(); if (haltProcessing) Log($" -- %%%%%%%%%% => ASSESSING ROLLING DOWN"); if (Securities[tprec.uSymbol].Price <= 1.03M * symbolDataBySymbol[tprec.uSymbol].decOneYearPriceTarget_Current && !symbolDataBySymbol[tprec.uSymbol].continueTrade) { if (doDeepTracing) Log($" tprtprtprtprtprtprtpr --- ---- Calling KillTheCollar for {tprec.uSymbol.Value} because SBB is GREEN. SBB: {symbolDataBySymbol[tprec.uSymbol].bbTrigger.ToString()} VDI: {symbolDataBySymbol[tprec.uSymbol].VDI_Signal.ToString()}"); SymbolData sdThis = symbolDataBySymbol[tprec.uSymbol]; if (!KillTheCollar(tprec, ref LUD, " GREEN SBB", true, true)) continue; tprsToClose.Add(tprec); } if (!tprec.CheckRollingDown(this, LUD)){ processClosedTPRs = true; continue; } k = k + 1; } } if(tprsToClose.Count!=0) { tprsToClose.ForEach(tpr=>tpr.CloseTPR()); } tprsToClose.Clear(); processClosedTPRs = false; if (tprsToOpen.Count!=0){ foreach(TradePerfRec tpr in tprsToOpen) { if (doTracing) Debug(" --- ---- ---- Adding new TPR " + tpr.uSymbol.Value); if (symbolDataBySymbol.ContainsKey(tpr.uSymbol)) { tpr.OpenTPR(); tradeRecs.Add(tpr); } } } tprsToOpen.Clear(); LUD.clearLD(this); k = 0; // THIRD C -- PROCESS EXPIRATION AND DIVIDEND ROLLS if (tradeRecs.Any(tpr=> tpr!=null && tpr.isOpen && data.Time.Subtract(tpr.startDate).Days >=10)) { foreach(var tprec in tradeRecs.Where(tpr=> tpr.isOpen && data.Time.Subtract(tpr.startDate).Days >= 10)) { if (haltProcessing) Log($" -- %%%%%%%%%% => ASSESSING EXPIRATION ROLLS"); if(!symbolDataBySymbol.ContainsKey(tprec.uSymbol)) { tprsToClose.Add(tprec); processClosedTPRs = true; continue; } LUD.clearLD(this); LUD.uSymbol = tprec.uSymbol; LUD.loadVEData(this); LUD.SSQRMatrix.Clear(); if (!tprec.CheckRolling(this, LUD)){ processClosedTPRs = true; continue; } k = k + 1; } } if(tprsToClose.Count!=0) { tprsToClose.ForEach(tpr=>tpr.CloseTPR()); } tprsToClose.Clear(); processClosedTPRs = false; if (tprsToOpen.Count!=0){ foreach(TradePerfRec tpr in tprsToOpen) { if (doTracing) Debug($" --- ---- ---- ---- ----- --- Adding new TPR {tpr.uSymbol.Value} in CheckRolling"); if (symbolDataBySymbol.ContainsKey(tpr.uSymbol)) { tpr.OpenTPR(); tradeRecs.Add(tpr); } } } tprsToOpen.Clear(); LUD.clearLD(this); k = 0; /////////////////////////////////////////////////////////////////////////////////////////////////////// // FOURTH -- CHECK SDBS PROSPECTS FOR OPENING POSITIONS // ITERATE THROUGH SDBS AND EVALUATE OPPORTUNITIES EVERY 15 MINUTES IN 10:00 EST HOUR // DO NOT PROCESS BEFORE 10:00 EST AS OPTIONS MARKETS TOO THIN AND SPREADS TOO WIDE if (data.Time.Minute % 20 !=0) return; /* if(symbolDataBySymbol.Any(sdbs => !Portfolio[sdbs.Key].Invested && (!sdbs.Value.isRollable | !sdbs.Value.isTriggered)) ) { foreach(var pair in symbolDataBySymbol.Where(sdbs => !Portfolio[sdbs.Key].Invested && (!sdbs.Value.isRollable | !sdbs.Value.isTriggered ))){ if (Securities[pair.Key].Price < .97m * pair.Value.decOneYearPriceTarget_Current ) { SymbolsToRemove.Add(pair.Key); if (doDeepTracing) Log($" -- -- Removing Dormant SDBS's {pair.Key.Value}: Price: {Securities[pair.Key].Price} Target: {pair.Value.decOneYearPriceTarget_Current} SBB: {pair.Value.bbTrigger} VDI: {pair.Value.VDI_Signal} PSAR: {pair.Value.psarSignal} TSI: {pair.Value.tsi} (trigger): {pair.Value.tsi_trigger} ALMA-X: {pair.Value.almas_crossing} IsTriggered: {pair.Value.isTriggered}."); } } } */ if (symbolDataBySymbol.Any(sdbs => sdbs.Value.isRollable && sdbs.Value.isTriggered && sdbs.Value.openInterestCheck && !Portfolio[sdbs.Key].Invested)) { try{ if (doTracing) Log($" --- SCANNING PROSPECTIVE PORTFOLIO CANDIDATES AT {data.Time.ToString()}."); foreach(var pair in symbolDataBySymbol.Where(sdbs => sdbs.Value.isRollable && sdbs.Value.IsTriggered(this) && sdbs.Value.openInterestCheck && !sdbs.Value.FadeShort(this, Securities[sdbs.Key].Price) && !Portfolio[sdbs.Key].Invested)) { k +=1; if (pair.Key == null) { Debug(" --- ---- ---- Found null key in foreach sdbs."); continue; } Symbol thisSymbol = pair.Key; if (doTracing) Log($" --- ---- Checking Correct KEY: the key is {pair.Key}."); if (!IsMarketOpen(thisSymbol)) continue; if (!data.ContainsKey(thisSymbol)) { if (doDeepTracing) Log($" --- ---- --- there's no data in slice for {thisSymbol.Value}. "); //continue; } if (!Securities.ContainsKey(thisSymbol)) { if (doDeepTracing) Log($" --- ---- --- there's no data in Securities for {thisSymbol.Value}. "); continue; } if (Portfolio[thisSymbol].Invested) continue; /// Don't compound packages. Do only 1 package per symbol at any time if (doTracing) Log($" --- --- CANDIDATE: {thisSymbol.Value}."); LUD.clearLD(this); LUD.uSymbol = thisSymbol; LUD.intType = 0; SymbolData symbolData = pair.Value; goodThresh = false; // set the threshold switch to false; hasPrimaryRec = hasSecondaryRec = false; // reset processing branch flags string tickerString = thisSymbol.Value; //////////////////////////////////////////////////////////////////////////// // IN VERSION 5 DO NOT CHECK FOR DIVIDENDS THIS MONTH // JUST DO THE TRADE WHEN VALUENGINE HAS A 5 ENGINE RATING //LUD.GetNextExDate(tickerString, this); //// returns DateTime.MinValue if no date discovered //// this function also populates the VE parameter members in LUD LUD.getNxtIExDt(tickerString, this); pair.Value.divdndAmt = LUD.divdndAmt; LUD.loadVEData(this); pair.Value.decOneYearPriceTarget_Initial = LUD.decOneYearPriceTarget; pair.Value.decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget; pair.Value.initialTargetEndDate = LUD.initialTargetEndDate; hasDividends = true; stockPrice = Securities[thisSymbol].Price; if (stockPrice == 0) { if (doDeepTracing) Log($" --- ---- --- Securities price for {thisSymbol.Value} is zero. "); //LUD.clearLD(this); continue; } //if ((LUD.divdndAmt * 4m) + LUD.decOneYearPriceTarget < 1.05m * stockPrice) { // LUD.GetTotalGain(0, stockPrice); if (LUD.decTotalGain > -.03M * stockPrice) { // **** **** 2024-01 enforce 3% decline threshhold 2023-03-18 modified to total projected gain in 9 mos. //symbolData.isRollable = false; //SymbolsToRemove.Add(thisSymbol); // don't trade and remove any symbol that isn't set to apprciate at least 5% if (doTracing) Debug($" *^*^*^*^*^**^*^**^ FADING ENTRY OF {thisSymbol.Value} @ {stockPrice.ToString("0.00")} due to less than 3% potential yield or {(LUD.decTotalGain/stockPrice*100).ToString("P2")}."); //LUD.clearLD(this); continue; } thisROC = 0; thisROR = 0; LUD.SSQRMatrix.Clear(); if(doTracing) Log($" -- PSAR: {symbolData.psarSignal.ToString()} | SBB: {symbolData.bbTrigger.ToString()} | VDI: {symbolData.VDI_Signal.ToString()} | TSI {symbolData.tsi_weak_trigger.ToString()} | ALMA-X {symbolData.almas_crossing.ToString()}"); if (hasDividends & symbolData.isRollable & symbolData.openInterestCheck & !symbolData.FadeShort(this, Securities[symbolData.symbol].Price) & symbolData.IsTriggered(this)) { bestSSQRColumn = GetBestCollar(this, ref LUD); // send an LUD with the required information for making trading decisions if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty()) // just in case somehow we got here with a null bestSSQRColumn { if (doTracing) Log($"*** *** *** null OR EMPTY bestSSQR in Trade Initializing {thisSymbol} *************"); symbolData.SSQRFailCnt += 1; if (symbolData.SSQRFailCnt >= 4) { if(doTracing) Log($"*** *** *** *** Removing {thisSymbol} after 4 null bestSSQLRs."); SymbolsToRemove.Add(pair.Key); } //LUD.clearLD(this); continue; } else { if (!bestSSQRColumn.IsEmpty()) { if (doTracing) Log($"*** *** *** ADDING NEW BESTSSQRCOLUMN AND SYMBOLDATA TO BE PROCESSED -- {thisSymbol} --- on " + data.Time.ToShortDateString() + " at " + data.Time.ToShortTimeString()) ; //LUD.GetNextExDate(bestSSQRColumn.uSymbol, this); //LUD.loadVEData(this); //LUD.uSymbol=bestSSQRColumn.uSymbol; ColumnSD thisCSD = new ColumnSD(bestSSQRColumn, ref symbolData, LUD); CSDsToDo.Add(thisCSD); } /// !bestSSQRColumn.IsEmpty() //LUD.clearLD(this); } } else { // buyMoreShares & hasDividends & symbolData.isRollable & symbolData.openInterestCheck if (doDeepTracing) Log($" --- ---- --- --- {thisSymbol.Value} FAILED ROLLABLE or OI TEST. "); //LUD.clearLD(this); } } // end ForEach(var pair in symbolDataBySymbol) } catch (Exception forExcp) { Log($" Error Prospect Evaluation in foreach sdbs in SDBS " + forExcp.Message); //LUD.clearLD(this); } } else if (symbolDataBySymbol.Any(sdbs => !Portfolio[sdbs.Key].Invested)) { if (doTracing) Log($" --- RE-SCANNING PROSPECTIVE PORTFOLIO CANDIDATES AT {data.Time.ToString()}."); foreach(var pair in symbolDataBySymbol.Where(sdbs => !Portfolio[sdbs.Key].Invested)) { if(doTracing) Log($" --- --- Symbol: {pair.Key.Value} Rollable: {pair.Value.isRollable} OICheck: {pair.Value.openInterestCheck} Triggered: {pair.Value.isTriggered} Faded: {pair.Value.fadeShort}."); if(doTracing) Log($" --- --- PSAR: {pair.Value.psarSignal} SBB: {pair.Value.bbTrigger} VDI: {pair.Value.VDI_Signal} TSI: {pair.Value.tsi_trigger} ALMA-X: {pair.Value.almas_crossing}."); } } if(CSDsToDo.Count != 0){ foreach (ColumnSD csd in CSDsToDo){ if(doDeepTracing) Log($" *** *** *** *** *** PROCESSING CSDsToDo: {csd.sd.symbol.Value}. "); if (csd.ld!=null){ csd.ld.clearLD(this); csd.ld.uSymbol=csd.sd.symbol; //csd.ld.GetNextExDate(this); csd.ld.getNxtIExDt(csd.sd.symbol.Value, this); csd.ld.loadVEData(this); } if(csd.ld==null && doDeepTracing) { if(doTracing) Log($" *** *** *** *** *** {csd.sd.symbol.Value} is null for some reason"); } else if(doDeepTracing) Log($" *** *** *** *** *** LUD Check {csd.ld.uSymbol.Value} has VE statistic OneYearPriceTarget={csd.ld.decOneYearPriceTarget.ToString("0.00")}" ); ExecuteSSQRs(data, csd); // TradeDetermination if (csd.sd.bbTrigger == 1) { csd.sd.bbTrigger = 0; // Clear an opposing/closing bbTrigger csd.sd.killShort = false; } if (csd.sd.VDI_Signal == 1) csd.sd.VDI_Signal = 0; // Clear an opposing/closing bbTrigger Log($" tprtprtprtprtprtprtpr --- ContTrad: {csd.sd.continueTrade} KillShort: {csd.sd.killShort} SBB: {csd.sd.bbTrigger} VDI: {csd.sd.VDI_Signal} TSI: {csd.sd.tsi_trigger} ALMA-X: {csd.sd.almas_crossing} "); } CSDsToDo.Clear(); } } // OnData() public void ProcessRemoveSecurities() { SymbolData sd_r; if (SymbolsToRemove.Count != 0) { foreach(var symbol_r in SymbolsToRemove) { if (ourUniverse !=null && ourUniverse.ContainsMember(symbol_r)) { if (symbolDataBySymbol.ContainsKey(symbol_r)){ symbolDataBySymbol[symbol_r].isRollable = false; symbolDataBySymbol[symbol_r].isTriggered = false; } continue; } if (doDeepTracing) Log($" -- Removing SDBS Record for {symbol_r.Value}"); if(symbolDataBySymbol.ContainsKey(symbol_r)) { sd_r = symbolDataBySymbol[symbol_r]; /// SDBS for this symbol_r if(doTracing) Log($" - - --- Processing {sd_r.symbol.Value} out of SymbolDataBySymbol"); if(strFilterTkr != ""){ foreach (var indyWinKVP in sd_r.IndicatorsWindows) { /// Dictionary<string, RollingWindow<IndicatorDataPoint>> var indyDataPointsWin = indyWinKVP.Value.Reverse(); /// RollingWindow<IndicatorDataPoint> String strObj = ""; String strIndName = indyWinKVP.Key; // Log($" -- -- Processing SDBS Indicator: {indyWinKVP.Key}"); //// DIAGNOSTIC LOGGING foreach(IndicatorDataPoint indyDataPoint in indyDataPointsWin){ strObj += $"{indyDataPoint.Time}, {indyDataPoint.Value.ToString("0.00")} \n"; // Log($" -- -- -- {indyDataPoint.Time}, {indyDataPoint.Value.ToString("0.00")}"); /// Object Store DIAGNOSTICS } ObjectStore.Save(symbol_r.Value + "_" + strIndName, strObj); // Debug($" ObjStore Value saved: {strObj}"); } } RemoveLowOI_OptSymbs(symbol_r); if (!Portfolio[symbol_r].Invested) { SubscriptionManager.RemoveConsolidator(symbol_r, sd_r.consolidator); RemoveSecurity(symbol_r); if (doTracing) Debug("--- --- *** " + SecRemvdCnt.ToString() + ": Removing " + symbol_r.Value + " Collar killed or no OI."); symbolDataBySymbol.Remove(symbol_r); } } } } SymbolsToRemove.Clear(); /* Log($" |||| |||| LOGGING THE HOLDINGS ON REMOVE SYMBOLS"); foreach(var kvp in Securities) { var security = kvp.Value; if (security.Invested) { Log($" |||| |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } */ } public void RemoveLowOI_OptSymbs(Symbol symbol) { if (symbolDataBySymbol.ContainsKey(symbol)) { SymbolData sd = symbolDataBySymbol[symbol]; if (Portfolio[sd.optSymbol].Invested) Debug($" *^*^*^*^**^*^*^ *^*^*^*^*^*^*^^* OPTION BEING LIQUIDATED: {symbolDataBySymbol[symbol].optSymbol.Value} ||||||"); RemoveSecurity(sd.optSymbol); //symbolDataBySymbol.Remove(symbol); } } public override void OnSecuritiesChanged(SecurityChanges changes) { foreach(var security in changes.AddedSecurities) { // Debug("Securities updated at " + Time.ToString() + ": " + security.Symbol.Value); SecAddedCnt += 1; Symbol thisSymbol = security.Symbol; if (security.Type == SecurityType.Equity) { // Debug("Securities updated at " + Time.ToString() + ": " + security.Symbol.Value); if (symbolDataBySymbol.ContainsKey(thisSymbol)) { if (doDeepTracing) Debug("--- --- " + SecAddedCnt.ToString() + " SDBS already has " + thisSymbol.ToString()); continue; } if (thisSymbol.Value == "HLT" | thisSymbol.Value == "META" | thisSymbol.Value == "FOX"){ RemoveSecurity(thisSymbol); continue; } addedStocks = true; var opt = AddOption(thisSymbol, Resolution.Minute, Market.USA, true, 0m); opt.PriceModel = OptionPriceModels.BjerksundStensland(); /// necessary for Greeks opt.SetFilter(universe => from symbol in universe //.IncludeWeeklys() .Expiration(TimeSpan.Zero, TimeSpan.FromDays(190)) where symbol.ID.StrikePrice >= 0.8m * universe.Underlying.Price & symbol.ID.StrikePrice <= 1.2m * universe.Underlying.Price select symbol); symbolDataBySymbol.Add(thisSymbol, new SymbolData(thisSymbol, true, false, opt.Symbol, symbSPY)); SymbolData sd = symbolDataBySymbol[thisSymbol]; if(doTracing) Log($" --- ---- ***** ADDING {thisSymbol.Value} to SDBS with option {opt.Symbol.Value}"); } //else if (doDeepTracing) Debug("--- --- " + SecAddedCnt.ToString() + " Option Chains " + thisSymbol.Value + " added after Universe changes."); } foreach(var security in changes.RemovedSecurities) { // if(doTracing) Debug("--- --- --- Removing Symbol: " + security.ToString()); if (security.Type == SecurityType.Equity & !Portfolio[security.Symbol].Invested) { if (symbolDataBySymbol.ContainsKey(security.Symbol)) { RemoveSecurity(symbolDataBySymbol[security.Symbol].optSymbol); //symbolDataBySymbol.Remove(security.Symbol); } } } } // ********************** OnOrderEvent *********************************************** // **************************************************************************************************** public override void OnOrderEvent(OrderEvent orderEvent) { var order = Transactions.GetOrderById(orderEvent.OrderId); var oeSymb = orderEvent.Symbol; try { if (orderEvent.Status == OrderStatus.Filled) { //var order = Transactions.GetOrderById(orderEvent.OrderId); //var oeSymb = orderEvent.Symbol; if (order.Type == OrderType.OptionExercise) { if (doDeepTracing) Log(" 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 if (doDeepTracing) Log(" OO " + orderEvent.UtcTime + " LEAN LEAN LEAN ASSIGNMENT ORDER EVENT LEAN LEAN LEAN OOOOOO"); if (doDeepTracing) Log(" OO LEAN ASSIGNMENT SYMBOL: " + oeSymb ); if (oeSymb.HasUnderlying && oeSymb.ID.OptionRight == OptionRight.Call) { sellThePut = true; } } if (doDeepTracing) Log(" OO Quantity: " + orderEvent.FillQuantity + ", price: " + orderEvent.FillPrice); if (oeSymb.HasUnderlying) { /// /// /// THIS IS AN OPTION EXERCISE ORDER didTheTrade = true; var thisOption = (Option)Securities[oeSymb]; var stkSymbol = thisOption.Underlying.Symbol; if (doDeepTracing) Log(" OO OPTIONS EXERCISE ORDER FOR : " + oeSymb + " IS A " + (oeSymb.ID.OptionRight == OptionRight.Put ? "PUT. " : "CALL.") + "for underlying: " + stkSymbol.Value); if(oeSymb.ID.OptionRight == OptionRight.Put) { if (doDeepTracing) Log(" oo PUT OPTION EXERCISE ORDER FOR : " + oeSymb); foreach (TradePerfRec pTPR in tradeRecs){ //Debug($" OO TPR OO TPR OO {tpr.uSymbol.Value} is {(tpr.isOpen ? "open" : "closed")} and has {tpr.cQty.ToString()} calls."); if (pTPR.pSymbol.Equals(oeSymb) & pTPR.pQty == -order.Quantity) { if (doDeepTracing) Log(" oo oo FOUND SHORT PUT 1ST TPR oo "); pTPR.uEndPrice = oeSymb.ID.StrikePrice; /// set the TPR underlying end price pTPR.endDate = orderEvent.UtcTime; if (pTPR.reasonForClose !=null || pTPR.reasonForClose != "") { pTPR.reasonForClose = pTPR.reasonForClose + " PUT OPTIONS ASSIGNMENT -- UNDERLYING CLOSED"; } else pTPR.reasonForClose = pTPR.reasonForClose + " PUT OPTIONS ASSIGNMENT -- UNDERLYING CLOSED"; if (doDeepTracing) Log($" OO OO UPDATED {oeSymb.Value} END PRICE TO : {orderEvent.FillPrice}."); if (pTPR.cSymbol != null && pTPR.cEndPrice != 0) { var shrtCall = (Option)Securities[pTPR.cSymbol]; TimeSpan daysToCallExpiry = shrtCall.Expiry.Subtract(orderEvent.UtcTime); if (doDeepTracing) Log(" OO OO OO SELLING THE CALL IF IT EXISTS"); var closeCTkt = MarketOrder(pTPR.cSymbol, -pTPR.cQty); if (closeCTkt.Status == OrderStatus.Filled) { pTPR.cEndPrice = closeCTkt.AverageFillPrice; } //} } if (doDeepTracing) Log(" OO SELLING THE WING CALL IF IT EXISTS"); if (pTPR.wcSymbol != null && pTPR.wcEndPrice != 0) { //var wingCall = (Option)Securities[pTPR.wcSymbol]; var closeWingTkt = MarketOrder(pTPR.wcSymbol, -pTPR.wcQty); if (closeWingTkt.Status == OrderStatus.Filled) { pTPR.wcEndPrice = closeWingTkt.AverageFillPrice; } } } } } else if (oeSymb.ID.OptionRight == OptionRight.Call){ if (doDeepTracing) Log(" oo CALL OPTION EXERCISE ORDER FOR : " + oeSymb); foreach (TradePerfRec cTPR in tradeRecs){ if (cTPR.cSymbol.Equals(oeSymb) & cTPR.cQty == -order.Quantity) { if (doDeepTracing) Log(" oo oo FOUND LONG CALL 1ST TPR oo "); cTPR.cEndPrice = orderEvent.FillPrice; if (doDeepTracing) Log($" oo oo oo UPDATED 1ST TPR SHORT CALL {oeSymb.Value} END PRICE TO : {orderEvent.FillPrice}."); cTPR.uEndPrice = oeSymb.ID.StrikePrice; /// set the TPR underlying end price cTPR.endDate = orderEvent.UtcTime; if (cTPR.reasonForClose !=null || cTPR.reasonForClose != "") { cTPR.reasonForClose = cTPR.reasonForClose + " CALL OPTIONS ASSIGNMENT -- UNDERLYING CLOSED"; } else cTPR.reasonForClose = cTPR.reasonForClose + " CALL OPTIONS ASSIGNMENT -- UNDERLYING CLOSED"; if(symbolDataBySymbol.ContainsKey(cTPR.uSymbol) ){ symbolDataBySymbol[cTPR.uSymbol].isRollable = false; SymbolsToRemove.Add(cTPR.uSymbol); } if (doDeepTracing) Log($" oo oo oo UPDATED 1ST SHORT CALL TPR uEndPrice {oeSymb.Underlying.Value} END PRICE TO : {cTPR.cStrike}."); if (cTPR.pSymbol != null) { var longPut = (Option)Securities[cTPR.pSymbol]; if (doDeepTracing) Log(" oo 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]; if (doDeepTracing) Log(" oo oo oo SELLING THE WING CALL IF IT EXISTS OR HASN'T BEEN BOUGHT"); if (cTPR.wcEndPrice != 0) { if (doDeepTracing) Log(" 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 if (doDeepTracing) Log(" oo oo oo oo THE WING CALL WAS ALREADY SOLD"); } tprsToClose.Add(cTPR); } } //// THE FOLLOWING WOULD EXECUTE IF ALGO EXERCISED THE WING CALL -- NOT CONTEMPLATED } } else { /// !.HasUnderlying -- this is stock being assigned if (doDeepTracing) Log(" oo ASSIGNMENT OF UNDERLYING ORDER FOR : " + oeSymb); if (doDeepTracing) Log(" oo STOCK EXERCISE ORDER EVENT FOR: " + order.Quantity + " shares." ); didTheTrade = true; if(tradeRecs.Any(t => t!=null && t.uSymbol.Equals(oeSymb) & Math.Abs(t.uQty) == Math.Abs(order.Quantity*100M))) { /// this failed on 2/6/23 to find tpr // 2023-04-09: USE MATH.ABS() TO FIND QTY if (doDeepTracing) Log($" oo UPDATING TPR -- UNDERLYING END PRICE AND DATE {orderEvent.FillPrice.ToString()}"); var uTPR = tradeRecs.Where(t => t!=null && t.uSymbol.Equals(oeSymb) & Math.Abs(t.uQty) == Math.Abs(order.Quantity*100M)).FirstOrDefault(); tradeRecCount = 0; // reset tradeRec Counter ??? may be obviated uTPR.uEndPrice = orderEvent.FillPrice; uTPR.endDate = orderEvent.UtcTime; if (uTPR.reasonForClose !=null || uTPR.reasonForClose != "") { uTPR.reasonForClose = uTPR.reasonForClose + " oo oo OPTIONS ASSIGNMENT -- UNDERLYING CLOSED IN NOT_HAS_UNDERLYING"; } else uTPR.reasonForClose = " oo oo OPTIONS ASSIGNMENT -- UNDERLYING CLOSED IN notHASUNDERLYING"; if(symbolDataBySymbol.ContainsKey(oeSymb)) { if (doDeepTracing) Log($" oo oo oo FOUND {oeSymb.Value} in SDBS. Setting isRollable to False and marking it for removal from prospects list."); symbolDataBySymbol[oeSymb].isRollable = false; SymbolsToRemove.Add(oeSymb); } tprsToClose.Add(uTPR); } else { if (doDeepTracing) Log($" oo oo oo oo => FAILED TO LOCATE {oeSymb.Value} TPR THAT HAS {(100M* order.Quantity).ToString()} SHARES. "); } } if (doDeepTracing) Log(" ---------------------------------------------------------------------------"); } // Order.Type = OrderType.OptionExercise else { if (doDeepTracing) Log(" OO ** ** NON EXERCISE ORDER -- " + oeSymb); if (doDeepTracing) Log(" OO ** ** " + order.Type + ": " + orderEvent.UtcTime + ": " + orderEvent.Direction + " ** OO "); if (doDeepTracing) Log(" OO ** ** " + orderEvent.Status + ": " + orderEvent.Direction + " " + order.Quantity + " @ " + orderEvent.FillPrice ); if (oeSymb.HasUnderlying && order.Type == OrderType.Limit ) { /// Option if (oeSymb.ID.OptionRight == OptionRight.Put) { if (doDeepTracing) Log(" OO PUT OPTION LIMIT ORDER FOR : " + oeSymb); if (doDeepTracing) Log(" OO PROCESSING TPR IN NEXT ON DATA oo oo oo oo "); foreach (TradePerfRec pTPR in tradeRecs){ //Debug($" OO TPR OO TPR OO {tpr.uSymbol.Value} is {(tpr.isOpen ? "open" : "closed")} and has {tpr.cQty.ToString()} calls."); if (pTPR.pSymbol.Equals(oeSymb) & pTPR.pQty == -order.Quantity) { if (doDeepTracing) Log(" oo oo FOUND SHORT PUT 1ST TPR oo "); pTPR.pEndPrice = orderEvent.FillPrice; if (doDeepTracing) Log(" OO ** Setting pEndPrice."); } } } else if (oeSymb.ID.OptionRight == OptionRight.Call) { if (doDeepTracing) Log(" OO CALL OPTION LIMIT ORDER FOR : " + oeSymb); if (doDeepTracing) Log(" OO PROCESSING TPR IN NEXT ON DATA oo oo oo oo "); foreach (TradePerfRec cTPR in tradeRecs){ if (cTPR.cSymbol.Equals(oeSymb) & cTPR.cQty == -order.Quantity) { cTPR.cEndPrice = orderEvent.FillPrice; if (doDeepTracing) Log(" OO ** Setting cEndPrice."); } } //if (doDeepTracing) Log(" 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) { if (doDeepTracing) Log(" OO PUT OPTION MARKET ORDER FOR : " + oeSymb); if (doDeepTracing) Log(" OO PROCESSING TPR SYNCHRONOUSLY IN LINE oo oo oo "); /*if (tradeRecs.Any(tpr => tpr!=null && tpr.pSymbol.Equals(oeSymb) && tpr.pQty == -order.Quantity )){ TradePerfRec tpRec = tradeRecs.Where(tpr => tpr!=null && tpr.pSymbol.Equals(oeSymb) && tpr.pQty == -order.Quantity).FirstOrDefault(); tpRec.pEndPrice = orderEvent.FillPrice; } */ if (doDeepTracing) Log(" OO NOTE ALGO-DRIVEN PUT market order"); } else if (oeSymb.ID.OptionRight == OptionRight.Call) { if (doDeepTracing) Log(" OO CALL OPTION MARKET ORDER FOR : " + oeSymb); if (doDeepTracing) Log(" OO PROCESSING TPR SYNCHRONOUSLY IN LINE oo oo oo "); /*if (tradeRecs.Any(tpr => tpr!=null && tpr.cSymbol.Equals(oeSymb) && tpr.cQty == -order.Quantity )){ TradePerfRec tpRec = tradeRecs.Where(tpr => tpr!=null && tpr.cSymbol.Equals(oeSymb) && tpr.cQty == -order.Quantity).FirstOrDefault(); tpRec.cEndPrice = orderEvent.FillPrice; } */ if (doDeepTracing) Log(" OO NOTE ALGO-DRIVEN CALL MARKET ORDER"); } } else if (!oeSymb.HasUnderlying) { // limit order if (doDeepTracing) Log($" OO UNDERLYING ORDER FOR : {oeSymb.Value} IN NON-EXERCISE."); } else { // NON EXERCISE ORDER DOES NOT HAVE UNDERLYING == STOCK if (doDeepTracing) Log(" OO UNKNOWN ALGO ORDER ORDER FOR : " + oeSymb); foreach (TradePerfRec uTPR in tradeRecs){ if ( uTPR.uSymbol.Equals(oeSymb) & uTPR.uQty == -100M*order.Quantity) { Debug (" OO ** THERE IS A TPR THAT IS " + (uTPR.isOpen ? " OPEN" : " CLOSED")); uTPR.isOpen = false; uTPR.uEndPrice = orderEvent.FillPrice; uTPR.endDate = orderEvent.UtcTime; uTPR.reasonForClose = uTPR.reasonForClose + ": Options Expiration next day onOpen"; } //Plot("Stock Chart", "Sells", orderEvent.FillPrice); } } if (doDeepTracing) Log(" ---------------------------------------------------------------------------"); } // non exercise option order } // orderStatus = Filled } catch (Exception errMsg) { if (doTracing) Log(" ERROR in OnOrder() Event " + errMsg ); return; } } class StockDataSource : BaseData { // *** ::: short VE 1&2 MSSQL generated TotalLoss >= $8 or >=10% LOSS //private const string BacktestUrl = @"https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/EbJeQsE8A85PmKRwhcKvKgIBc6VfGe_vqmBQU259KVzLKQ?e=nJbEdP&download=1"; // *** ::: short VE 1&2 Corrected Date and Number @Parameters in SQL TotalLoss >= $10 //private const string BacktestUrl = @"https://hyperionhedgefundcom-my.sharepoint.com/:t:/g/personal/crmcwilliams_hldfund_com/EfWMhBW7TChIt9R6J1EaN-ABGnisGKSF2nFUVZqLaNBAwg?e=ZCy6SK&download=1"; // *** ::: short VE 1&2 Corrected Date and Number @Parameters in SQL TotalLoss >= $8 //private const string BacktestUrl = @"https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/EfSdJTFsPFNFoBaEdAWCBVEB9yzr1b96i95gtk1lus8ueA?e=pdovae&download=1"; // *** ::: Charley's manual selection //private const string BacktestUrl = @"https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/Ef7B98ZT-WBFq2FqDdKYsUIB22hMN1GAbg2Sqv33J5t4Zw?e=MtKP92&download=1"; // *** ::: -- Standardized monthly FDOM //private const string BacktestUrl = @"https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/ESFpfDn8miBDnAuwOiS2R0MB4sQTc-d5x2PF813KYeaniw?e=kQ7pOG&download=1"; // *** ::: Price>$90 loss>10pct private const string BacktestUrl = @"https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/EeMhiQJs8_JFh0-BLeHvwokBgDm4fw2ouAEM8f9fcw1Vyg?e=UCtbPE&download=1"; // *** ::: Price$90 loss>15pct //private const string BacktestUrl = @"https://hyperionhedgefundcom-my.sharepoint.com/:x:/g/personal/crmcwilliams_hldfund_com/EYBLXTBWzKtCqe8P8h1CnWcBpMpTS5Sic4NZgEF3UuzfLg?e=W8Wato&download=1"; /// <summary> /// The symbols to be selected /// </summary> public List<string> Symbols { get; set; } public StockDataSource() { // initialize our list to empty Symbols = new List<string>(); } public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode) { //var url = isLiveMode ? LiveUrl : BacktestUrl; var url = BacktestUrl; return new SubscriptionDataSource(url, SubscriptionTransportMedium.RemoteFile); } public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode) { try { // create a new StockDataSource and set the symbol using config.Symbol var stocks = new StockDataSource {Symbol = config.Symbol}; // break our line into csv pieces char[] charsToTrim = {',', ' '}; line = line.TrimEnd(charsToTrim); var csv = line.ToCsv(); if (isLiveMode) { // our live mode format does not have a date in the first column stocks.Time = date; stocks.Symbols.AddRange(csv); } else { if(strFilterTkr != ""){ if(!ourUniverse.Securities.ContainsKey(strFilterTkr) ){ stocks.Time = date; stocks.Symbols.Add(strFilterTkr); } } else { // our backtest mode format has the first column as date, parse it stocks.Time = DateTime.Parse(csv[0]); stocks.Symbols.AddRange(csv.Skip(1)); //foreach (var smbl in stocks.Symbols) { //} } } return stocks; } // return null if we encounter any errors //catch { return null; } catch (Exception eMsg) { var msg = eMsg; return null; } } } public override void OnEndOfAlgorithm() { Debug($" -- ON END OF ALGORITHM !!"); var dailyBars = ""; string strObj = ""; string strIndName = ""; string strIndValue = ""; String ProjectID = this.ProjectId.ToString(); String AlgoID = this.AlgorithmId.ToString(); ObjectStore.Save("AlgoID", AlgoID); ObjectStore.Save("ProjectID", ProjectID); Log($" |||| |||| LOGGING THE HOLDINGS ON END OF ALGORITHM"); foreach(var kvp in Securities) { var security = kvp.Value; if (security.Invested) { Log($" |||| |||| |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } foreach(var pair in symbolDataBySymbol){ //Debug($" Processing SDBS for {pair.Key}"); Symbol symb = pair.Key; SymbolData sd = pair.Value; if(strFilterTkr != ""){ foreach (var indyWinKVP in sd.IndicatorsWindows) { /// Dictionary<string, RollingWindow<IndicatorDataPoint>> var indyDataPointsWin = indyWinKVP.Value.Reverse(); /// RollingWindow<IndicatorDataPoint> strObj = ""; strIndName = indyWinKVP.Key; // Log($" -- -- Processing SDBS Indicator: {indyWinKVP.Key}"); foreach(IndicatorDataPoint indyDataPoint in indyDataPointsWin){ strObj += $"{indyDataPoint.Time.ToString("yyyy-M-d")}, {indyDataPoint.Value.ToString("0.00")} \n"; // Log($" -- -- -- {indyDataPoint.Time}, {indyDataPoint.Value.ToString("0.00")}"); } ObjectStore.Save(pair.Key + "_" + strIndName, strObj); // Debug($" ObjStore Value saved: {strObj}"); //var ObjStore = ObjectStore.ToReadOnlyDictionary(); //foreach(var kvp in ObjStore){ // Debug($" ObjStore : {kvp.Key}"); //} } } try{ if (sd.VDIData != null) { string columns = string.Join(", ", sd.VDIData.Columns.Cast<System.Data.DataColumn>().Select(column => column.ColumnName)); //Log($"Columns: {columns}"); // Log each row of the dataTable foreach (System.Data.DataRow row in sd.VDIData.Rows) { string rowData = string.Join(", ", row.ItemArray); //Log($"Row: {rowData}"); } } } catch (Exception e) { if (doTracing) Log($" -- --- Exception: {e}"); } } var ObjStore = ObjectStore.ToReadOnlyDictionary(); /* Debug($" -- -- Writing out Object Store Values -- --"); foreach(var kvp in ObjStore){ //Debug($" ObjStore : {kvp.Key}"); } */ foreach(var kvp in Securities) { var security = kvp.Value; if (security.Invested) { Debug($" |||| |||| |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } 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.uSymbol != null) { if (doDeepTracing) Log($" --- --- --- Setting End Prices for {tpr.uSymbol.Value} to {Securities[tpr.uSymbol].Price}. "); if (Securities[tpr.uSymbol].HasData & Securities[tpr.uSymbol].Price != 0) tpr.uEndPrice = Securities[tpr.uSymbol].Price; } if (tpr.pEndPrice == 0 && tpr.pSymbol != null) { if (Securities[tpr.pSymbol].HasData & Securities[tpr.pSymbol].Price != 0) tpr.pEndPrice = Securities[tpr.pSymbol].Price; } if (tpr.cEndPrice == 0 && tpr.cSymbol != null) { if (Securities[tpr.cSymbol].HasData & Securities[tpr.cSymbol].Price != 0) tpr.cEndPrice = Securities[tpr.cSymbol].Price; } tpr.endDate = Time; } } string jsonString = ConvertTradePerfRec(tradeRecs); tprEnum = ETFRecs.GetEnumerator(); while (tprEnum.MoveNext()) { TradePerfRec tpr = tprEnum.Current; if (tpr.isOpen) { if (tpr.uEndPrice == 0 && tpr.uSymbol != null) { if (doDeepTracing) Log($" --- --- --- Setting End Prices for {tpr.uSymbol.Value} to {Securities[tpr.uSymbol].Price}. "); if(Securities[tpr.uSymbol].HasData & Securities[tpr.uSymbol].Price != 0) tpr.uEndPrice = Securities[tpr.uSymbol].Price; } if (tpr.pEndPrice == 0 && tpr.pSymbol != null) { if(Securities[tpr.pSymbol].HasData & Securities[tpr.pSymbol].Price != 0) tpr.pEndPrice = Securities[tpr.pSymbol].Price; } if (tpr.cEndPrice == 0 && tpr.cSymbol != null) { if(Securities[tpr.cSymbol].HasData & Securities[tpr.cSymbol].Price != 0) tpr.cEndPrice = Securities[tpr.cSymbol].Price; } tpr.endDate = Time; } } //jsonString = ConvertTradePerfRec(ETFRecs); } private bool CheckBadDate(DateTime checkDate) { if (!badDtParameter) return badDtParameter; DateTime badDate1 = Convert.ToDateTime(GetParameter("BadDate")); DateTime badDate2 = Convert.ToDateTime(GetParameter("BadDate2")); DateTime badDate3 = Convert.ToDateTime(GetParameter("BadDate3")); //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) | checkDate.Equals(badDate3)) { return true; } else { return false; } } } // class } // namespace
#region imports using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Util; using QuantConnect.Data; using QuantConnect.Securities; using QuantConnect.Securities.Option; #endregion 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 // 1. Get a) tPRec.pSymbol // b) Strike and // c) old bidPrice // 2. Get Stock Price and tPRec.uStartPrice -- calculate appreciation // 3. Get sdbs.decOneYearPriceTarget_initial // 4. is a) current price > 1yrTarget -- What is VERank now? Is initial 1 year more than 2 months // b) current price < 1yrTarget -- What is 1yrTarget now? 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 LUD.thisFFRate = LUD.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 highStrike = 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 = (LUD.thisFFRate + LUD.ibkrRateAdj)/LUD.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; } //decimal currPutBidPrice = algo.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 ((this.pStartPrice - currPutBidPrice) > (this.uStartPrice + this.pStartPrice - this.cStartPrice) ) { if (LUD.doTracing) algo.Log(" 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, -this.cQty); if (closeCallTicket.Status == OrderStatus.Filled) { this.cEndPrice = closeCallTicket.AverageFillPrice; } var putExerciseTicket = ExerciseOption(longPutSymbol, this.pQty); potentialCollars.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log(" ************** END ITM PUT CALC -- EXERCISED PUTS ******"); return isRolled; } */ //bool goodThresh = (LUD.intVERating == 5 & LUD.decOneYearPriceTarget > 1.05m * stockPrice) | (LUD.intVERating > 3 & bestSSQRColumn.upsidePotential >=5); } }
#region imports #endregion 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 /// 2021-10-17 Moved all CheckRoll.cs code for evaluating and processing rolls based upon expirations and options-monieness into TradePerfRec class. /// Then, in Main.cs OnData() the list of 1st TPR's are iterated and processed by calling tpr.CheckRoll() method. /*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); } } */ // ********************** IsFirstTradingDay ****************************************** // *** Generalized function to find and return a DateTime for a given year, month, DayOfWeek // *** and occurrence in the month. In this case, it's the 3rd Friday // *** // ******************************************************************************************** /* public bool IsFirstTradingDay(DateTime testDate) { if (haltProcessing) { Debug("--- --- Logging IsFirstTradingDay() " + testDate.ToString()); } if (testDate.DayOfWeek == DayOfWeek.Sunday | testDate.DayOfWeek == DayOfWeek.Saturday) return false; DateTime firstDayOfMonth = new DateTime(testDate.Year, testDate.Month, 1); while (USHoliday.Dates.Contains(firstDayOfMonth)) firstDayOfMonth = firstDayOfMonth.AddDays(1); while (firstDayOfMonth.DayOfWeek == DayOfWeek.Sunday || firstDayOfMonth.DayOfWeek == DayOfWeek.Saturday) firstDayOfMonth = firstDayOfMonth.AddDays(1); while (USHoliday.Dates.Contains(firstDayOfMonth)) firstDayOfMonth = firstDayOfMonth.AddDays(1); ///Debug("First Day of Month is " + firstDayOfMonth.ToString()); if (testDate.Month.Equals(firstDayOfMonth.Month) && testDate.Day.Equals(firstDayOfMonth.Day)) {return true; } else {return false;} } */ }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion using QuantConnect.Securities.Option; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public class SSQRColumn { public decimal stockPrice = 0; public DateTime exDate = DateTime.Now; public DateTime putExpiry = DateTime.Now; public DateTime callExpiry = DateTime.Now; public int daysInPosition = 0; public decimal interestCost = 0; public Symbol uSymbol; public Symbol putSymbol; public Symbol callSymbol; public Symbol wCallSymbol; public decimal putPremium = 0; // paid for buying the body public decimal callPremium = 0; // received for selling back call public decimal wCallPremium = 0; // paid for buying the wings public decimal putStrike = 0; public decimal callStrike = 0; public decimal wCallStrike = 0; public decimal putOpenInterest = 0; public decimal callOpenInterest = 0; public decimal putDelta = 0; public decimal callDelta = 0; public decimal wcDelta = 0; public decimal wingFactor = 0; public decimal putGamma = 0; public decimal callGamma = 0; public decimal wcGamma = 0; public decimal putVega = 0; public decimal callVega = 0; public decimal putRho = 0; public decimal callRho = 0; public decimal putTheta = 0; public decimal callTheta = 0; public decimal putImpliedVol = 0; public decimal callImpliedVol = 0; public decimal divAmt = 0; public int divCount = 0; public decimal downsideRisk = 0; public decimal upsidePotential = 0; public decimal netIncome = 0; public decimal netOptions = 0; public decimal divDollars = 0; public decimal haircut = 0; // committed capital in a portfolio margin account public decimal ROC = 0; // Return on Capital public decimal ROR = 0; // Return on Risk public decimal CCOR = 0; // Call Coverage over downside Risk public int intVERating; // This month's VE Rating public decimal decMomentum; // This month's VE momentum public decimal decOneMonthForecat; // VE One Month Forecast public decimal decOneYearPriceTarget; // VE One Year Target public int intMomentumRank; // VE Momentum Rank public string description1 = ""; public string description2 = ""; //public string description3; public override string ToString() { return this.description1; } public bool IsEmpty() { return this.description1.IsNullOrEmpty(); } } } }
#region imports using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Util; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Securities; using QuantConnect.Securities.Option; #endregion ///////////////////////////// 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 Method will assemble the calls and puts into separate List<OptionContract> // *** Here the VE Ranking will determine the composition and ultimate selection of the SSQR // *** 5 - Highest probability and appreciation --> Use lower put (-3 strikes) because less probability of individual downside so reduce cost // *** Also use shorter duration call OTM to offset Put cost // *** 4 - Lower but postive probability of appreciation --> Use -2 stike put to protect individual downside and write OTM calls with same expiration // *** 3 - Neutral probability of appreciation --> tighten collar --> experiment with ITM call // *** 2 - Probably will decline in price in 12 monmths --> Don't do these stocks // *** 1 - Highest probability to decline in value -- >> don't do these stocks // *********************************************************************************** public void AssembleSSQRMatrix(QCAlgorithm algo, ref LookupData LD, Dictionary<int, DateTime> putExpiries, Dictionary<int, DateTime> callExpiries) { int i = 1; if (LD.doTracing) algo.Log($" -- AA AA ASSEMBLE SSQR MATRIX FOR {thisSymbol}"); Symbol symbU = LD.uSymbol; int strikesCnt = 0; decimal strikeStep = 0; decimal estTrgtPutStrk = 0; decimal estTrgtCallStrk = 0; decimal stockPrice = 0; //List<OptionChain> allUnderlyingOptions = new List<OptionChain>(); // chain object to get all options //allUnderlyingOptions = thisSlice.OptionChains.Values.Where(u => u.Underlying.Symbol.Equals(symbU)).ToList(); OptionChain allUnderlyingOptions = null; // chain opbjec to get all contracts OptionChain putChain; // chain object to get put contracts OptionChain callChain; // chain object to get call contracts OptionChain wcChain; // chain object to get wc contracts OptionChain atmChain; // chain object to ATM call List<OptionContract> putContracts = new List<OptionContract>(); List<OptionContract> callContracts = new List<OptionContract>(); List<OptionContract> wCallContracts = new List<OptionContract>(); OptionContract putContract; // contract object to collect put greeks OptionContract callContract; // contract object to collect call greeks //OptionContract wcContract; // contract object to collect wing call greeks Greeks putGreeks; Greeks callGreeks; Greeks wcGreeks; Slice thisSlice = algo.CurrentSlice; DateTime tradeDate = thisSlice.Time; // current date, presumed date of trade SSQRColumn thisSSQRColumn = new SSQRColumn(); stockPrice = algo.Securities[symbU].Price; if(LD.doTracing) algo.Log("@@@@@ logging assembleSSQR processing for: " + symbU.ToString() + " Price in Securities object is " + stockPrice.ToString()); // if(!thisSlice.OptionChains.TryGetValue(SD.optSymbol, out allUnderlyingOptions)) return; /// NOTE: DOES NOT RETURN wcChain // var gotChain = thisSlice.OptionChains.TryGetValue(symbU.opt, out var thisChain); // if (!gotChain) {return;} // allUnderlyingOptions = thisChain; foreach(var chain in thisSlice.OptionChains.Values){ if (chain.Underlying.Symbol != symbU) { continue; } allUnderlyingOptions = chain; break; } if (allUnderlyingOptions == null) { if (LD.doTracing) algo.Debug("@@@@@ @@ No options returned at " + thisSlice.Time + " for " + symbU.Value); return; // return null SSQRMatrix and pass control back to OnData() } // Get the ATM call contract var atmCall = allUnderlyingOptions.Where(s => s.Right == OptionRight.Call) .OrderBy(s => Math.Abs(stockPrice - s.Strike))/// - stockPrice)) .FirstOrDefault(); var atmPut = allUnderlyingOptions.Where(s => s.Right == OptionRight.Put) .OrderBy(s => Math.Abs(stockPrice - s.Strike)) /// - stockPrice)) .FirstOrDefault(); var atmStrike = atmCall.Strike; if (atmStrike == 0 ) { return;} var firstITMCallStrike = allUnderlyingOptions.Where(s => s.Right == OptionRight.Call & s.Strike < stockPrice) .OrderByDescending(s => s.Strike - stockPrice)/// - stockPrice)) .FirstOrDefault().Strike; var lowestOTMPutStrike = allUnderlyingOptions.Where(s => s.Right == OptionRight.Put & s.Strike < stockPrice) .OrderByDescending(s => s.Strike - stockPrice)/// - stockPrice)) .FirstOrDefault().Strike; // var lowStrike = (1 - ((decimal)LD.maxPutOTM / (decimal)100)) * atmStrike; // ~~ eventually need a mechanism to determine strike steps var lowStrike = allUnderlyingOptions.Where(s => s.Right == OptionRight.Put) .OrderByDescending(s => (stockPrice - s.Strike)) /// - stockPrice)) .FirstOrDefault().Strike; //var highStrike = (decimal)1.1 * atmStrike; // ~~ and use strike steps to set upper and lower bounds var highStrike = allUnderlyingOptions.Where(s => s.Right == OptionRight.Call) .OrderByDescending(s => (s.Strike - stockPrice))/// - stockPrice)) .FirstOrDefault().Strike; // get the distinct strikes in a list to get a count. With the count, and the range, get the strike steps. var strikesList = allUnderlyingOptions.Where( o=> (DateTime.Compare(o.Expiry, callExpiries[1])==0)).DistinctBy(o => o.Strike); strikesCnt = strikesList.Count(); if (strikesCnt == 1){ strikeStep = (decimal)highStrike - (decimal)lowStrike; } else { strikeStep = ((decimal)highStrike - (decimal)lowStrike)/((decimal)strikesCnt - 1M); } if (strikeStep % 0.5m != 0) strikeStep = Math.Round(strikeStep/0.5m) * 0.5m; 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>(); //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 LD.thisFFRate = LD.fedFundsRates[justDate]; // fedFundsRates is a Dictionary where DateTime index are all 12:00:00am if (stockPrice <= 100m & strikeStep == 5) strikeStep = 2.5m; /// make adjustment to standardize options placement if (strikeStep <= 1.5m) strikeStep = 2m; /// make adjustment to widen unusual case of $1 strike steps decimal VEDepPct = 0; /// IF THIS IS POST INITIALIZATION, IS THE ONE YEAR TARGET THE SAME, ABOVE OR BELOW THE INITIAL TARGET ??? ??? ??? ??? if (symbolDataBySymbol.ContainsKey(symbU)){ if (symbolDataBySymbol[symbU].decOneYearPriceTarget_Initial > LD.decOneYearPriceTarget){ symbolDataBySymbol[symbU].decOneYearPriceTarget_Current = LD.decOneYearPriceTarget; VEDepPct = (stockPrice - LD.decOneYearPriceTarget) / stockPrice; } else { VEDepPct = (stockPrice - symbolDataBySymbol[symbU].decOneYearPriceTarget_Initial) / stockPrice; LD.initialTargetEndDate = symbolDataBySymbol[symbU].initialTargetEndDate; } } if (VEDepPct < 0 && symbolDataBySymbol[symbU].continueTrade) VEDepPct = .10m; /// Depreciation is positive when the decOneYearPrice target is lower /* var distinctExpirations = allUnderlyingOptions.Select(contract => contract.Expiry).Distinct(); foreach(var thingy in distinctExpirations){ algo.Log($" --- --- --- Option Expiry: {thingy.ToShortDateString()}"); } */ if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ -- Assembling {symbU.Value} SSQRs using VEDepPct : {stockPrice.ToString("0.00")} - {LD.decOneYearPriceTarget.ToString()} = {VEDepPct.ToString("P2")} | StrikeStep: {strikeStep.ToString()}."); if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ -- High -- Low -- ATM-C -- ITM-C --"); if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ -- {highStrike.ToString("0.00").PadLeft(4,' ')} -- {lowStrike.ToString("0.00").PadLeft(4,' ')} -- {atmStrike.ToString("0.00").PadLeft(4,' ')} -- {firstITMCallStrike.ToString("0.00").PadLeft(4,' ')} --"); //switch (LD.intVERating) { switch (VEDepPct){ case var _ when VEDepPct <= .03M: // Stock is predicted to gain value, do a bull collar, write ITM Put, but its Total YLD including Dividends is negative and ranked in monthly file, or this is a roll LD.VECase = "Case -1"; // **** **** **** /// **** **** **** *** /// 2023-03-15 prohibited ITM call writing //estTrgtCallStrk = atmStrike - strikeStep; //estTrgtPutStrk = atmStrike - 1m * strikeStep; estTrgtPutStrk = atmStrike - strikeStep; estTrgtCallStrk = atmStrike + strikeStep; if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ @@@@ -- Case 1: {VEDepPct.ToString("P2")} less than 3%. Call target: {estTrgtCallStrk.ToString("0.00")} / Put Target: {estTrgtPutStrk.ToString("0.00")} "); callContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Call && DateTime.Compare(o.Expiry, tradeDate.AddMonths(3))<=0 & // Shorts take only a few weeks to work. //DateTime.Compare(o.Expiry, tradeDate.AddMonths(2))>=0 & o.Strike >= estTrgtCallStrk) //o.Strike > stockPrice) .OrderByDescending(o=>o.Expiry) .ThenBy(o => o.Strike) .ToList(); putContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Put && DateTime.Compare(o.Expiry, tradeDate.AddMonths(3))<=0 & // get close, but don't exceed 1 year target. Get as much put premium (theta) as possible by staying close to stock price //DateTime.Compare(o.Expiry, tradeDate.AddMonths(2))>=0 & o.Strike < stockPrice) .OrderByDescending(o=>o.Expiry) .ThenByDescending(o => o.Strike) .ToList(); break; case var _ when VEDepPct > .03M & VEDepPct <=.08M: // Stock is predicted to lose some value. Do a standard collar, place put as close to VEDepPct as possible. LD.VECase = "Case -2"; estTrgtPutStrk = LUD.decOneYearPriceTarget + strikeStep; // OneYearPriceTarget is below stock price. Short a put just above that target to capture decline estTrgtCallStrk = atmStrike + strikeStep; // Buy a call 1 strike step above stock price. if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ @@@@ -- Case 2: {VEDepPct.ToString("P2")} grtr betrween 3-8%. Call target: {estTrgtCallStrk.ToString("0.00")} / Put Target: {estTrgtPutStrk.ToString("0.00")} "); callContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Call && DateTime.Compare(o.Expiry, tradeDate.AddMonths(3))<=0 & // Try getting options out to 3 months or more. Get as much call premium (theta) as possible //DateTime.Compare(o.Expiry, tradeDate.AddMonths(2))>=0 & o.Strike >= estTrgtCallStrk) .OrderByDescending(o=>o.Expiry) .ThenBy(o => o.Strike) .ToList(); putContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Put && DateTime.Compare(o.Expiry, tradeDate.AddMonths(3))<=0 & // Try getting options out to 3 months or more. Get as much call premium (theta) as possible //DateTime.Compare(o.Expiry, tradeDate.AddMonths(2))>=0 & //o.Strike < stockPrice) o.Strike <= estTrgtPutStrk) .OrderByDescending(o=>o.Expiry) .ThenByDescending(o => o.Strike) .ToList(); break; case var _ when VEDepPct > .08M: LD.VECase = "Case -3"; estTrgtCallStrk = atmStrike + 2m * strikeStep; // /// //// DO NOT WRITE A CALL WHEN INITIALIZING estTrgtPutStrk = LUD.decOneYearPriceTarget + 2M * strikeStep; // /// //// 2023-05-02 :: changed from - to +. if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ @@@@ -- Case 3: {VEDepPct.ToString("P2")} grtr than 8%. PUT target: --no put --/ CALL Target: {estTrgtCallStrk.ToString("0.00")} "); callContracts = allUnderlyingOptions.Where( o=> DateTime.Compare(o.Expiry, justDate.AddMonths(3))<=0 & //DateTime.Compare(o.Expiry, tradeDate.AddMonths(2))>=0 & o.Strike >= estTrgtCallStrk & o.Right == OptionRight.Call) .OrderByDescending(o => o.Expiry) .ThenBy(o => o.Strike) .ToList(); //.FirstOrDefault(); putContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Put && DateTime.Compare(o.Expiry, tradeDate.AddMonths(3))<=0 & // Try getting options out to 3 months or more. Get as much call premium (theta) as possible //DateTime.Compare(o.Expiry, tradeDate.AddMonths(2))>=0 & //o.Strike < stockPrice) o.Strike <= estTrgtPutStrk) .OrderByDescending(o=>o.Expiry) .ThenByDescending(o => o.Strike) .ToList(); break; } /// end switch // /// /// COLLAR PROCESSING BEGINS HERE // /// /// /// if (putContracts == null | putContracts.Count() == 0) // **************** Check if any puts are returned. { if (LD.doTracing) algo.Debug($"@@@@@ -- @@@@ -- @@@@ get putContracts failed new paradigm in VE Rank {LUD.VECase}. Exit AssembleSSQRMatrices Method."); /// use the expiries[1] date as the seed and find the subsequent 4 3-month expirations return; } if( callContracts == null | callContracts.Count() == 0) /// **************** Check if no calls are returned. If not, try incrementing 1 month for VERating 4 & 5 or decrementing for 3's { // if (LD.haltProcessing) { if (LD.doTracing) algo.Debug($"@@@@@ -- @@@@ -- @@@@ get CALL contracts failed 1st Pass try using +3 mos expiries in VE Case {LUD.VECase} ---------------"); if (LD.doTracing) algo.Debug("--" + stockPrice.ToString() +", " + callExpiries[1].ToString("MM/dd/yy") + ", " + callExpiries[2].ToString("MM/dd/yy") + ", " + callExpiries[3].ToString("MM/dd/yy") + ", " + callExpiries[4].ToString("MM/dd/yy") + ", " + callExpiries[5].ToString("MM/dd/yy")); //if (doDeepTracing);(callSymbolsForThisExpiry, "callSymbols"); // } putContracts = allUnderlyingOptions.Where( o=> o.Right==OptionRight.Put && ( DateTime.Compare(o.Expiry, putExpiries[2])==0 | DateTime.Compare(o.Expiry, putExpiries[3]) == 0) & o.Strike <= estTrgtPutStrk) .OrderByDescending(o=>o.Expiry) .ThenByDescending(o => o.Strike) .ToList(); callContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Call && ( DateTime.Compare(o.Expiry, putExpiries[2])==0 | DateTime.Compare(o.Expiry, putExpiries[3])==0) & o.Strike > estTrgtCallStrk) //o.Strike > stockPrice) .OrderByDescending(o=>o.Expiry) .ThenBy(o => o.Strike) .ToList(); if (putContracts == null || putContracts.Count() == 0) { if (LD.doTracing) algo.Debug("@@@@@ -- @@@@ -- @@@@ PUT Expiries Failed 2nd Pass try every month -----------------"); if (LD.doTracing) algo.Debug("--" + stockPrice.ToString() +", " + putExpiries[1].ToString("MM/dd/yy") + ", " + putExpiries[2].ToString("MM/dd/yy") + ", " + putExpiries[3].ToString("MM/dd/yy") + ", " + putExpiries[4].ToString("MM/dd/yy") + ", " + putExpiries[5].ToString("MM/dd/yy")); putExpiries[1] = FindNextOptionsExpiry(putExpiries[1], symbU.Value, -1); /// //// //// *** *** *** *** used this for first time in 6F.... changed results putExpiries[2] = FindNextOptionsExpiry(putExpiries[1], symbU.Value, 1); putExpiries[3] = FindNextOptionsExpiry(putExpiries[1], symbU.Value, 2); //putExpiries[4] = FindNextOptionsExpiry(putExpiries[1], 3); //putExpiries[5] = FindNextOptionsExpiry(putExpiries[1], 5); //putExpiries[6] = FindNextOptionsExpiry(putExpiries[1], 6); // putExpiries[7] = FindNextOptionsExpiry(putExpiries[1], 7); // putExpiries[8] = FindNextOptionsExpiry(putExpiries[1], 8); putContracts = allUnderlyingOptions.Where( o=> o.Right==OptionRight.Put && ( DateTime.Compare(o.Expiry, putExpiries[2])==0 | DateTime.Compare(o.Expiry, putExpiries[3])==0 ) & o.Strike <= estTrgtPutStrk) .OrderByDescending(o=>o.Expiry) .ThenByDescending(o => o.Strike) .ToList(); callContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Call && ( DateTime.Compare(o.Expiry, putExpiries[2])==0 | DateTime.Compare(o.Expiry, putExpiries[3])==0) & //o.Strike <= estTrgtCallStrk o.Strike > stockPrice) .OrderByDescending(o=>o.Expiry) .ThenBy(o => Math.Abs(estTrgtCallStrk - o.Strike)) .ToList(); if (putContracts == null || putContracts.Count() == 0) { if (LD.doTracing) algo.Debug("@@@@@ -- @@@@ -- @@@@ PUT Expiries Failed 3rd Pass return out -----------------"); if (LD.doTracing) algo.Debug("--" + stockPrice.ToString() +", " + putExpiries[1].ToString("MM/dd/yy") + ", " + putExpiries[2].ToString("MM/dd/yy") + ", " + putExpiries[3].ToString("MM/dd/yy") + ", " + putExpiries[4].ToString("MM/dd/yy") + ", " + putExpiries[5].ToString("MM/dd/yy")); foreach(var opt in allUnderlyingOptions) { Debug($"@@@@@ @@@@@ @@@@@ {opt.ToString()}"); } return; } else { if (LD.doTracing) algo.Debug("---------------------- PUT Expiries Succeeded on 3rd Pass -----------------"); } } else { if (LD.doTracing) algo.Debug("---------------------- PUT Expiries Succeeded on 2nd Pass -----------------"); } } if (LD.doTracing) Debug("@@@@@ -- @@@@@ -- @@@@@ -- get putSymbolsForTheseExpiries succeeded. -- @@@@@ -- @@@@@ -- @@@@@ -- "); var pEnumerator = putContracts.GetEnumerator(); while (pEnumerator.MoveNext()) { var cEnumerator = callContracts.GetEnumerator(); putContract = pEnumerator.Current; //if (LD.doDeepTracing) algo.Debug($" ---- ---- ---- : {putContract.ToString()} target strike: {estTrgtPutStrk.ToString()}."); OptionContract wcContract = atmCall; while (cEnumerator.MoveNext()) { callContract = cEnumerator.Current; //if (LD.doDeepTracing) algo.Debug($" ---- ---- ---- ---- {callContract.ToString()}"); // if ((callContract.Strike > putContract.Strike & DateTime.Compare(callContract.Expiry, putContract.Expiry)>=0) | (callContract.Strike >= putContract.Strike & DateTime.Compare(callContract.Expiry,putContract.Expiry)>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) if (callContract.Strike > putContract.Strike & DateTime.Compare(callContract.Expiry, putContract.Expiry)==0) { //foreach (var wcContract in wCallContracts) { //if (wcContract.Strike > callContract.Strike ) { //thisSSQRColumn = buildSSQRColumn(putContract, callContract, wcContract, algo, LD); thisSSQRColumn = buildSSQRColumn(putContract, callContract, algo, LD); //} //} if (thisSSQRColumn != null) LD.SSQRMatrix.Add(thisSSQRColumn); } // if thisCallStrike == thisPutStrike } // while callEnum } // while putEnum if (LD.doTracing) Debug($" @@@@@ -- AA AA RETURNED {LD.SSQRMatrix.Count()} SSQR MATRICES FOR {LD.uSymbol}" ); // if (LD.doDeepTracing) { // var orderedSSQRMatrix = LUD.SSQRMatrix.OrderByDescending(p => p.upsidePotential); // IterateOrderedSSQRMatrix(orderedSSQRMatrix); // } return; } // AssembleSSQRMatrix // ********************** buildSSQRColumn 4-parms VE 4 and 5 ************************************** // *** This sub routine takes in the variables for the iterated put Options Lists // *** as well as the dividends count, dividend amount, and stock price // *** and returns an SSQRColumn to be added to the SSQRMatrix list // *************************************************************************************************** public SSQRColumn buildSSQRColumn(OptionContract perkPutOpt, OptionContract thisCallOpt, QCAlgorithm algo, LookupData LD) //public SSQRColumn buildSSQRColumn(Option perkPutOpt, 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 int monthsInTrade = 0; int daysInTrade = 0; int dividends = 0; Slice thisSlice = algo.CurrentSlice; // LD.loadVEData(thisSlice.Time); // load this instance of LUD with VE data from file. decimal stockPrice = algo.Securities[LD.uSymbol].Price; SSQRColumn thisColumn = new SSQRColumn(); // get a new SSQRColumn if (thisCallOpt == null) return thisColumn; // 2023-02-10 -- trap null thisPutOpt -- crashed in new differentiated VE model if (thisCallOpt.AskPrice == 0) return thisColumn; // don't build SSQRColumns with missing premium values DateTime tradeDate = algo.CurrentSlice.Time; daysInTrade = (thisCallOpt.Expiry - tradeDate).Days; decimal intCost = (LD.thisFFRate + LD.ibkrRateAdj)/LD.workingDays * (decimal) daysInTrade * stockPrice; if (perkPutOpt!=null){ //if (LUD.doDeepTracing) algo.Log($" BSSQR BSSQR - Logging 4-parameter buildSSQRColumn processing for {thisCallOpt.Symbol.Value}/{perkPutOpt.Symbol.Value}."); } else { //if (LUD.doDeepTracing) algo.Log($" BSSQR BSSQR - Logging 4-parameter buildSSQRColumn processing for {thisCallOpt.Symbol.Value}/--no put --."); } monthsInTrade = thisCallOpt.Expiry.Month - LD.exDivdnDate.Month; if( thisCallOpt.Expiry.Year != LD.exDivdnDate.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.uSymbol = LD.uSymbol; thisColumn.putSymbol = perkPutOpt != null ? perkPutOpt.Symbol : null; // thisColumn.wCallSymbol = wcOpt.Symbol; // atm call for this column (based upon put) thisColumn.putPremium = perkPutOpt != null ? perkPutOpt.BidPrice : 0; thisColumn.exDate = LD.exDivdnDate; thisColumn.putExpiry = perkPutOpt != null ? perkPutOpt.Expiry : default(DateTime); thisColumn.putStrike = perkPutOpt != null ? perkPutOpt.Strike : 0; thisColumn.putDelta = perkPutOpt != null ? perkPutOpt.Greeks.Delta : 0; // thisColumn.wcDelta = wcOpt.Greeks.Delta; thisColumn.putGamma = perkPutOpt != null ? perkPutOpt.Greeks.Gamma : 0; thisColumn.putImpliedVol = perkPutOpt != null ? perkPutOpt.ImpliedVolatility : 0; thisColumn.callSymbol = thisCallOpt.Symbol; thisColumn.callPremium = thisCallOpt.AskPrice; thisColumn.callExpiry = thisCallOpt.Expiry; thisColumn.callStrike = thisCallOpt.Strike; thisColumn.callDelta = thisCallOpt.Greeks.Delta; thisColumn.callGamma = thisCallOpt.Greeks.Gamma; thisColumn.callImpliedVol = thisCallOpt.ImpliedVolatility; //thisColumn.wcGamma = wcOpt.Greeks.Gamma; //thisColumn.putVega = thisPutOpt.Greeks.Vega; //thisColumn.callVega = thisCallOpt.Greeks.Vega; //thisColumn.putRho = thisPutOpt.Greeks.Rho; //thisColumn.callRho = thisCallOpt.Greeks.Rho; //thisColumn.putTheta = thisPutOpt.Greeks.Theta; //thisColumn.callTheta = thisCallOpt.Greeks.Theta; thisColumn.divAmt = LD.divdndAmt; thisColumn.divCount = dividends; thisColumn.stockPrice = stockPrice; thisColumn.daysInPosition = daysInTrade; thisColumn.interestCost = intCost; thisColumn.intVERating = LD.intVERating; thisColumn.decMomentum = LD.decMomentum; thisColumn.decOneMonthForecat = LD.decOneMonthForecast; thisColumn.decOneYearPriceTarget = LD.decOneYearPriceTarget; thisColumn.intMomentumRank = LD.intMomentumRank; thisSpread = Math.Truncate(thisCallOpt.Strike - stockPrice); if (!LD.ibkrHairCuts.ContainsKey( (thisSpread)) ) { //Debug("*^*^*^*^*^*^*^*^*^*^**^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*"); //Debug("Make a haircut entry for " + (thisCallStrike - thisPutStrike).ToString()); //Debug("*^*^*^*^*^*^*^*^*^*^**^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*"); if (thisSpread < 5M) { thisColumn.haircut = .5M; } else { if (thisSpread % 0.5m != 0) thisSpread = Math.Round(thisSpread/0.5m) * 0.5m; if(thisSpread > 35m) thisSpread = 35; thisColumn.haircut = LD.ibkrHairCuts[thisSpread]; } }else { thisColumn.haircut = 35m; } decimal divDollars = -LD.divdndAmt * dividends; // dividends are negative. We pay dividends when we short. thisColumn.divDollars = divDollars; decimal stockLossIfCalled = 0; // loss=0 if stkPrice>putStrike, otherwise negative ***Loss (negative value) if ITM calls are assigned (0 if #calls<#puts) decimal perkPutPremium = 0; if (perkPutOpt != null) perkPutPremium = perkPutOpt.BidPrice; decimal netOptions = -thisColumn.callPremium + thisColumn.putPremium; /// netOptions is a cost and equals negative callPrem (expense) plus positive put 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 if (wingFactor < 0) wingFactor = 0; if (wingFactor > 0.2M ) wingFactor = 0.2M; //thisColumn.wingFactor = wingFactor; thisColumn.wingFactor = 0; // for VE statistical analysis, set WingFactor to 0. Don't do wings 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 - thisColumn.netOptions) < thisColumn.callStrike) ? thisColumn.callStrike - stockPrice + netOptions - thisColumn.interestCost + divDollars: thisColumn.interestCost + divDollars + netOptions; // downside risk is the net price of the collar - putStrike (deliberately discounts dividends as they are not guaranteed past the declared dividend) thisColumn.downsideRisk = downsideRisk; // subtracts dividends collected and net options premiums and intCost decimal upsidePotential = 0; if(perkPutOpt!=null) { upsidePotential = LD.decOneYearPriceTarget > perkPutOpt.Strike ? stockPrice - LD.decOneYearPriceTarget + divDollars + netOptions - intCost : stockPrice - perkPutOpt.Strike + divDollars + netOptions - intCost ; // When writing OTM puts, there is a potential upside appreciation from net collar cost to the put strike. } else { upsidePotential = stockPrice - LD.decOneYearPriceTarget + divDollars + netOptions - intCost; // When writing OTM puts, there is a potential upside appreciation from net collar cost to the put strike. } thisColumn.upsidePotential = upsidePotential; // 2021-03-24 -- -- changed sign on downsideRisk from negative to positive. Earlier iterations represented downside risk as negative (putStrike - stock purchase price). thisColumn.ROR = downsideRisk == 0 ? upsidePotential : upsidePotential/downsideRisk; // store ROR for statistical analysis /*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 " + LD.uSymbol + " @ " + stockPrice + " is the " + thisColumn.putStrike + "/" + thisColumn.callStrike + " collar "; /* thisColumn.description2 = thisColumn.uSymbol.Value; thisColumn.description2 = stockPrice.ToString(); thisColumn.description2 = LD.exDivdnDate.ToString(); thisColumn.description2 = thisColumn.putExpiry.ToString(); thisColumn.description2 = thisColumn.callExpiry.ToString(); thisColumn.description2 = thisColumn.putStrike.ToString(); thisColumn.description2 = thisColumn.putPremium.ToString(); thisColumn.description2 = thisColumn.callStrike.ToString(); thisColumn.description2 = thisColumn.callPremium.ToString(); thisColumn.description2 = thisColumn.putDelta.ToString(); thisColumn.description2 = thisColumn.callDelta.ToString(); thisColumn.description2 = thisColumn.wingFactor.ToString(); */ try{ thisColumn.description2 = "," + thisColumn.uSymbol.Value + "," + String.Format("{0:0.00}", stockPrice) + "," + LD.exDivdnDate.ToString("MM/dd/yy") + "," + dividends + "," + String.Format("{0:0.00}", LD.divdndAmt) + "," + String.Format("{0:0.00}",divDollars) + "," + daysInTrade + ", " + String.Format("{0:0.00}", intCost) + ", " + thisColumn.putExpiry.ToString("MM/dd/yy") + ", " + thisColumn.callExpiry.ToString("MM/dd/yy") + ", " + String.Format("{0:0.00}",thisColumn.putStrike) + ", " + String.Format("{0:0.00}",thisColumn.putPremium) + ", " + String.Format("{0:0.00}",thisColumn.callStrike) + ", " + String.Format("{0:0.00}",thisColumn.callPremium) + ", " + "-no wingcall-" + ", " + "-no wingcall-" + ", " + String.Format("{0:0.00}",thisColumn.putDelta) + ", " + String.Format("{0:0.00}",thisColumn.callDelta) + ", " + String.Format("{0:0.00}",thisColumn.netOptions) + ", " + String.Format("{0:0.00}", thisColumn.netIncome) + ", " + String.Format("{0:0.00}",thisColumn.intVERating) + ", " + String.Format("{0:0.00}",thisColumn.decMomentum) + ", " + String.Format("{0:0.00}",thisColumn.decOneYearPriceTarget) + ", " + String.Format("{0:0.00}", thisColumn.haircut) + ", " + String.Format("{0:0.00}",thisColumn.ROC) + "," + String.Format("{0:0.00}", thisColumn.upsidePotential) + "," + String.Format("{0:0.00}", thisColumn.downsideRisk) + "," + String.Format("{0:0.00}",thisColumn.ROR) + "," + String.Format("{0:0.00}", thisColumn.CCOR ) + "," + "-no wingcall-," + (thisColumn.putSymbol!=null ? thisColumn.putSymbol.Value : "-no put-") + "," + (thisColumn.callSymbol!=null ? thisColumn.callSymbol.Value : "-no call-") + "," + String.Format("{0:0.00}", thisColumn.decOneYearPriceTarget); } catch (Exception msg) { Debug($" @@@@@ -- @@@@@ -- Description2 has some issue: {msg}"); } return thisColumn; } // ********************** 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 // *** Target next 3 Ex-Datas whether 1st is Slice.Time.Month or later. // *********************************************************************************** public Dictionary<int, DateTime> GetOptionExpiries(DateTime tradeD, LookupData lookupD, DateTime thisMonthExpiry, bool isPrimary, bool isCall){ // Initialize expiration date variables // DateTime nextExDate = lookupD.exDivdnDate; String tkr = lookupD.uSymbol.Value; DateTime firstExpiry = new DateTime(); DateTime secondExpiry = new DateTime(); DateTime thirdExpiry = new DateTime(); DateTime fourthExpiry = new DateTime(); DateTime fifthExpiry = new DateTime(); DateTime sixthExpiry = new DateTime(); // DateTime seventhExpiry = new DateTime(); // DateTime eigthExpiry = new DateTime(); // DateTime ninthExpiry = new DateTime(); // DateTime tenthExpiry = new DateTime(); // DateTime eleventhExpiry = new DateTime(); // DateTime twelvethExpiry = new DateTime(); // DateTime thirteenthExpiry = 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 (!isCall & isPrimary) // isPrimary ? 1stTPR : 2ndTPR 1stTPR do monthly options every quarter : 2ndTPR do monthly options every month { if (DateTime.Compare(nextExDate, thisMonthExpiry) <= 0) { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 0); // first figure out the options expiry for exDivDate month if (firstExpiry.Subtract(tradeD).Days <= 10) { // if firstExpiry is less than 10 days after tradeDate, assignment risk is too high. Move expiries back a month firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 1); secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 4); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 7); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 10); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 13); } else { secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 3); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 6); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 9); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 12); } } else { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 1); secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 4); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 7); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 10); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 13); } } else { // this is for 2ndTPRs -- monthly options every month to catch some if (DateTime.Compare(nextExDate, thisMonthExpiry) <= 0) { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 0); secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 1); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 2); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 3); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 4); sixthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 5); // seventhExpiry = FindNextOptionsExpiry(thisMonthExpiry, 6); // eigthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 7); // ninthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 8); // tenthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 9); // eleventhExpiry = FindNextOptionsExpiry(thisMonthExpiry, 10); // twelvethExpiry= FindNextOptionsExpiry(thisMonthExpiry, 11); // thirteenthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 12); }else { firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 1); secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 2); thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 3); fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 4); fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 5); sixthExpiry = FindNextOptionsExpiry(thisMonthExpiry, tkr, 6); // seventhExpiry = FindNextOptionsExpiry(thisMonthExpiry, 7); // eigthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 8); // ninthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 9); // tenthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 10); // eleventhExpiry = FindNextOptionsExpiry(thisMonthExpiry, 11); // twelvethExpiry= FindNextOptionsExpiry(thisMonthExpiry, 12); // thirteenthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 13); } } expiries.Add(1, firstExpiry); expiries.Add(2, secondExpiry); expiries.Add(3, thirdExpiry); expiries.Add(4, fourthExpiry); expiries.Add(5, fifthExpiry); expiries.Add(6, sixthExpiry); // expiries.Add(7, seventhExpiry); // expiries.Add(8, eigthExpiry); if (isCall) { // expiries.Add(9, ninthExpiry); // expiries.Add(10, tenthExpiry); // expiries.Add(11, eleventhExpiry); // expiries.Add(12, twelvethExpiry); // expiries.Add(13, thirteenthExpiry); } 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, string tkr, 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; } // adjust if month = 0 if(month + addedMonths == 0) { month = 12; } else { month = month + addedMonths; } // Adjust if bigger than 12 if(month > 12){ month = month % 12; year = year + 1; } if (haltProcessing) { Debug("--- --- Logging 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 var securityExchangeHours = Securities[tkr].Exchange.Hours; while(!securityExchangeHours.IsDateOpen(findDate)) findDate = securityExchangeHours.GetPreviousTradingDay(findDate); return findDate; } // ********************** FindDay (options expiry) *************************************** // *** Generalized function to find and return a DateTime for a given year, month, DayOfWeek // *** and occurrence in the month. In this case, it's the 3rd Friday // *** // ******************************************************************************************** public DateTime FindDay(int year, int month, DayOfWeek Day, int occurrence) { if (haltProcessing) { //Debug("--- --- Logging 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}"); } } // ********************** IsLastTradingDay ****************************************** // *** Generalized function to find and return a DateTime for a given year, month, DayOfWeek // *** and occurrence in the month. // ******************************************************************************************** public bool IsLastTradingDay(DateTime testDate) { DateTime nextTestDate = testDate.AddMonths((1)); DateTime lastDayOfMonth = new DateTime(nextTestDate.Year, nextTestDate.Month, 1).AddDays(-1); var securityExchangeHours = Securities["SPY"].Exchange.Hours; while(!securityExchangeHours.IsDateOpen(lastDayOfMonth)) lastDayOfMonth = securityExchangeHours.GetPreviousTradingDay(lastDayOfMonth); ///Debug("First Day of Month is " + lastDayOfMonth.ToString()); if (testDate.Month.Equals(lastDayOfMonth.Month) && testDate.Day.Equals(lastDayOfMonth.Day)) {return true; } else {return false;} } public bool IsFirstTradingDay(DateTime testDate, string testTicker) { DateTime firstDayOfMonth = new DateTime(testDate.Year, testDate.Month, 1); var securityExchangeHours = Securities[testTicker].Exchange.Hours; while(!securityExchangeHours.IsDateOpen(firstDayOfMonth)) firstDayOfMonth = securityExchangeHours.GetNextTradingDay(firstDayOfMonth); return testDate.Day == firstDayOfMonth.Day; } // ********************** Get Last Options Expiry Day ************************************ // *** Generalized function to find and return a DateTime for a given year, month, DayOfWeek // *** and occurrence in the month. // ******************************************************************************************** public DateTime GetLastOptionsExpiry(DateTime testDate) { DateTime nextTestDate = testDate.AddMonths((1)); DateTime lastDayOfMonth = new DateTime(nextTestDate.Year, nextTestDate.Month, 1).AddDays(-1); var securityExchangeHours = Securities["SPY"].Exchange.Hours; while(!securityExchangeHours.IsDateOpen(lastDayOfMonth)) lastDayOfMonth = securityExchangeHours.GetPreviousTradingDay(lastDayOfMonth); ///Debug("First Day of Month is " + lastDayOfMonth.ToString()); return lastDayOfMonth; } // ********************** 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 list of chains, " + 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(" ---------------------------------------------------------------------------------------------"); } // ********************** 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 30 31 32 33 Debug(",Ticker,Stock Price,Ex-Date,# Dividends,Dividend,Dollars,Days In,Interest,PExpiry, CExpiry, PutStrike, PutBid, CallStrike, CallAsk, wCStrike, wCallAsk, PutDelta, CallDelta, NetOptions,Net Income,VE Rating, VE Momentum, VE 1 Yr, Haircut,ROC,Upside,Downside,ROR,CCOR, wingFactor, PutSymb, CallSymb, OneYearTarget"); foreach (SSQRColumn thisColumn in thisOrdMatrix) { //Debug("Iterated " + k + " times"); Debug(thisColumn.description2); //Debug(" "); k++; if (k == 31) 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; } } } }
#region imports using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Util; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Orders; using QuantConnect.Securities; using QuantConnect.Securities.Option; #endregion 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; //////////////////////////////////////////////////////////////////////////////////// // RollCriterium //////////////////////////////////////////////////////////////////////////////////// /// Securities[tpr.uSymbol].Price < 100 & tpr.uStartPrice-Securities[tpr.uSymbol].Price>=5m) | (Securities[tpr.uSymbol].Price>=100 & Securities[tpr.uSymbol].Price<200 & tpr.uStartPrice-Securities[tpr.uSymbol].Price>=10m) | (Securities[tpr.uSymbol].Price>=200 & tpr.uStartPrice-Securities[tpr.uSymbol].Price>=15m)) )) public bool RollCriterium(decimal startPrice, decimal currPrice) { decimal diffy = startPrice - currPrice; if (diffy >= 0) return false; switch (currPrice) { case <= 100: if (diffy >= 5) return true; else return false; break; case > 100 and <=200: if (diffy >= 10) return true; else return false; break; case > 200 and <=300: if (diffy >= 20) return true; else return false; break; case > 300 : if (diffy >= 25) return true; else return false; break; default: return false; break; } } //////////////////////////////////////////////////////////////////////////////////// // ExecuteTrade //////////////////////////////////////////////////////////////////////////////////// public bool ExecuteTrade(Slice data, SSQRColumn bestSSQRColumn, ref SymbolData symbData, LookupData LD) { thisCCOR = bestSSQRColumn.CCOR; decimal maxWingFactor = 0; decimal thisWingFactor = 0; decimal wingPremium = 0; decimal thisNetOptions = bestSSQRColumn.netOptions; Log($" TD TD TD TD **** LUD check symbol is {LD.uSymbol.Value} and OneYearPriceTarget={LD.decOneYearPriceTarget.ToString("0.00")}"); if (haltProcessing) { //if (doDeepTracing) Log(" Logging ExecuteTheTrade() "); } //goodThresh = (thisCCOR >= CCORThresh); goodThresh = true; if (goodThresh) { sharesToBuy = Math.Truncate(LUD.unitRiskAmt/(bestSSQRColumn.callStrike - bestSSQRColumn.stockPrice - bestSSQRColumn.netOptions)); // use +NetOptions. netOptions is calculated as an expense in the SSQR potential SSQRColumn.P&L sharesToBuy = sharesToBuy - sharesToBuy % 100; sharesToBuy = -sharesToBuy; optionsToTrade = sharesToBuy/100; //callsToTrade = Decimal.Round(optionsToTrade * bestSSQRColumn.putPremium / bestSSQRColumn.callPremium); /// legacy VCCPTS code //Log(tradableColumn.ToString()); Symbol tradablePut = bestSSQRColumn.putSymbol; Symbol tradableCall = bestSSQRColumn.callSymbol; Symbol tradableWCall = bestSSQRColumn.wCallSymbol; if (bestSSQRColumn.putSymbol!=null && bestSSQRColumn.putStrike - Securities[tradablePut].AskPrice > stockPrice) // make sure that no one can buy the option for less than the stock { if (doDeepTracing) Log($"@E@E@E@E@E@E EXERCISE PREVENTION FADE FOR {bestSSQRColumn.uSymbol} @E@E@E@E@E@E"); if (doDeepTracing) Log("@E@E@E@E@E@E Put ASK: " + Securities[tradablePut].AskPrice + " Strike: " + bestSSQRColumn.putStrike + " Stock Price: " + stockPrice +" @E@E@E@E@E@E"); if (doDeepTracing) Log("@E@E@E@E@E@E @E@E@E@E@E@E @E@E@E@E@E@E @E@E@E@E@E@E"); return false; } if (doTracing) Log($"@E@E@E@E@E@E EXECUTING COVERED CALL INITIALIZATION FOR {bestSSQRColumn.uSymbol} @E@E@E@E@E@E with {sharesToBuy.ToString()} shares and {optionsToTrade.ToString()}."); //tradeRecCount = tradeRecCount + 1; // increment trade record count symbData.intTPRCntr += 1; //collarIndex = collarIndex + 1; doTheTrade = true; var stockTicket = MarketOrder(bestSSQRColumn.uSymbol, sharesToBuy); if (stockTicket.Status == OrderStatus.Filled) { didTheTrade = true; //if (!string.IsNullOrEmpty(strFilterTkr)) Plot("Stock Chart", "Buys", stockTicket.AverageFillPrice + 5); // make a new TradePerfRec TradePerfRec thisNewCollar = new TradePerfRec(); thisNewCollar.strtngCndtn = "INITIAL COLLAR: " + " SBB " + symbData.bbTrigger.ToString() + " VDI " + symbData.VDI_Signal.ToString() + " TSI " + symbData.tsi_trigger.ToString() + " ALMA " + symbData.almas_crossing.ToString(); thisNewCollar.isOpen = true; thisNewCollar.isInitializer = true; thisNewCollar.tradeRecCount = collarIndex; thisNewCollar.index = symbData.intTPRCntr;; thisNewCollar.startDate = data.Time; thisNewCollar.expDate = bestSSQRColumn.callExpiry; thisNewCollar.thetaExpiration = bestSSQRColumn.callExpiry; thisNewCollar.uSymbol = bestSSQRColumn.uSymbol; 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 = bestSSQRColumn.ROR; thisNewCollar.ROC = bestSSQRColumn.ROC; thisNewCollar.CCOR = bestSSQRColumn.CCOR; thisNewCollar.RORThresh = RORThresh; thisNewCollar.ROCThresh = ROCThresh; thisNewCollar.CCORThresh = CCORThresh; //thisNewCollar.tradeCriteria = switchROC ? "ROC" : "ROR"; thisNewCollar.tradeCriteria = symbData.VECase; //thisNewCollar.stockADX = 0; //lastAdx; //thisNewCollar.stockADXR = 0; //lastAdxr; //thisNewCollar.stockOBV = 0; //lastObv; //thisNewCollar.stockAD = lastAd; //thisNewCollar.stockADOSC = lastAdOsc; //thisNewCollar.stockSTO = lastSto; //thisNewCollar.stockVariance = lastVariance; thisNewCollar.SSQRnetProfit = stockTicket.QuantityFilled * bestSSQRColumn.netIncome; thisNewCollar.VERating = LD.intVERating; thisNewCollar.momentum = LD.decMomentum; thisNewCollar.oneYearPriceTarget = LD.decOneYearPriceTarget; thisNewCollar.momentumRank = LD.intMomentumRank; doTheTrade = true; if(bestSSQRColumn.callSymbol!=null){ if (thisNewCollar.cStrike < thisNewCollar.uStartPrice) { var limitPrice = Securities[tradableCall].BidPrice + ((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; } } } //if(bestSSQRColumn.wCallSymbol!=null) thisWingFactor = bestSSQRColumn.wingFactor; //var putTicket = MarketOrder(tradablePut, (1 + thisWingFactor) * optionsToTrade); if(bestSSQRColumn.putSymbol!=null) { if (thisNewCollar.pStrike > thisNewCollar.uStartPrice) { var limitPrice = Securities[tradablePut].BidPrice + ((Securities[tradablePut].AskPrice - Securities[tradablePut].BidPrice) / 2M); // get the mid point for the limit price var putTicket = LimitOrder(tradablePut, -optionsToTrade, limitPrice); // sell limit order thisNewCollar.pQty = -(int)optionsToTrade; OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = putTicket; oLO.tpr = thisNewCollar; oLO.oRight = OptionRight.Put; oLOs.Add(oLO); } else { var putTicket = MarketOrder(tradablePut, 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); if (doTracing) Log("@E@E@E@E@E@E - ADDING A VE 3-4-5 TPR "); string sSBB = symbData.bbTrigger.ToString(); string sVDI = symbData.VDI_Signal.ToString(); string sTSI = symbData.tsi_trigger.ToString(); string sALMA = symbData.almas_crossing.ToString(); string reason = " SBB " + sSBB + " VDI " + sVDI + " TSI " + sTSI + " ALMA " + sALMA; Log($" -- --- --- INITIALIZATION TRIGGERS {reason}"); } // marketOrder(bestSSQRColumn.uSymbol) == filled } // goodThresh is TRUE return true; } /////////////////////////////////////////////////////////////////////////////////// // Close2ndTPR //////////////////////////////////////////////////////////////////////////////////// public void Close2ndTPR (TradePerfRec closeRec, DateTime closeDate, string reason) { decimal limitPrice = 0; if (haltProcessing) { //Log(" Logging Close2ndTPR "); } doTheTrade = true; var stockTicket = MarketOrder(closeRec.uSymbol, -closeRec.uQty); // sell the stock //if (doDeepTracing) Debug(" C2 ** MARKET ORDER TO SELL " + closeRec.uQty.ToString() + " shares of " + closeRec.uSymbol + " at the market."); //if (doDeepTracing) Log(" C2 ** C2 ** STARTING CLOSE2ndTPR PROCESSING ** C2 ** C2 "); //if (doDeepTracing) Log(" -- "); if (doDeepTracing) { 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; //Log($" |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } } if (stockTicket.Status == OrderStatus.Filled) { //closeRec.isOpen = false; tprsToClose.Add(closeRec); closeRec.uEndPrice = stockTicket.AverageFillPrice; //if (symbFilter != null) Plot("Stock Chart", "Sells", stockTicket.AverageFillPrice + 1); //if (symbFilter != null) Plot("Stock Chart", "PTSs", divPlotValue); tradeRecCount = 0; // reset trade record count } doTheTrade = true; closePutTicket = MarketOrder(closeRec.pSymbol, -closeRec.pQty); // sell the puts if (closePutTicket.Status == OrderStatus.Filled) { closeRec.pEndPrice = closePutTicket.AverageFillPrice; } closeRec.reasonForClose = reason; closeRec.endDate = closeDate; // set the end date of this collar //if (doDeepTracing) Log(" C2 ** C2 ** C2 ** C2 ** CLOSED 2nd TPR ** C2 ** C2 ** C2 ** C2 ** C2 ** "); //if (doDeepTracing) Log("-"); } /////////////////////////////////////////////////////////////////////////////////// // KillTheCollar //////////////////////////////////////////////////////////////////////////////////// public bool KillTheCollar(TradePerfRec killRec, ref LookupData LUD, string reason, bool force, bool isStock) { bool bKTC = false; // controls Main.cs foreach TPR routine -- exit the for loop if .isOpen is changed. decimal limitPrice = 0; //decimal currUPrice = Securities[killRec.uSymbol].Price; if (LUD.haltProcessing) { // Log(" logging kill rec on bad date"); } try { if (Securities[killRec.uSymbol].HasData) { var tryPrice = Securities[killRec.uSymbol].Price; if (tryPrice == null) { Debug($" KK ** KK ::: UNABLE TO FIND A STOCK PRICE IN PACKAGE KILL PROCESS FOR {killRec.uSymbol.Value}. "); return false; } decimal currUPrice = Convert.ToDecimal(tryPrice); killRec.uEndPrice = currUPrice; //2023-04-11 :: INSERTED THIS HERE TO FORCE uEndPrice } else { Debug($" KK ** KK ::: UNABLE TO FIND A STOCK DATA IN PACKAGE KILL PROCESS FOR {killRec.uSymbol.Value}. "); return false; } } catch (Exception excpt) { if (LUD.doTracing) Debug($" KK ** KK ** {excpt} for {killRec.uSymbol} at {CurrentSlice.Time.ToShortTimeString()} on {CurrentSlice.Time.ToShortDateString()}"); return bKTC; } 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 stockPrice = Securities[killRec.uSymbol].Price; if (doDeepTracing) Log($" KK ** STARTING KILLTHECOLLAR PROCESSING FOR {killRec.uSymbol} "); if (doDeepTracing) Log(" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); if (doDeepTracing & force) Log($" KK ** KK ** FORCING PACKAGE KILL "); if (force) goto noExercise; /* if (doDeepTracing) { 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; // Log($" |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } // Log($" |||| SELL OPTS P&L: " + String.Format("{0:0.00}", currSellPnL)); // Log($" |||| Exrcs PUT P&L: " + String.Format("{0:0.00}", currExrcsPutPnL)); // Log($" |||| Exrcs CALL P&L: " + String.Format("{0:0.00}", currExrcsCallPnL)); } */ doTheTrade = true; // determine if this is an ITM call or ITM put and within 1 day of expiry if (killRec.cSymbol != null && stockPrice >= callStrike && LUD.daysRemainingP <= 1) { /// ITM CALL -- Exercise or sell // determine if it's more expensive to sell or exercise ***** remember, killRec.cQty is negative for collars (sold calls) if (killRec.currExrcsCallPnL > killRec.currSellPnL) { // for an ITM PUT, both costs should be negative if (doDeepTracing) Log($" KK ** KK ** KK ** EXERCISING PUTS IN KILLTHECOLLAR FOR {thisSymbol}"); if (killRec.cSymbol != null) { // Exercise the CALLs. Let longer expiry calls ride to attempt theta decay -- create a "theta TPR" to track and manage call //var shrtCall = (Option)Securities[killRec.cSymbol]; //TimeSpan daysToCallExpiry = shrtCall.Expiry.Subtract(killDate); /*if (daysToCallExpiry.Days > 10 ) { Log(" 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 (doDeepTracing) Debug(" KK ** KK ** BUYING BACK SHORT PUTS IN KILLTHECOLLAR CALL EXERCISE ** KK ** KK "); if (killRec.pQty != 0){ closePutTicket = MarketOrder(killRec.pSymbol, -killRec.pQty); // buy the puts if (doDeepTracing) Log(" KK ** KK ** KK ** MARKET ORDER TO BUY " + killRec.pQty.ToString() + " contracts of " + killRec.pSymbol + " at the market."); if (doDeepTracing) Log("-"); if (closePutTicket.Status == OrderStatus.Filled) { killRec.pEndPrice = closePutTicket.AverageFillPrice; } } ///// killRec.cSymbol != null } //if (doDeepTracing) Log(" ------- "); //if (doDeepTracing) Log(" KK ** KK ** EXERCISING PUTS IN KILLTHECOLLAR ** KK ** KK "); closeCallTicket = ExerciseOption(killRec.cSymbol, killRec.cQty); /// underlying will be closed in onOrder() event killRec.grossPnL = killRec.currExrcsCallPnL; /// log the PnL used in runtime decision bestSSQRColumn = new SSQRColumn(); bKTC = true; return bKTC; } else { //// ITM PUT but more profitable to sell the collar if (doDeepTracing) Log(" KK ** KK ** ITM CALL MORE PROFITABLE TO SELL COLLAR THAN EXERCISE ** KK ** KK"); goto noExercise; } } else { /// ITM CALL ON LAST DAY -->> GET HERE IF CALL IS OTM OR THERE IS NO CALL if (doDeepTracing) Log(" KK ** KK ** OTM CALL -- CHECKING CALL MONEY ** KK ** KK "); } if (killRec.pSymbol != null && stockPrice <= putStrike && LUD.daysRemainingC <= 1) { if (doDeepTracing) Log(" KK ** KK ** CHECKING PUT STRATEGY P&L ** KK ** TT "); killRec.grossPnL = killRec.currExrcsPutPnL; // log the PnL used in runtime decision if (killRec.currExrcsPutPnL > killRec.currSellPnL) { // for an ITM CALL, both costs should be positive //if (doDeepTracing) Log(" KK ** KK ** EXIT KILLTHECOLLAR AND AWAIT CALL EXERCISE** KK ** TT "); if (doDeepTracing) Log(" KK ** KK ** KK ** ITM PUT AWAITING LEAN EXERCISE -- CLOSING POSITIONS IN OnOrder() Processing "); bKTC = true; return bKTC; } else { if (doDeepTracing) Log(" KK ** KK ** KK ** ITM PUT MORE PROFITABLE TO SELL COLLAR THAN EXERCISE"); goto noExercise; } } else { // ITM CAll and 3rd Friday if (doDeepTracing) Log(" KK ** KK ** KK ** OTM CALL -- POSITIONS IN KILLTHECOLLAR "); } //if OTM or it's less costly to execute orders, then do so here. if (doDeepTracing) Log(" KK ** KK ** KK ** KK ** OTM PUT AND CALL -- LIQUIDATE HERE IN KILLTHECOLLAR"); noExercise: var stockTicket = MarketOrder(killRec.uSymbol, -killRec.uQty); // sell the stock if (doDeepTracing) Log(" KK ** KK ** KK ** MARKET ORDER TO SELL " + killRec.uQty.ToString() + " shares of " + killRec.uSymbol + " at the market."); // Log the sale bKTC = true; if (stockTicket.Status == OrderStatus.Filled) { if (doDeepTracing) Log(" KK ** KK ** KK ** KK ** UPDATING TPR.U END PRICE AND SDBS.ISROLLABLE ** KK ** KK"); // Log the UPDATING /// add the killTPR to TPRS to close; tprsToClose.Add(killRec); killRec.uEndPrice = stockTicket.AverageFillPrice; string sSBB = symbolDataBySymbol[killRec.uSymbol].bbTrigger.ToString(); string sVDI = symbolDataBySymbol[killRec.uSymbol].VDI_Signal.ToString(); string sTSI = symbolDataBySymbol[killRec.uSymbol].tsi_trigger.ToString(); string sALMA = symbolDataBySymbol[killRec.uSymbol].almas_crossing.ToString(); reason = reason + " SBB " + sSBB + " VDI " + sVDI + " TSI " + sTSI + " ALMA " + sALMA; Log($" -- -- REASON FOR KILL: {reason}"); killRec.reasonForClose = reason; killRec.endDate = CurrentSlice.Time; // set the end date of this collar killRec.grossPnL = currSellPnL; // for logging and analysis of runtime conditions if (isStock) { symbolDataBySymbol[killRec.uSymbol].intTPRCntr = 0; //// reset the SYMBOL DATA COUNTER -- should be redundant symbolDataBySymbol[killRec.uSymbol].isRollable = false; //// 2023-02-08 Found that some orders are so delayed that the Symbol is not removed from SDBS //SymbolsToRemove.Add(killRec.uSymbol); //// 2024-01-01 Testing hypothesis to keep the SD in case it re-qualifies } else { etfDataBySymbol[killRec.uSymbol].intTPRCntr = 0; //// reset the SYMBOL DATA COUNTER -- should be redundant //etfDataBySymbol[killRec.uSymbol].isRollable = false; //// ETFs are always available to be traded based upon relative momentum //SymbolsToRemove.Add(killRec.uSymbol); //// ETFs are fixed and always prospects } //if (symbFilter != null) Plot("Stock Chart", "Sells", stockTicket.AverageFillPrice + 1); tradeRecCount = 0; // reset trade record count } doTheTrade = true; if (doDeepTracing) Log(" KK ** KK ** KK ** KK ** SOLD UNDERLYING -- WORKING OPTION ** KK ** KK ** KK ** KK ** KK ** "); if (killRec.pSymbol != null) { // Buy back any PUTS if possible var shrtPut = (Option)Securities[killRec.pSymbol]; if (doDeepTracing) Log(" KK ** Stock Price: " + stockPrice.ToString() + " Call Bid/Offer: " + Securities[killRec.pSymbol].BidPrice.ToString() + "/" + Securities[killRec.pSymbol].AskPrice.ToString()); // -- if dealing with theta TPR use LUD.daysRemainingC not *** //TimeSpan daysToCallExpiry = shrtCall.Expiry.Subtract LUD.dtTst); /*if (daysToCallExpiry.Days > 10 ) { Log(" 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.pStrike >= stockPrice) { /// ITM Put -- use limit order limitPrice = stockPrice - killRec.pStrike + 0.10M; killRec.pEndPrice = killRec.pStrike - stockPrice; /// /// 2023-04-23 ::: FORCED pEndPrice to Moneyness closePutTicket = LimitOrder(killRec.pSymbol, -killRec.pQty, limitPrice); OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closePutTicket; oLO.tpr = killRec; oLO.oRight = OptionRight.Put; oLOs.Add(oLO); if (doDeepTracing) Log(" KK ** LIMIT ORDER TO BUY TO CLOSE SHORT PUT " + killRec.pQty.ToString() + " contracts of " + killRec.pSymbol + " at " + limitPrice.ToString()); } else { closePutTicket = MarketOrder(killRec.pSymbol, -killRec.pQty); // buy the puts if (doDeepTracing) Log(" KK ** KK ** MARKET ORDER TO BUY TO CLOSE SHORT CALL" + killRec.pQty.ToString() + " contracts of " + killRec.pSymbol + " at the market."); if (closePutTicket.Status == OrderStatus.Filled) { killRec.pEndPrice = closePutTicket.AverageFillPrice; } } } //if (doDeepTracing) Log("---------------------------------------"); if(killRec.cSymbol != null) { if (killRec.cStrike <= stockPrice) /// ITM CALL -- use limit order { limitPrice = stockPrice - killRec.cStrike + 0.10M; closeCallTicket = LimitOrder(killRec.cSymbol, -killRec.cQty, limitPrice); // sell the puts OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closeCallTicket; oLO.tpr = killRec; oLO.oRight = OptionRight.Call; oLOs.Add(oLO); if (doDeepTracing) Log($" KK ** LIMIT ORDER TO SELL TO CLOSE {killRec.cQty.ToString()} contracts of {killRec.cSymbol.Value} at {limitPrice.ToString()}"); //if (doDeepTracing) Log("-"); } else { closeCallTicket = MarketOrder(killRec.cSymbol, -killRec.cQty); // sell the puts if (doDeepTracing) Log($" KK ** KK ** MARKET ORDER TO SELL TO CLOSE {killRec.cQty.ToString()} contracts of {killRec.cSymbol.Value} at the market." ); if (doDeepTracing) Log("-"); if (closeCallTicket.Status == OrderStatus.Filled) { killRec.cEndPrice = closeCallTicket.AverageFillPrice; if (doDeepTracing) Log(" KK ** KK ** KK ** UPDATING CALL PRICE TO " + killRec.cEndPrice + " ** KK ** KK"); } return bKTC; } } // /// /// /// killRec.pSymbol != null /* 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; //if (doDeepTracing) Log(" 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 wing calls OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closeWCallTicket; oLO.tpr = killRec; oLO.oRight = OptionRight.Call; oLO.isWingCall = true; oLOs.Add(oLO); //if (doDeepTracing) Log("-"); } else { closeWCallTicket = MarketOrder(killRec.wcSymbol, -killRec.wcQty); // sell the puts //if (doDeepTracing) Log(" KK ** LIMIT ORDER TO SELL TO CLOSE WING " + killRec.wcQty.ToString() + " contracts of " + killRec.wcSymbol + " at " + limitPrice.ToString()); //if (doDeepTracing) Log("-"); //} if (closeWCallTicket.Status == OrderStatus.Filled) { killRec.wcEndPrice = closePutTicket.AverageFillPrice; //if (doDeepTracing) Log(" KK ** UPDATING WING END PRICE TO " + killRec.wcEndPrice + " ** KK ** KK"); } } */ return bKTC; //if (doDeepTracing) Log("-"); } /////////////////////////////////////////////////////////////////////////////////// // RollCallDown //////////////////////////////////////////////////////////////////////////////////// public bool RollCallDown(SSQRColumn bestSSQRColumn, ref LookupData LUD, TradePerfRec oldTPR, decimal sPrice, bool isCollar){ int rollQty = oldTPR.cQty; // change in qty, difference between total stock and covered stock = uncovered stock == amount to roll up. int findYear = CurrentSlice.Time.Year; int findMonth = CurrentSlice.Time.Month; bool prosecuteCalls; OrderTicket closePutTicket; // used to close the open puts OrderTicket rollPutTicket; // used to open (roll up) new puts OrderTicket closeCallTicket; OrderTicket rollCallTicket; prosecuteCalls = !oldTPR.cSymbol.Equals(bestSSQRColumn.callSymbol); if (LUD.doTracing) Log(" RC ** RC ** RC ** Logging ROLLCALL RC ** RC ** RC **"); // Compute the 3rd Friday of this month [options expiration] ---> do not adjust for potential holiday here DateTime thisMonthExpiry = FindDay(findYear, findMonth, DayOfWeek.Friday, 3); /* 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 (doDeepTracing) Log(" ************** END 2nd TPR ITM PUT CALC ****************"); //if (doDeepTracing) Log("-"); } if (symbFilter != null) Plot("Stock Chart", "PTSs", divPlotValue); return bKTC; // loop around and try again } */ symbolDataBySymbol[oldTPR.uSymbol].intTPRCntr =+ 1; //if (doDeepTracing) Log(" RP ** MARKET ORDER TO SELL " + rollQty + " contracts of " + oldTPR.pSymbol + " at market"); if (prosecuteCalls) { closeCallTicket = MarketOrder(oldTPR.cSymbol, -rollQty); // sell the calls if (closeCallTicket.Status == OrderStatus.Filled) { oldTPR.cEndPrice = closeCallTicket.AverageFillPrice; } } else { oldTPR.cEndPrice = Securities[oldTPR.cSymbol].Price; } // first adjust the old tradePerfRec to decrement pQty and uQty. It remains open to be processed for the remaining covered, collared stock. 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) //if (doDeepTracing) Log(" RP ** MARKET ORDER TO BUY " + rollQty + " contracts of " + bestPutSpread.newPutSymb + " at market"); if (prosecuteCalls) { rollCallTicket = MarketOrder(bestSSQRColumn.callSymbol, rollQty); // buy the lower calls if (rollCallTicket.Status == OrderStatus.Filled) { newTPR1.cStartPrice = rollCallTicket.AverageFillPrice; } else { newTPR1.cStartPrice = Securities[oldTPR.cSymbol].Price; /// place holder } } // TradePerfRec newTPR2 = new TradePerfRec(); // create a TradePerfRec #2 for the new Synthetic Call (stock-covered puts) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // NOTE: THIS CODE MAY CLONE THE OLDTPR... DOES IT COPY SYMBOLS PROPERLY? //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /* foreach (var field in typeof(TradePerfRec).GetFields()) // copy oldTPR to newTPR1 { field.SetValue(newTPR1, field.GetValue(oldTPR)); } */ //TradePerfRec rolledPutTPR = this.MemberwiseClone(); //symbolDataBySymbol[oldTPR.uSymbol].intTPRCntr += 1; oldTPR.uEndPrice = sPrice; oldTPR.endDate = CurrentSlice.Time; newTPR1.uSymbol = oldTPR.uSymbol; // newTPR1 for the uncovered synthetic call (put + stock) portion of the original collar newTPR1.index = symbolDataBySymbol[oldTPR.uSymbol].intTPRCntr; // maintain collarIndex throughout the entire sequence of collars and synthCalls newTPR1.uQty = oldTPR.uQty; // log the starting and ending values and close the TradePerfRec newTPR1.uStartPrice = oldTPR.uStartPrice; newTPR1.uEndPrice = 0; newTPR1.cSymbol = bestSSQRColumn.callSymbol; newTPR1.cStrike = bestSSQRColumn.callStrike; newTPR1.pSymbol = bestSSQRColumn.putSymbol; newTPR1.pStrike = bestSSQRColumn.putStrike; newTPR1.cDelta = bestSSQRColumn.callDelta; newTPR1.pDelta = bestSSQRColumn.putDelta; newTPR1.cGamma = bestSSQRColumn.callGamma; newTPR1.pGamma = bestSSQRColumn.putGamma; newTPR1.expDate = bestSSQRColumn.callExpiry; newTPR1.cQty = rollQty; newTPR1.startDate = CurrentSlice.Time; newTPR1.isInitializer = false; newTPR1.isSecondary = false; newTPR1.numDividends = 0; newTPR1.divIncome = 0; newTPR1.tradeRecCount = oldTPR.tradeRecCount + 1; newTPR1.ROR = oldTPR.ROR; newTPR1.ROC = oldTPR.ROC; newTPR1.CCOR = oldTPR.CCOR; newTPR1.tradeCriteria = oldTPR.tradeCriteria; newTPR1.strtngCndtn = "CALL ROLL DOWN" + (bestSSQRColumn.putSymbol!=null ? " WITH PERK PUT" : ""); if (prosecuteCalls) { oldTPR.reasonForClose = $"C ROLL DOWN STOCK DEPRECIATION: {oldTPR.uSymbol.Value} : {String.Format("{0:0.00}",sPrice-oldTPR.uStartPrice)} COSTING {(newTPR1.cStartPrice - oldTPR.cEndPrice).ToString()}"; } else{ oldTPR.reasonForClose = $"C ROLL DOWN STOCK DEPRECIATION ADD PUTS ONLY: {oldTPR.uSymbol.Value} : {String.Format("{0:0.00}",sPrice-oldTPR.uStartPrice)} PUTS {(newTPR1.pStartPrice).ToString()}"; } if (doDeepTracing) Log($" R ** R ** CREATING NEW TPR IN ROLL CALL DOWN PROCESSING ** R ** R "); if (doDeepTracing) Log($" RC ** RC ** {oldTPR.uSymbol.Value} ** RC ** R "); if (doDeepTracing) Log($" RC ** Depreciation: {String.Format("{0:0.00}",sPrice-oldTPR.uStartPrice)} COSTING {(newTPR1.cStartPrice - oldTPR.cEndPrice).ToString()} ** RP ** RP "); newTPR1.VERating = LUD.intVERating; newTPR1.momentum = LUD.decMomentum; newTPR1.oneYearPriceTarget = LUD.decOneYearPriceTarget; newTPR1.momentumRank = LUD.intMomentumRank; newTPR1.uStartPrice = sPrice; // set the newTPR.uPrice to 0-delta current sPrice if(isCollar & oldTPR.pSymbol!=null){ // buy back old calls /// NOTE: NEED TO VERIFY THIS SITUATION IS HANDLED PROPERLY if (oldTPR.pStrike > sPrice) { var limitPrice = Securities[oldTPR.pSymbol].BidPrice + ((Securities[oldTPR.pSymbol].AskPrice - Securities[oldTPR.pSymbol].BidPrice) / 2M); // get the mid point for the limit price closePutTicket = LimitOrder(oldTPR.pSymbol, -oldTPR.pQty, limitPrice); // sell limit order oldTPR.pEndPrice = oldTPR.pStrike - sPrice; OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closePutTicket; oLO.tpr = oldTPR; oLO.oRight = OptionRight.Put; oLOs.Add(oLO); oldTPR.pEndPrice = limitPrice; /// place holder for endprice } else { closePutTicket = MarketOrder(oldTPR.pSymbol, -oldTPR.pQty); if (closePutTicket.Status == OrderStatus.Filled) { oldTPR.pEndPrice = closePutTicket.AverageFillPrice; } } if (bestSSQRColumn.putSymbol!=null && doDeepTracing) Log($" RP ** RP ** SELL {oldTPR.pQty.ToString()} NEW CALLS {bestSSQRColumn.putSymbol.Value} ** RP ** RP "); } if(bestSSQRColumn.putSymbol!=null){ // sell calls to generate income /// NOTE: NEED TO VERIFY THIS SITUATION IS HANDLED PROPERLY newTPR1.pStrike = bestSSQRColumn.putStrike; newTPR1.thetaExpiration = bestSSQRColumn.putExpiry; newTPR1.pSymbol = bestSSQRColumn.putSymbol; newTPR1.pQty = -rollQty; if (bestSSQRColumn.putStrike > newTPR1.uStartPrice) { var limitPrice = Securities[bestSSQRColumn.putSymbol].BidPrice + ((Securities[bestSSQRColumn.putSymbol].AskPrice - Securities[bestSSQRColumn.putSymbol].BidPrice) / 2M); // get the mid point for the limit price rollPutTicket = LimitOrder(bestSSQRColumn.putSymbol, -rollQty, limitPrice); // sell limit order OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = rollPutTicket; oLO.tpr = newTPR1; oLO.oRight = OptionRight.Put; oLOs.Add(oLO); newTPR1.pStartPrice = limitPrice; /// place holder for pStartPrice. Should be set in oloHandling newTPR1.pQty = -rollQty; } else { rollPutTicket = MarketOrder(bestSSQRColumn.putSymbol, -rollQty); if (rollPutTicket.Status == OrderStatus.Filled) { newTPR1.pStartPrice = rollPutTicket.AverageFillPrice; newTPR1.pQty = (int)rollPutTicket.QuantityFilled; } } if (doDeepTracing) Log($" RP ** RP ** ROLLING COLLAR CALLS ** RP ** RP "); if (doDeepTracing) Log($" RP ** RP ** BUYING: {(oldTPR.pSymbol!=null ? oldTPR.pSymbol.Value : "-- no put --")} | SELLING: {(bestSSQRColumn.putSymbol != null ? bestSSQRColumn.putSymbol.Value : "-- no put--") } ** RP ** RP "); if (doDeepTracing) Log($" RP ** Call Appreciation: {(oldTPR.pSymbol != null ? String.Format("{0:0.00}",Securities[oldTPR.pSymbol].AskPrice - oldTPR.pStartPrice) : "-- NA --")} COSTING {(oldTPR.pSymbol != null ? (Securities[newTPR1.pSymbol].BidPrice - Securities[oldTPR.pSymbol].AskPrice).ToString() : newTPR1.pStartPrice)} ** RP ** RP "); } if (doDeepTracing) Log(" -- "); if (doTracing) Log(" RP ** RP ** END CALL DOWN ** RP ** RP ** "); tprsToClose.Add(oldTPR); tprsToOpen.Add(newTPR1); return true; } /////////////////////////////////////////////////////////////////////////////////// // RollTheCollar //////////////////////////////////////////////////////////////////////////////////// public bool RollTheCollar(LookupData LUD, TradePerfRec oldTradeRec, ref SSQRColumn bestSSQRColumn, string reason) { Slice data = CurrentSlice; decimal stockPrice = Securities[LUD.uSymbol].Price; thisCCOR = bestSSQRColumn.CCOR; decimal thisNetOptions = bestSSQRColumn.netOptions; decimal limitPrice = 0; OrderTicket closeCallTicket; OrderTicket closePutTicket; OrderTicket callTicket; if (haltProcessing) { //if (doDeepTracing) Log(" Logging ROLL "); } //if (symbFilter != null) Plot("Stock Chart", "Rolls", stockPrice + 5); 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 if (doDeepTracing) Debug(" ROLLING ** STARTING ** ROLLING ** STARTING ** ROLLING ** STARTING ** ROLLING ** STARTING ** ROLLING ** STARTING ** "); doTheTrade = true; //if (doDeepTracing) Log(" -- "); if (doDeepTracing) { 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; //Log($" |||| HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } // Log($" |||| SELL OPTS P&L: " + String.Format("{0:0.00}", currSellPnL)); // Log($" |||| Exrcs PUT P&L: " + String.Format("{0:0.00}", currExrcsPutPnL)); // Log($" |||| Exrcs CALL P&L: " + String.Format("{0:0.00}", currExrcsCallPnL)); } if (oldTradeRec.cStrike <= stockPrice) /// ITM Call -- use limit order to close { limitPrice = stockPrice - oldTradeRec.cStrike + 0.10M; if (doDeepTracing) Log(" @R @R @R LIMIT ORDER TO SELL " + oldTradeRec.cQty.ToString() + " contracts of " + oldTradeRec.cSymbol + " at " + limitPrice.ToString()); closeCallTicket = LimitOrder(oldTradeRec.cSymbol, -oldTradeRec.cQty, limitPrice); // sell the callse // closeCallTicket = MarketOrder(oldTradeRec.cSymbol, -oldTradeRec.cQty); // sell the calls oldTradeRec.cEndPrice = stockPrice - oldTradeRec.cStrike; OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closeCallTicket; oLO.tpr = oldTradeRec; oLO.oRight = OptionRight.Call; oLOs.Add(oLO); //if (closePutTicket.Status == OrderStatus.Submitted) oldTradeRec.pEndPrice = limitPrice; } else { if (doDeepTracing) Log(" @R @R @R @R MARKET ORDER TO SELL TO CLOSE " + oldTradeRec.cQty.ToString() + " contracts of " + oldTradeRec.cSymbol + " at market"); closeCallTicket = MarketOrder(oldTradeRec.cSymbol, -oldTradeRec.cQty); // sell the calls } if (closeCallTicket.Status == OrderStatus.Filled) { oldTradeRec.cEndPrice = closeCallTicket.AverageFillPrice; if (doDeepTracing) Log(" @R @R @R @R UPDATING PUT " + oldTradeRec.cSymbol + " END PRICE @ " + oldTradeRec.cEndPrice ); } if (doDeepTracing) Log("-"); // 2nd, buy back the long call doTheTrade = true; if(oldTradeRec.pSymbol != null){ if (oldTradeRec.pStrike >= stockPrice) /// ITM PUT -- use limit order { /// PUT QTY should be negative from the opening short trade limitPrice = oldTradeRec.pStrike - stockPrice + 0.10M; if (doDeepTracing) Log(" @R @R @R @R LIMIT ORDER TO BUY TO CLOSE " + oldTradeRec.pQty.ToString() + " contracts of " + oldTradeRec.pSymbol + " at " + limitPrice.ToString()); closePutTicket = LimitOrder(oldTradeRec.pSymbol, -oldTradeRec.pQty, limitPrice); //if (closePutTicket.Status == OrderStatus.Submitted) oldTradeRec.pEndPrice = limitPrice; oldTradeRec.pEndPrice = oldTradeRec.pStrike - stockPrice; OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = closePutTicket; oLO.tpr = oldTradeRec; oLO.oRight = OptionRight.Put; oLOs.Add(oLO); } else { if (doDeepTracing) Log(" @R @R @R @R MARKET ORDER TO BUY TO CLOSE " + oldTradeRec.pQty.ToString() + " contracts of " + oldTradeRec.pSymbol + " at market"); closePutTicket = MarketOrder(oldTradeRec.pSymbol, -oldTradeRec.pQty); // buy the calls } if (closePutTicket.Status == OrderStatus.Filled) { oldTradeRec.pEndPrice = closePutTicket.AverageFillPrice; if (doDeepTracing) Log(" @R @R @R @R UPDATING PUT END PRICE @ " + oldTradeRec.pEndPrice ); } } // Log("-"); // 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; if (doDeepTracing) Log(" @R @R @R 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 { if (doDeepTracing) Log(" @R @R @R 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 } if (doDeepTracing) Log("-"); if (closeCallTicket.Status == OrderStatus.Filled) { oldTradeRec.wcEndPrice = closeWCallTicket.AverageFillPrice; if (doDeepTracing) Log(" @R @R @R UPDATING WING CALL " + oldTradeRec.wcSymbol + "END PRICE @ " + oldTradeRec.wcEndPrice ); } } // Keep the stock, but close this trade performance record. */ if (doDeepTracing) Log(" ROLLING ** ROLLING ** ROLLING ** ROLLING ** ROLLING ** SELL NEW COLLAR ** ROLLING ** ROLLING ** ROLLING ** ROLLING ** ROLLING ** "); symbolDataBySymbol[oldTradeRec.uSymbol].intTPRCntr += 1; //LUD.GetNextExDate(this); // set last known dividend and next exdata LUD.getNxtIExDt(LUD.uSymbol.Value, this); LUD.loadVEData(this); symbolDataBySymbol[oldTradeRec.uSymbol].divdndAmt = LUD.divdndAmt; // set new dividend amount symbolDataBySymbol[oldTradeRec.uSymbol].decOneYearPriceTarget_Initial = LUD.decOneYearPriceTarget; // set new initial 1-yr price target symbolDataBySymbol[oldTradeRec.uSymbol].initialTargetEndDate = LUD.initialTargetEndDate; // set net initial 1-yr target date symbolDataBySymbol[oldTradeRec.uSymbol].VECase = LUD.VECase; 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 = LUD.uSymbol; // 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 = "ROLLED / " + reason; thisNewTPRec.index = symbolDataBySymbol[oldTradeRec.uSymbol].intTPRCntr; // maintain the collarIndex through the entire sequence of collars thisNewTPRec.tradeRecCount = oldTradeRec.tradeRecCount + 1; // count the trades thisNewTPRec.startDate = data.Time; // set the start date thisNewTPRec.cStrike = bestSSQRColumn.callStrike; thisNewTPRec.cDelta = bestSSQRColumn.callDelta; //thisNewTPRec.wcStrike = bestSSQRColumn.wCallStrike; thisNewTPRec.expDate = bestSSQRColumn.callExpiry; // 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 = LUD.VECase; // thisNewTPRec.stockADX = lastAdx; // thisNewTPRec.stockADXR = lastAdxr; // thisNewTPRec.stockOBV = lastObv; // thisNewTPRec.stockAD = lastAd; // thisNewTPRec.stockADOSC = lastAdOsc; // thisNewTPRec.stockSTO = lastSto; // thisNewTPRec.stockVariance = lastVariance; thisNewTPRec.VERating = LUD.intVERating; thisNewTPRec.momentum = LUD.decMomentum; thisNewTPRec.oneYearPriceTarget = LUD.decOneYearPriceTarget; thisNewTPRec.momentumRank = LUD.intMomentumRank; //Log(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 // 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; // thisWingFactor = 1; 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 // *** /// **** tradablePut can be null doTheTrade = true; //if (doDeepTracing) Log(" @R @R @R EXECUTING PUT BUY MARKET ORDER TO OPEN " + ((1 + thisWingFactor) * optionsToTrade) + " contracts of " + tradablePut ); if (tradablePut!=null){ var putTicket = MarketOrder(tradablePut, optionsToTrade); /// 2023-04-23 *** ::: **** reverted this to quantity==optionsToTrade from wingFactor*optionsToTrade if (putTicket.Status == OrderStatus.Filled) { thisNewTPRec.pSymbol = tradablePut; thisNewTPRec.pStartPrice = putTicket.AverageFillPrice; thisNewTPRec.pQty = (int)putTicket.QuantityFilled; thisNewTPRec.pStrike = bestSSQRColumn.putStrike; thisNewTPRec.pDelta = bestSSQRColumn.putDelta; //if (doDeepTracing) Log(" @R @R @R UPDATING PUT START PRICE TO " + thisNewTPRec.pStartPrice + " FOR " + thisNewTPRec.pQty + " CONTRACTS" ); } } doTheTrade = true; if(bestSSQRColumn.callSymbol != null) { if (doDeepTracing) Log(" @R @R @R @R EXECUTING CALL BUY MARKET ORDER TO OPEN " + optionsToTrade + " contracts of " + tradableCall ); //if (tradableCall.ID.StrikePrice > stockPrice) { callTicket = MarketOrder(tradableCall, -optionsToTrade); /*} else { limitPrice = stockPrice - tradableCall.ID.StrikePrice + 0.10M; if (doDeepTracing) Log(" @R @R @R @R LIMIT ORDER TO BUY TO CLOSE " + oldTradeRec.cQty.ToString() + " contracts of " + oldTradeRec.cSymbol + " at " + limitPrice.ToString()); callTicket = LimitOrder(tradableCall, -optionsToTrade, limitPrice); //if (closeCallTicket.Status == OrderStatus.Submitted) oldTradeRec.cEndPrice = limitPrice; OpenLimitOrder oLO = new OpenLimitOrder(); oLO.oTicket = callTicket; oLO.tpr = thisNewTPRec; oLO.oRight = OptionRight.Call; oLOs.Add(oLO); } */ //var callTicket = MarketOrder(tradableCall, -callsToTrade); if (callTicket.Status == OrderStatus.Filled) { thisNewTPRec.cSymbol = tradableCall; thisNewTPRec.cStartPrice = callTicket.AverageFillPrice; thisNewTPRec.cQty = (int)callTicket.QuantityFilled; //if (doDeepTracing) Log(" @R @R @R UPDATING SHORT CALL START PRICE TO " + thisNewTPRec.cStartPrice + " FOR " + thisNewTPRec.cQty + " CONTRACTS" ); } } /* doTheTrade = true; if (thisWingFactor > 0) { //if (doDeepTracing) Log(" @R @R @R 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; //if (doDeepTracing) Log(" @R @R @R UPDATING WING CALL START PRICE TO " + thisNewTPRec.wcStartPrice + " FOR " + thisNewTPRec.wcQty + " CONTRACTS" ); } else { //if (doDeepTracing) Log(" ROLLING ** WING FACTOR IS 0 -- NO WINGS ADDED"); } } */ /// Roll is done. save the new trade performance record var orderedSSQRMatrix = LUD.SSQRMatrix.OrderByDescending(p => p.ROR); IterateOrderedSSQRMatrix(orderedSSQRMatrix); // IterateTradeRecord(thisNewTPRec); tprsToClose.Add(oldTradeRec); tprsToOpen.Add(thisNewTPRec); //tradeRecs.Add(thisNewTPRec); return true; } /////////////////////////////////////////////////////////////////////////////////// // GetBestCollar 2 parameters //////////////////////////////////////////////////////////////////////////////////// public SSQRColumn GetBestCollar(CollarAlgorithm algo, ref LookupData LD) { if (haltProcessing) { // Log(" @@@@@@ Logging GetPotentialCollars 1 on Upside Potential"); } Slice thisSlice = CurrentSlice; Symbol thisStock = LD.uSymbol; // First get the underlying stock price in this Slice decimal stockPrice = thisSlice[thisStock].Price; SSQRColumn bestTradableColumn = new SSQRColumn(); OptionChain putChain; // instantiate an OptionChain var for updating SSQRMatrix with slice data OptionChain callChain; // OptionChain wcallChain; // OptionContract putContract; // OptionContract callContract; // Symbol ssqrPutSymbol; // instantiate a Symbol var for updating SSQRMatrix with slice Data Symbol ssqrCallSymbol; // // Second get its options symbols var allUnderlyingOptionsSymbols = OptionChainProvider.GetOptionContractList(thisStock, thisSlice.Time); if (allUnderlyingOptionsSymbols.Count() == 0) // missing data at this time { if (doDeepTracing) Debug(" DDDDDDDDDDDDDDDDDDDDD Missing Data at " + thisSlice.Time + " no options for " + thisStock); return bestTradableColumn; } int findYear = thisSlice.Time.Year; int findMonth = thisSlice.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 // in version 6, put package on whenever VE ranking is high. (or Chaiken / Accumulation/Distribution indicates) Dictionary<int, DateTime> putExpiries = GetOptionExpiries(thisSlice.Time, LD, thisMonthExpiry, true, false); Dictionary<int, DateTime> callExpiries = GetOptionExpiries(thisSlice.Time, LD, thisMonthExpiry, true, true); // now assemble the SSQR matrix using the expiries dictionary and the contracts lists LD.SSQRMatrix.Clear(); AssembleSSQRMatrix(this, ref LD, putExpiries, callExpiries); // Get the SSQRColumn with the best reward to risk if (LD.SSQRMatrix == null | LD.SSQRMatrix.Count == 0){ if(LD.doTracing) algo.Debug($" ** GET BEST COLLAR ** :: 0 or empty SSQR in TradeDetermination.GetBestCollar for {LD.uSymbol.Value}"); return bestTradableColumn; /// found it's possible to have no SSQRs, if so, pass the empty/null SSQRColumn to calling routine } if(algo.symbolDataBySymbol.ContainsKey(thisStock)) { algo.symbolDataBySymbol[thisStock].VECase = LD.VECase; } // var qualifyingCollars = LD.SSQRMatrix.Where(s=>s.putPremium!=0 & s.putPremium<=s.callPremium).Count(); // if (qualifyingCollars == 0) return bestTradableColumn; // bestTradableColumn = passedMatrix.OrderByDescending(p => p.CCOR).FirstOrDefault(); bestTradableColumn = LD.SSQRMatrix.OrderByDescending(bTC => bTC.ROR).FirstOrDefault(); /// 2021-03-21 -- changed from OrderedByDescending ..... using downsideRisk/upsidePotential //bestTradableColumn = LD.SSQRMatrix.OrderByDescending(bTC => bTC.upsidePotential).FirstOrDefault(); /// 2022-12-12 -- changed from ROR ..... using upsidePotential //bestTradableColumn = LD.SSQRMatrix.OrderByDescending(bTC=>bTC.putExpiry).ThenByDescending(bTC=>bTC.upsidePotential).FirstOrDefault(); if(LD.doDeepTracing) algo.Log($" ** GET BEST COLLAR ** :: The bestTradable ROR for {LD.uSymbol.Value} is {bestTradableColumn.ROR.ToString("0.00")}"); /*if ((decimal)bestTradableColumn.ROR < 1m){ if(LD.doTracing) algo.Debug($" TD ** TD ** TD ** TD *** BestCollar {bestTradableColumn.ROR} failed ROR Threshold for {LD.uSymbol.Value}"); if (symbolDataBySymbol.ContainsKey(bestTradableColumn.uSymbol)) { symbolDataBySymbol[bestSSQRColumn.uSymbol].SSQRFailCnt += 1; if (symbolDataBySymbol[bestSSQRColumn.uSymbol].SSQRFailCnt >=4 ) { symbolDataBySymbol[bestSSQRColumn.uSymbol].isRollable = false; SymbolsToRemove.Add(bestSSQRColumn.uSymbol); } } return null; /// found it's possible to have no SSQRs, if so, pass the empty/null SSQRColumn to calling routine } */ return bestTradableColumn; } ///////////////////////////////////////////////////////////////////////////////////// // // // Excute SSQRS //////////////////////////////////////////////////////////////////////////////////// public void ExecuteSSQRs(Slice slc, ColumnSD csd){ didTheTrade = ExecuteTrade(slc, csd.col, ref csd.sd, csd.ld); if (didTheTrade) { logPortfolio = true; if (doTracing) Log($"*** *** *** DID TRADE -- {thisSymbol} --- Based upon this SSQR matrix: "); if (csd.sd.bbSignal == 1) csd.sd.bbSignal = 0; // var orderedSSQRMatrix = LUD.SSQRMatrix.OrderByDescending(p => p.upsidePotential); var orderedSSQRMatrix = csd.ld.SSQRMatrix.OrderByDescending(p => p.ROR); IterateOrderedSSQRMatrix(orderedSSQRMatrix); } else { if (doTracing) Log($"*** *** *** DIDN'T TRADE - {thisSymbol} --- "); } if (doTracing && logPortfolio) { Log($"|||| |||| TRADE RESULTS/HOLDINGS: on {slc.Time.ToShortDateString()} at {slc.Time.ToShortTimeString()}"); foreach(var kvp in Securities) /// make sure there's no leaking of abandoned stocks or options { try{ var security = kvp.Value; if (security.Invested) { Log($"|||| |||| |||| Package: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); } } catch (Exception errMsg) { Log(" ERROR at 383 in main.cs " + errMsg ); } } didTheTrade = false; } } // ||||||||||||||||||||||||||||||||||||||||||||||| // Prints greeks for the corresponding symbol public void PrintGreeks(ref Dictionary<Symbol, bool> foundOption, Slice thisSlice, Symbol pairKey, bool pairValue) { decimal callDelta; if (pairValue == true) { return; } foreach(var chain in thisSlice.OptionChains) { foreach(var option in chain.Value) { if(pairKey.ToString() == option.ToString()) { callDelta = option.Greeks.Delta; foundOption[pairKey] = true; /////if (doDeepTracing) Log(" || Succesfully added Greeks || " + pairKey + " Delta = " + callDelta.ToString()); //break; } } } } // ||||||||||||||||||||||||||||||||||||||||||||||| // Loops through dictionary of active contracts public void CheckGreeks(ref Dictionary<Symbol, bool> foundOption, Slice thisSlice) { OptionContract callContract; OptionChain callChain; Symbol optSymbol; Dictionary<Symbol, bool> tempDict = foundOption; foreach(var pair in tempDict) { Symbol pairKey = pair.Key; bool pairValue = pair.Value; PrintGreeks(ref foundOption, thisSlice, pairKey, pairValue); } } } }
#region imports using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Data; using QuantConnect.Orders; #endregion using QuantConnect.Securities.Option; using Newtonsoft.Json; namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public class optGrksRec { //algo.Log(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 partial 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 = false; // 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 decimal pGamma; // 23 put Gamma public decimal cGamma; // 24 call Gamma public decimal wcGamma; // 25 atm Call Gamma public int uQty; // 26 number of underlying shares public int pQty; // 27 number of put contracts public int cQty; // 28 number of call contracts public int wcQty; // 29 number of wing call contracts public decimal uStartPrice; // 30 Underlying Price when trade put on public decimal pStartPrice; // 31 Put Price when trade put on public decimal cStartPrice; // 32 Call Price when trade put on public decimal wcStartPrice; // 33 ATM Call Price when trade put on public decimal uEndPrice; // 34 Underlying Price when trade taken off public decimal pEndPrice; // 35 Put Price when trade taken off public decimal cEndPrice; // 36 Call Price when trade taken off public decimal wcEndPrice; // 37 ATM Call Price when trade taken off public int numDividends; // 38 # of dividends collected during the trade public decimal divIncome; // 39 $'s collected in Dividend income during the trade public decimal betaValue; // 40 beta value of underlying when trade put on public decimal RORThresh; // 41 Threshold for ROR public decimal ROCThresh; // 42 Threshold for ROC public decimal CCORThresh; // 43 Threshold for CCOR public string tradeCriteria; // 44 ROR or ROC or CCOR public decimal ROR; // 45 ROR calculation from SSQR Matrix public decimal ROC; // 46 ROC calculation from SSQR Matrix public decimal CCOR; // 47 CCOR calculation from SSQR Matrix public decimal stockADX; // 48 Average Directional Index Value public decimal stockADXR; // 49 Average Directional Index Rating public decimal stockOBV; // 50 On Balance Volume public decimal stockAD; // 51 Accumulation/Distribution public decimal stockADOSC; // 52 Accumulation/Distribution Oscillator public decimal stockSTO; // 53 Stochastic value public decimal stockVariance; // 54 Variance of underlying stock public decimal currSellPnL; // 55.. Rolltime evaluation of PnL if selling public decimal currExrcsPutPnL; // 56.. Rolltime evaluation of PnL if exercising put public decimal currExrcsCallPnL; // 57.. Rolltime evaluation of PnL if calls are assigned public decimal grossPnL; // 58 runtime calculation of PnL at close; public decimal SSQRnetProfit; // 59 runtime calculation of replacement bestSSQR net Profit public int VERating; // 60 VE Rating for Stat Analysis public decimal momentum; // 61 VE momentum for Stat Analysis public decimal oneYearPriceTarget; // 62 VE OYPT for Stat Analysis public int momentumRank; // 63 VE Momentum Rank for Stat Analysis // **** put class methods here to use collection of TradePerfRecs as basis to examine positions for expirations and assignments public void CleanUp(CollarAlgorithm algo) { DateTime endTime = algo.CurrentSlice.Time; //if (this.pEndPrice != 0) this.pEndPrice = 0; //if (this.cEndPrice != 0) this.cEndPrice = 0; this.endDate = endTime; /*decimal securitiesPrice = algo.Securities[this.uSymbol].Price; if (securitiesPrice == 0){ algo.Debug($" ***** ***** No Clean up Securities Price for {this.uSymbol.Value}. "); if (algo.lastClosePrice[this.uSymbol] !=0 ){ securitiesPrice = algo.lastClosePrice[this.uSymbol]; } else algo.Debug($" ***** ***** No Clean up lastClosePrice data for {this.uSymbol.Value} . "); } if (this.uEndPrice != 0) this.uEndPrice = securitiesPrice; */ this.reasonForClose = "Orphaned Package due to Options Expiration without assignment / exercise."; //algo.Log($" ************** CLEANING UP ORPHAN STOCK {this.uSymbol.Value} and setting end price to {this.uEndPrice.ToString("0.00")}"); algo.tprsToClose.Add(this); } public bool CheckRolling(CollarAlgorithm algo, LookupData LUD) { try { bool hasPut = false; bool hasCall = false; Slice slc = algo.CurrentSlice; Symbol symbUndr = this.uSymbol; LUD.clearLD(algo); LUD.uSymbol = this.uSymbol; //// CRITICAL :: SET THE SYMBOL TO BE PROCESSED IN THE LUD //if (symbUndr.Value == "CNP") { // algo.Debug(" --- --- This is CNP Processing"); //} string strTckr = symbUndr.Value; decimal stkPrc = 0m; decimal putPrc = 0m; decimal callPrc = 0m; if(LUD.doTracing) algo.Log($" ******* Check Rolling Symbol {this.uSymbol.Value} TPR ."); if (LUD.intType==0 && algo.symbolDataBySymbol.ContainsKey(this.uSymbol)){ if(!algo.symbolDataBySymbol[this.uSymbol].isRollable){ if(LUD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is not rollable -- check if this TPR is going to be closed."); return true; } } else if (LUD.intType!=0 && algo.etfDataBySymbol.ContainsKey(this.uSymbol)){ if(!algo.etfDataBySymbol[this.uSymbol].isRollable){ if(LUD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is not rollable -- check if this EFTPR is going to be closed."); return true; } } else { if(LUD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is no longer in SDBS."); algo.tprsToClose.Add(this); return false; } if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling for " + symbUndr.Value + " @" + slc.Time.ToString() ); //if (slc.ContainsKey(symbUndr) ) // 2023-03-12 ***** **** WHY CHECK IF THERE WAS A TICK FOR THIS UNDERLYING IN CURRENT SLICE. SHOULD BE UNNECCESSARY //{ // var tryPrice = slc[symbUndr].Price; // 2023-03-13 *** *** *** NOTE NOTE NOTE *** SUBSTANTIAL CHANGE // var tryPrice == null; if(slc.ContainsKey(this.uSymbol)) { stkPrc = slc[this.uSymbol].Price; } else stkPrc = algo.Securities[this.uSymbol].Price; if (stkPrc == 0) { if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling found no price data for " + symbUndr.Value + " @" + slc.Time.ToString() ); return false; } //stkPrc = Convert.ToDecimal(tryPrice); LUD.stockPrice = stkPrc; if(LUD.intType==0){ LUD.initialTargetEndDate = algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate; LUD.decOneYearPriceTarget = algo.symbolDataBySymbol[this.uSymbol].decOneYearPriceTarget_Initial; } else { LUD.initialTargetEndDate = algo.FindDay(slc.Time.AddMonths(1).Year, slc.Time.AddMonths(1).Month, DayOfWeek.Friday, 4); LUD.decOneYearPriceTarget = algo.etfDataBySymbol[this.uSymbol].decMOMP/100m * stkPrc; } if (this.pQty != 0){ var tryPrice = algo.Securities[this.pSymbol].AskPrice; if (tryPrice==null) { if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling found no Put Bid Price data for " + this.pSymbol.Value + " @" + slc.Time.ToString() ); return false; } else { putPrc = Convert.ToDecimal(tryPrice); hasPut = true; } } if (this.cQty != 0){ var tryPrice = algo.Securities[this.cSymbol].BidPrice; if (tryPrice==null) { if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling found no Call Price data for " + this.pSymbol.Value + " @" + slc.Time.ToString() ); return false; } else { callPrc = Convert.ToDecimal(tryPrice); hasCall = true; } } //} else { // if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling found no data for " + symbUndr.Value + " @" + slc.Time.ToString() ); // return false; //} LUD.dtTst = slc.Time; if(LUD.intType==0) LUD.getNxtIExDt(this.uSymbol.Value, algo); //// get NextExDate for this symbol this.GetPnLs(algo, LUD, ref stkPrc, ref callPrc, ref putPrc); if(LUD.intType==0) LUD.daysRemainingDiv = LUD.exDivdnDate.Subtract(slc.Time).Days; if (hasCall) { LUD.daysRemainingC = this.cSymbol.ID.Date.Subtract(slc.Time).Days; } else LUD.daysRemainingC = 100; if (hasPut) { LUD.daysRemainingP = this.pSymbol.ID.Date.Subtract(slc.Time).Days; } else LUD.daysRemainingP = 100; if(LUD.doDeepTracing) algo.Debug($" ********* {this.uSymbol.Value} Package Days Remaining: | Div: {LUD.daysRemainingDiv.ToString()} | Put: {LUD.daysRemainingP.ToString()} | Call: {LUD.daysRemainingC.ToString()} ---" ); /* if (LUD.intType==0 && hasCall && LUD.daysRemainingDiv < 4 && LUD.daysRemainingDiv > 0) // restrict Dividend Approachment to stock. Don't do ETF's { LUD.SSQRMatrix.Clear(); if (LUD.doTracing) algo.Debug(" ************** Calling TPR CheckDivRoll " + symbUndr.Value + " @" + slc.Time.ToString() ); if (this.CheckDivRoll(algo, ref stkPrc, LUD)) return true; } */ if (hasCall) { if (((stkPrc - this.cStrike)/stkPrc >= .05M && LUD.daysRemainingC <= 10 && LUD.daysRemainingC > 1) || ((stkPrc - this.cStrike) > 0 && LUD.daysRemainingC <= 1)) { LUD.SSQRMatrix.Clear(); if (LUD.doTracing) algo.Debug(" ************** Calling TPR CheckCallRoll " + symbUndr.Value + " @" + slc.Time.ToString() ); if (this.CheckCallRoll(algo, LUD, ref stkPrc, ref callPrc, ref putPrc)) return true; } } if (hasPut) { if (( (this.pStrike - stkPrc )/stkPrc >= .05M && LUD.daysRemainingP <= 10 && LUD.daysRemainingP > 1) || ( (this.pStrike > stkPrc) && LUD.daysRemainingP <= 1) ) { LUD.SSQRMatrix.Clear(); if (LUD.doTracing) algo.Debug(" ************** Calling TPR CheckPutRoll " + symbUndr.Value + " @" + slc.Time.ToString() ); if (this.CheckPutRoll(algo, LUD, ref stkPrc, ref callPrc, ref putPrc)) return true; } } if ((hasCall && (LUD.daysRemainingC <= 1 && stkPrc <= this.cStrike)) | (hasPut && (LUD.daysRemainingP <= 1 && stkPrc >= this.pStrike))) // this is the put expiration by design. the puts always control the collar and the risk { LUD.SSQRMatrix.Clear(); if (LUD.doTracing) algo.Debug(" ************** Calling TPR CheckOTMRoll " + symbUndr.Value + " @" + slc.Time.ToString() ); if (CheckOTMRoll(algo, LUD, ref stkPrc, ref callPrc, ref putPrc) ) return true; } return false; } catch (Exception errMsg) { algo.Debug(" ERROR TradeLogging.CheckRolling.cs " + errMsg ); return false; //// 2022-02-15: replaced return with continue -- could have caused premature exits } } ////// end CheckRolling //************************************************************************************************* //************** GetPnLs ************************************************************* //*** **** **** calculate P^L based upon current put bid and current call ask prices -- //*** **** **** this is conservative becuase limit orders at mid point would actually be used. //// /// *** *** *** *** VERIFIED CALCULATIONS FOR SHORT SYSTEM //************************************************************************************************* public void GetPnLs(CollarAlgorithm algo, LookupData LUD, ref decimal stockPrice, ref decimal currCallBidPrice, ref decimal currPutAskPrice) { this.currSellPnL = (this.uQty*(stockPrice-this.uStartPrice)) + (100*this.pQty*(currPutAskPrice - this.pStartPrice)) + (100*this.cQty*(currCallBidPrice - this.cStartPrice)); /// + (100*this.wcQty*(this.wcEndPrice - this.wcStartPrice)); if (this.pStrike > stockPrice ) { this.currExrcsPutPnL = (this.uQty*(this.pStrike-this.uStartPrice)) + (100*this.pQty*(0 + this.pStartPrice)) + (100*this.cQty*(currCallBidPrice - this.cStartPrice)); /// + (100*this.wcQty*(this.wcEndPrice - this.wcStartPrice)); } else {this.currExrcsPutPnL = -10000000;} if (this.cStrike < stockPrice ) { this.currExrcsCallPnL = (this.uQty*(this.cStrike-this.uStartPrice)) + (100*this.pQty*(currPutAskPrice - this.pStartPrice)) + (100*this.cQty*(0 - this.cStartPrice)); /// + (100*this.wcQty*(this.wcEndPrice - this.wcStartPrice)); } else {this.currExrcsCallPnL = -10000000;} //algo.Log($" -- -- -- P&L's Sell: {this.currExrcsPutPnL.ToString("0.00")} PutExercise: {this.currExrcsPutPnL.ToString("0.00")} CallExercise: {this.currExrcsPutPnL.ToString("0.00")} ."); } //************************************************************************************************* //************** GetCorrspndingPut ********* THIS IS USED FOR DETECTING DIVIDEND-DRIVEN ASSIGNMENT //************************************************************************************************* private Symbol GetCorrspndngPut() { int indexOfC = this.cSymbol.ToString().LastIndexOf("C"); char[] charArrayC = this.cSymbol.ToString().ToCharArray(); char[] charArrayP = charArrayC; charArrayP[indexOfC] = 'P'; string putString = new string(charArrayP); return putString; } //************************************************************************************************* //************** CheckOTMRoll ******************************************************* //************************************************************************************************* public bool CheckOTMRoll(CollarAlgorithm algo , LookupData LUD, ref decimal stockPrice, ref decimal currCallBidPrice, ref decimal currPutAskPrice) { bool killed = false; bool annHighFade = false; bool isRolled = false; // risk of options expiration WITHOUT EXERCISE if (LUD.doTracing) algo.Log($" ************** BEGIN OTM OPTIONS CALC FOR {LUD.uSymbol} ****************"); try { SymbolData sd = LUD.intType==0 ? algo.symbolDataBySymbol[LUD.uSymbol] : algo.etfDataBySymbol[LUD.uSymbol]; if (LUD.intType == 0) sd.decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget; //LUD.loadVEData(algo); Slice slD = algo.CurrentSlice; var oneYearBars = sd.Bars.ToList(); var lowBar = oneYearBars.Min(b => b.Close); //if (stockPrice < lowBar) annHighFade = true; if(LUD.intType==0 && annHighFade) { if (LUD.doTracing) algo.Log($" ************** KILLING {LUD.uSymbol.Value} due to price {stockPrice.ToString()} being below annual low close {lowBar.ToString()} minus 2."); killed = isRolled = algo.KillTheCollar(this, ref LUD, "ABORT OTM ROLL -- PROXIMITY TO ANNUAL LOW " + LUD.uSymbol, false, true ); if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } return killed; } SSQRColumn bestSSQRColumn = new SSQRColumn(); if (LUD.intType==0) { bestSSQRColumn = algo.GetBestCollar(algo, ref LUD); } else { //algo.Debug($" *** *** *** *** *** THE {LUD.uSymbol.Value} SECURITY LUD.TYPE IS {LUD.intType}"); bestSSQRColumn = LUD.SSQRMatrix.FirstOrDefault(); } if (LUD.SSQRMatrix.Count == 0) { if (LUD.daysRemainingC <= 1 | LUD.daysRemainingP <= 1) { if (LUD.doTracing) algo.Log($" *** *** *** *** *** KILLING {LUD.uSymbol.Value} on last day because no collars came back."); killed = isRolled = algo.KillTheCollar(this, ref LUD, $"ABORT OTM ROLL -- NO POT COLLARS FOR {LUD.uSymbol.Value}", false, true); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doDeepTracing) algo.Log($" ************** END OTM OPTIONS KILL FOR {LUD.uSymbol.Value} ****************"); if (LUD.doDeepTracing) algo.Log("-"); return isRolled; } else { LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); algo.Debug($" ************** END OTM OPTIONS CALC -- NO POTCOLS FOR {LUD.uSymbol.Value} -- LOOP AND TRY AGAIN LATER ***"); return isRolled; // if no collars then return and loop around again } } if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty()) { if (LUD.doTracing) algo.Log($" ************** null bestSSQRColumn in OTM Expiry Approachment FOR {LUD.uSymbol.Value} *************"); if (LUD.doTracing) algo.Log($" ************** END OTM OPTIONS CALC FOR {LUD.uSymbol.Value} ****************"); if (LUD.daysRemainingC <= 1 | LUD.daysRemainingP <= 1) { if (LUD.doTracing) algo.Log($" *** *** *** *** *** KILLING {LUD.uSymbol.Value} on last day because no collars came back."); killed = isRolled = algo.KillTheCollar(this, ref LUD, $"KILLED IN OTM PROCESSING -- NO VIABLE SSQRS FOR {LUD.uSymbol.Value}", false, true); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doDeepTracing) algo.Log($" ************** END OTM OPTIONS KILL FOR {LUD.uSymbol} LOOP AND TRY AGAIN LATER ****************"); if (LUD.doDeepTracing) algo.Log("-----"); return isRolled; } else { LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doDeepTracing) algo.Log($" ************** END OTM OPTIONS CALC FOR {LUD.uSymbol} -- bestSSQRColumn NULL or EMPTY ***"); return isRolled; // exit OnData() and loop around and try again } } // no bestSSQRColumn // IS IT NECESSARY TO SET THESE HERE Symbol tradablePut = bestSSQRColumn.putSymbol; Symbol tradableCall = bestSSQRColumn.callSymbol; //goodThresh = bestSSQRColumn.CCOR >= CCORThresh; // bool goodThresh = (LUD.intVERating == 5 & LUD.decOneYearPriceTarget > 1.05m * stockPrice) | (LUD.intVERating > 3 & bestSSQRColumn.upsidePotential >=5); bool goodThresh = (((LUD.divdndAmt * 3m) + LUD.decOneYearPriceTarget) < .95m * stockPrice) | algo.symbolDataBySymbol[this.uSymbol].continueTrade; if (goodThresh) // roll the position forward { if (LUD.doTracing) algo.Log($" ************** BEGIN OTM OPTIONS ROLL FOR {LUD.uSymbol} ****************"); bool bRollable = algo.symbolDataBySymbol[LUD.uSymbol].isRollable; if (!annHighFade && bRollable && (this.currSellPnL > 0 | this.uQty * bestSSQRColumn.upsidePotential > Math.Abs(this.currSellPnL) | algo.symbolDataBySymbol[this.uSymbol].continueTrade )){ // only roll the collar if the current record may be closed profitably-- otherwise seek exercise in kill //if (currSellPnL > 0) { if (algo.RollTheCollar(LUD, this, ref bestSSQRColumn, "ROLLED IN OTM EXPIRATION ")) { isRolled = true; if (LUD.doDeepTracing) algo.Log($" ************** ROLLED OTM OPTIONS FOR {LUD.uSymbol} COMPLETED WITH SSQR: ****************"); if (LUD.doDeepTracing) algo.Log("-"); var orderedSSQRMatrix = LUD.SSQRMatrix.OrderBy(p => p.upsidePotential); algo.IterateOrderedSSQRMatrix(orderedSSQRMatrix); //didTheTrade = false; LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doDeepTracing) algo.Log($" ************** END SUCCESSFUL OTM OPTIONS ROLL FOR {LUD.uSymbol} ****************"); if (LUD.doDeepTracing) algo.Log("-"); return isRolled; } else { if (LUD.daysRemainingC <= 1 | LUD.daysRemainingP <= 1) { if (LUD.doTracing) algo.Log($" ************** KILLING OTM OPTIONS COLLAR FOR {LUD.uSymbol} ON LAST DAY - FAILED ROLL ****************"); killed = isRolled = algo.KillTheCollar(this, ref LUD, $"KILLED IN OTM PROCESSING -- FAILED ROLL FOR {LUD.uSymbol}", false, true); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Debug($" ************** END OTM OPTIONS KILL FOR {LUD.uSymbol} ON LAST DAY - FAILED ROLL ****************"); if (LUD.doTracing) algo.Log("-"); return isRolled; } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Debug($" ************** END OTM OPTIONS ROLL FOR {LUD.uSymbol} -- FAILED ROLL ****************"); if (LUD.doTracing) algo.Log("-"); return isRolled; } } else if (LUD.daysRemainingC <= 1 | LUD.daysRemainingP <= 1) { // CANNOT EXECUTE ROLL PROFITABLY SO KILL THE COLLAR IF ON LAST DAY if (annHighFade & LUD.doTracing) algo.Debug($" ************** FADE ROLLING {LUD.uSymbol.Value} due to price {stockPrice.ToString()} being above annual low close {lowBar.ToString()} minus 2."); killed = isRolled = algo.KillTheCollar(this, ref LUD, $"KILLED IN OTM PROCESSING -- UNPROFITABLE ROLL FOR {LUD.uSymbol} ON THE LAST DAY", false, true ); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Debug($" ************** END OTM OPTIONS ROLL FOR {LUD.uSymbol} WITH KILL ****************"); if (LUD.doTracing) algo.Log("-"); return isRolled; } if (LUD.doTracing) algo.Debug($" ************** END OTM OPTIONS ROLL PROCSSING FOR {LUD.uSymbol} ****************"); if (LUD.doTracing) algo.Log("------------------------------------"); LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); return isRolled; } else if (LUD.daysRemainingC <= 1 | LUD.daysRemainingP <= 1) { // IF BADTHRESH if (LUD.doTracing) algo.Debug($" ************** BEGIN OTM OPTIONS COLLAR KILL FOR {LUD.uSymbol} ON LAST DAY ****************"); // kill the collar killed = isRolled = algo.KillTheCollar(this, ref LUD, "BAD THRESH ON OTM OPTIONS ROLL", false, true); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Debug($" ************** END OTM OPTIONS ROLL WITH KILL ON BAD THRESH FOR {LUD.uSymbol} ****************"); if (LUD.doTracing) algo.Log("-------"); return isRolled; } // goodThresh on rolling OTM Options LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Debug($" ************** END OTM OPTIONS ROLL PROCESSING FOR {LUD.uSymbol} ****************"); if (LUD.doTracing) algo.Log("-"); return isRolled; } catch (Exception errMsg) { //algo.Debug(" ERROR TradeLogging.CheckOTMRoll.cs " + errMsg ); return false; //// 2022-02-15: replaced return with continue -- could have caused premature exits } } /// END OTM OPTIONS ROLL //************************************************************************************************* //************** CheckPutRoll ******************************************************* //************************************************************************************************* public bool CheckPutRoll (CollarAlgorithm algo , LookupData LUD, ref decimal stockPrice, ref decimal currCallBidPrice, ref decimal currPutAskPrice) { try{ bool isRolled = false; bool annHighFade = false; // Determine if it should be rolled forward. if (LUD.doTracing) algo.Log($" ************** BEGIN ITM PUT CALC FOR {LUD.uSymbol} ****************"); Slice slD = algo.CurrentSlice; //LUD.loadVEData(algo); /// load VE Data in Main.cs prior to calling tpr.CheckRolling SymbolData sd = LUD.intType==0 ? algo.symbolDataBySymbol[LUD.uSymbol] : algo.etfDataBySymbol[LUD.uSymbol]; if (LUD.intType==0 ) sd.decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget; /// set current 1-year price target for stocks. For ETFs use the price target set on the Last Day of Month /* var oneYearBars = sd.Bars.ToList(); var lowBar = oneYearBars.Min(b => b.Close); if (sd.decOneYearPriceTarget_Current < lowBar + 2m) annHighFade = true; //// fading on acheiving 1-year low is not supported by evidence in short trades if (LUD.doTracing & annHighFade) algo.Debug($" ************** FADE ROLLING {LUD.uSymbol.Value} due to price {stockPrice.ToString()} being above annual low close {lowBar.ToString()} minus 2."); if(LUD.intType!=0) { LUD.decOneYearPriceTarget = (1m + sd.decMOMP/100m) * stockPrice; LUD.stockPrice = stockPrice; isRolled = algo.GetETFMatrix(ref LUD, false); if (!isRolled) return isRolled; } //SSQRColumn bestSSQRColumn = LUD.intType==0 ? algo.GetBestCollar(algo, ref LUD) : LUD.SSQRMatrix.FirstOrDefault(); */ SSQRColumn bestSSQRColumn = new SSQRColumn(); if (LUD.intType==0) { bestSSQRColumn = algo.GetBestCollar(algo, ref LUD); } else bestSSQRColumn = LUD.SSQRMatrix.FirstOrDefault(); if (LUD.SSQRMatrix.Count == 0) { if (LUD.daysRemainingP <= 1) { if (LUD.doTracing) algo.Log($" ************** END ITM PUT FORCED ASSIGNMENT PROCESSING FOR {LUD.uSymbol} -- NO POTENTIAL COLLARS ON LAST DAY *************"); isRolled = algo.KillTheCollar(this, ref LUD, "KILL ITM PUT ASSIGNMENT -- NO POTENTIAL COLLARS ON LAST DAY", false, true); LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.intType == 0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (isRolled) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } if (LUD.doTracing) algo.Log($" ************** END CHECK EXPLICIT PUT ASSIGNMENT FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log("-----"); } if (LUD.doTracing) algo.Log($" ************** END ITM PUT CALC FOR {LUD.uSymbol} -- NO POTCOLS ***"); return isRolled; // if no collars then return and loop around again } if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty() ) { if (LUD.daysRemainingP <= 1) { // if at the last day of put expiration and haven't yet rolled, kill the collar. if (LUD.doTracing) algo.Log($" ********* KILL 1st TPR ON LAST DAY OF ITM PUT PROCESSING FOR {LUD.uSymbol} *************"); isRolled = algo.KillTheCollar(this, ref LUD, "KILLED IN ITM PUT ASSIGNMENT -- EMPTY BEST COLLARS ON LAST DAY", false, true); if (LUD.intType == 0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (isRolled) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log($" ************** END CHECK IMPLICIT PUT ASSIGNMENT FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log("--------"); return isRolled; } else { if (LUD.doTracing) algo.Log($" ********* END ITM PUT FORCED ASSIGNMENT PROCESSING FOR {LUD.uSymbol} -- bestSSQR null or empty LOOPING TO TRY AGAIN"); LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); return isRolled; // loop around and try again } } Symbol tradablePut = bestSSQRColumn.putSymbol; Symbol tradableCall = bestSSQRColumn.callSymbol; bool goodThresh = ((((LUD.divdndAmt * 3m) + LUD.decOneYearPriceTarget) < .95m * stockPrice) | algo.symbolDataBySymbol[this.uSymbol].continueTrade); /// roll put before ITM exercise if indicators dictate continue the trade down 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 (currPutAskPrice!=0 && currPutAskPrice + bestSSQRColumn.putStrike > stockPrice) // make sure that no one can buy the option for less than the stock { if (LUD.doTracing) algo.Log($"@@@@@@@@@@@@@@@@@@@ ITM PUT ROLL ABORT FOR {LUD.uSymbol} -- IMMEDIATE CALL-EXERCISE PREVENTION FADE @@@@@@@@@@@@@@@@@@@@@@@"); if (LUD.doTracing) algo.Log("@@@@@@@@@@@@@@@@@@@ CALL ASK: " + currPutAskPrice + " Strike: " + bestSSQRColumn.putStrike + " Stock Price: " + stockPrice +" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (LUD.doTracing) algo.Log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (LUD.doTracing) algo.Log("------"); if (LUD.daysRemainingP <= 1) { isRolled = algo.KillTheCollar(this, ref LUD, "ABORT ITM PUT ROLL TO PREVENT SUBSEQUENT PUT ASSIGNMENT", false, true); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (isRolled) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); return isRolled; } if (LUD.doTracing) algo.Log($" ************** BEGIN ITM PUT ROLL FOR {LUD.uSymbol} ****************"); //if (!newRollDate.Equals(oldRollDate)) { bool bRollable = LUD.intType==0 ? algo.symbolDataBySymbol[LUD.uSymbol].isRollable : algo.etfDataBySymbol[LUD.uSymbol].isRollable; bool isKilled = false; if (!annHighFade && bRollable && (this.currSellPnL > 0 | this.uQty * bestSSQRColumn.upsidePotential > Math.Abs(this.currSellPnL))) { // Roll solely if we can sell the current collar profitably //if (currSellPnL > 0 ) { // Roll solely if we can sell the current collar profitably if (LUD.doTracing) algo.Debug($" *** *** *** *** *** {LUD.uSymbol.Value} selling is more profitable or new collar can make more money than selling."); if (algo.RollTheCollar(LUD, this, ref bestSSQRColumn, "ROLLED -- ITM PUT NEAR EXPIRATION")) { isRolled = true; if (LUD.doTracing) algo.Log($" ************** ROLLED ITM PUTS COMPLETED FOR {LUD.uSymbol} ****************"); var orderedSSQRMatrix = LUD.SSQRMatrix.OrderBy(p => p.upsidePotential); // 2021-03-21 -- changed from OrderedByDescending algo.IterateOrderedSSQRMatrix(orderedSSQRMatrix); // didTheTrade = false; LUD.SSQRMatrix.Clear(); return isRolled; } else { if (LUD.daysRemainingP <= 1) { isKilled = algo.KillTheCollar(this, ref LUD, "KILLED IN FAILED ITM PUT ROLL", false, true); if(LUD.intType==0){ if( isRolled & algo.symbolDataBySymbol.ContainsKey(this.uSymbol)) { algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (isKilled) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } LUD.SSQRMatrix.Clear(); return isKilled; } } } else { // un profitable roll if (LUD.daysRemainingP <= 1) { //if (annHighFade & LUD.doTracing) algo.Debug($" ************** FADE ROLLING {LUD.uSymbol.Value} due to price {stockPrice.ToString()} being below annual low close {lowBar.ToString()} minus 2."); if (LUD.doTracing) algo.Log($" ************** UNPROFITABLE ITM PUT ROLL FOR {LUD.uSymbol} ON LAST DAY -- ATTEMPT KILL"); isKilled = algo.KillTheCollar(this, ref LUD, "KILL- LOSS IN 1st TPR IN ITM PUT ROLL", false, true); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (isKilled) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log($" TT END CHECK IMPLICIT PUT ASSIGNMENT FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log("-----"); } return isRolled; // exit OnData and try again until last day } else { // bad threshhold on ITM PUT ROLL -- EXERCISE IT if (LUD.daysRemainingP <= 1) { if (LUD.doTracing) algo.Log($" ************** BAD SSQR THRESHOLD IN ITM PUT ROLL FOR {LUD.uSymbol} ON LAST DAY -- ATTEMPT KILL"); isRolled = algo.KillTheCollar(this, ref LUD, "KILL ON LAST DAY OF ITM PUT ", false, true); if (LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if(isRolled){ algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log($" ************** END ITM PUT CALC FOR {LUD.uSymbol} ****************"); if (LUD.doTracing) algo.Log("---------"); return isRolled; // roll around and try again } } catch (Exception errMsg) { algo.Debug(" ERROR in TradeLogging.CheckPutRoll.cs " + errMsg ); return false; //// 2022-02-15: replaced return with continue -- could have caused premature exits } } // end CheckPutRoll //************************************************************************************************* //************** CheckDividendRoll ******************************************************* //************************************************************************************************* private bool CheckDivRoll(CollarAlgorithm algo, ref decimal stockPrice, LookupData LUD) { int daysRemaining = LUD.daysRemainingDiv; bool annHighFade = false; if (LUD.doTracing) algo.Debug("//************** CheckDividendRoll ****************************" ); try{ Slice slc = algo.CurrentSlice; bool isRolled = false; string strCorrSpndngPut = this.GetCorrspndngPut(); Symbol symbCorrSpndngPut = strCorrSpndngPut; decimal decCrrSpndgPutPrice = 0m; if (algo.Securities.TryGetValue(symbCorrSpndngPut, out var cpSecurity)) { if (!cpSecurity.IsTradable) { if (LUD.doTracing) algo.Log(" ************** TPR CheckDividendRol found no tradable corresponding put " + symbCorrSpndngPut.Value + " on " + slc.Time.ToString() ); if (LUD.doTracing) algo.Log(" ************** *************** Adding Put Contract" ); algo.AddOptionContract(symbCorrSpndngPut, Resolution.Minute, true, 0m, false); return false; } //if (LUD.doTracing) algo.Log(" ************** TPR CheckDividendRol found tradable corresponding put " + symbCorrSpndngPut.Value + " on " + slc.Time.ToString() + " @ " + string.Format("{0,5:C2}", decCrrSpndgPutPrice ) ); decCrrSpndgPutPrice = cpSecurity.AskPrice; //if (LUD.doTracing) algo.Log(" ************** TPR CheckDividendRol found tradable corresponding put " + symbCorrSpndngPut.Value + " on " + slc.Time.ToString() + " @ " + decCrrSpndgPutPrice ) ; } else { if (LUD.doTracing) algo.Log(" ************** TPR CheckDividendRol found no Put securities data for corresponding put " + symbCorrSpndngPut.Value + " on " + slc.Time.ToString() ); if (LUD.doTracing) algo.Log(" ************** *************** Adding Put Contract" ); algo.AddOptionContract(symbCorrSpndngPut, Resolution.Minute, true, 0m, false); return false; } if (decCrrSpndgPutPrice < LUD.divdndAmt) { if (LUD.doTracing) algo.Log(" ************** BEGIN APPROACHMENT CALC FOR " + this.uSymbol + " priced @" ); ///+ algo.Securities[this.uSymbol].Price ); if (LUD.doTracing) algo.Log(" ************** EX-Date: " + LUD.exDivdnDate.ToString() ); if (LUD.doTracing) algo.Log(" ************** DIVIDEND " + LUD.divdndAmt.ToString() + " Extrinsic Value: " + decCrrSpndgPutPrice.ToString() ); //bestSSQRColumn = GetBestSSQR(data, LUD.uSymbol, nextExDate); // this is the normal route of non-delta execution SSQRColumn bestSSQRColumn = new SSQRColumn(); LUD.loadVEData(algo); SymbolData sd = algo.symbolDataBySymbol[LUD.uSymbol]; sd.decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget; var oneYearBars = sd.Bars.ToList(); var highBar = oneYearBars.Max(b => b.Close); if (LUD.decOneYearPriceTarget > highBar - 2m) annHighFade = true; bestSSQRColumn = algo.GetBestCollar(algo, ref LUD); 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 (LUD.doTracing) algo.Log(" OOOOOOOOOOOO NO bestSSQR ON LAST DAY OF DIVIDEND-FORCED EXERCISE -- KILL THE COLLAR OOOOOOOOOO"); isRolled = algo.KillTheCollar(this, ref LUD, "KILLED -- NO bestSSQR ON LAST DAY OF DIVIDEND-APPROACHMENT", false, true ); LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } if (LUD.doTracing) algo.Log("************** END APPROACHMENT PROCESSING ******************"); if (LUD.doTracing) algo.Log("-"); return isRolled; // Don't execute further processing in this slice if rolled due to dividend approachment } else { if (LUD.doTracing) algo.Log("************** END DIV APPROACHMENT PROCESSING -- NULL bestSSQR -- TRY AGAIN ******************"); if (LUD.doTracing) algo.Log("-"); return isRolled; // Exit CheckDivRoll if there's no SSQR Column to process but don't move onto CallExpiryEvaluation for this reason } } if (!bestSSQRColumn.IsEmpty() ) { //TimeSpan expireDateDeltaSSQR = bestSSQRColumn.putExpiry.Subtract(slD.Time); //goodThresh = (bestSSQRColumn.CCOR >= CCORThresh); //bool goodThresh = (LUD.intVERating == 5 & LUD.decOneYearPriceTarget > 1.05m * stockPrice) | (LUD.intVERating > 3 & bestSSQRColumn.upsidePotential >=5); bool goodThresh = (((LUD.divdndAmt * 3m) + LUD.decOneYearPriceTarget) < .95m * stockPrice); if (goodThresh) // roll the position forward { TimeSpan expireDateDeltaSSQR = bestSSQRColumn.putExpiry.Subtract(slc.Time); 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 (LUD.doTracing) algo.Log(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ DIVIDEND EXERCISE ROLL ABORT -- CALL PREVENTION @@@@@@@@@@@@@@@@@@@@@@@"); if (LUD.doTracing) algo.Log(" @@@@@@@@@@@@@@@@@@@ CALL ASK: " + slc[this.cSymbol].Price + " Strike: " + bestSSQRColumn.callStrike + " Stock Price: " + slc[LUD.uSymbol].Price +" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (LUD.doTracing) algo.Log(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@"); if (LUD.doTracing) algo.Log("-"); if (LUD.doTracing) algo.Log("************** END APPROACHMENT PROCESSING ******************"); if (LUD.doTracing) algo.Log("-"); LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); // DO NOT KILL THE COLLAR HERE. return isRolled; // Exit CheckDivRoll if there's no SSQR Column to process but don't move onto CallExpiryEvaluation for this reason } if (LUD.doTracing) algo.Log(" ************** BEGIN DIV APPROACHMENT ROLL ****************"); //iterate potetialCollars here solely when executing a trade if (!annHighFade && (this.currSellPnL > 0 | this.uQty * bestSSQRColumn.upsidePotential > Math.Abs(this.currSellPnL))) { // Roll solely if we can sell the current collar profitably bool didTheTrade = algo.RollTheCollar(LUD, this, ref bestSSQRColumn, "ROLLED ON DIVIDEND APPROACHMENT"); if (didTheTrade) { isRolled = true; //oldRollDate = slc.Time.Date; // set the oldRollDate to Date of Roll if (LUD.doTracing) algo.Log(" ************** SUCCESSFUL DIV APPROACHMENT ROLL WITH SSQR: "); var orderedSSQRMatrix = LUD.SSQRMatrix.OrderByDescending(p => p.upsidePotential); algo.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 { isRolled = algo.KillTheCollar(this, ref LUD, "KILL- FAILED ROLL 1ST TPR IN DIVIDEND-FORCED EXERCISE ON LAST DAY", false, true ); // KillTheCollar may return to try again as well if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } return didTheTrade; } else if (annHighFade & LUD.doTracing) algo.Debug($" ************** FADE ROLLING {LUD.uSymbol.Value} due to price {stockPrice.ToString()} being above annual low close {highBar.ToString()} minus 2."); LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log("************** END APPROACHMENT PROCESSING ******************"); if (LUD.doTracing) algo.Log("-"); if (LUD.doTracing) algo.Log(" ************** END DIV APPROACHMENT ROLL ****************"); if (LUD.doTracing) algo.Log("-"); return isRolled; // 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 (LUD.doTracing) algo.Log(" OOOOOOOOOOOO BAD THRESH ON DIVIDEND-FORCED EXERCISE -- KILL THE COLLAR ON LAST DAY OOOOOOOOOO"); isRolled = algo.KillTheCollar(this, ref LUD, "KILL- LAST DAY BAD THRESH ON DIVIDEND-FORCED EXERCISE", false, true ); // KillTheCollar may return to try again as well LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } if (LUD.doTracing) algo.Log("************** END APPROACHMENT PROCESSING ******************"); if (LUD.doTracing) algo.Log("-"); return isRolled; } else { if (LUD.doTracing) algo.Log(" OOOOOOOOOOOO BAD THRESH ON DIVIDEND-FORCED EXERCISE -- RETURN AND TRY AGAIN"); } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log("************** END APPROACHMENT PROCESSING ******************"); if (LUD.doTracing) algo.Log("-"); return isRolled; // Don't execute further processing in this slice if rolled due to dividend approachment } // not goodThresh } // !bestSSQRColumn /// there was no bestSSQRColumn } /// *** decCrrSpndgPutPrice > LUD.divdndAmt return isRolled; } catch (Exception errMsg) { // algo.Debug(" ERROR TradeLogging.CheckDivRoll.cs " + errMsg ); return false; //// 2022-02-15: replaced return with continue -- could have caused premature exits } } //************************************************************************************************* //************** CheckCallRoll ******************************************************* //************************************************************************************************* public bool CheckCallRoll (CollarAlgorithm algo, LookupData LUD, ref decimal stockPrice, ref decimal currCallAskPrice, ref decimal currPutBidPrice) { try{ // Determine if it should be rolled forward. bool isRolled = false; bool killed = false; bool annHighFade = false; Slice slD = algo.CurrentSlice; if (LUD.doTracing) algo.Log($" ************** BEGIN ITM CALL CALC FOR {LUD.uSymbol} ****************"); //LUD.loadVEData(algo); ////**** **** **** 2023-03-12 do this in Main.cs for stocks only. For ETFs there is no VE data SymbolData sd = LUD.intType==0 ? algo.symbolDataBySymbol[LUD.uSymbol] : algo.etfDataBySymbol[LUD.uSymbol]; if(LUD.intType==0) sd.decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget; var oneYearBars = sd.Bars.ToList(); var highBar = oneYearBars.Max(b => b.Close); if (sd.decOneYearPriceTarget_Current > highBar - 2m) annHighFade = true; if(LUD.intType!=0) { LUD.decOneYearPriceTarget = (1m + sd.decMOMP/100m) * stockPrice; LUD.stockPrice = stockPrice; isRolled = algo.GetETFMatrix(ref LUD, false); if (!isRolled) return isRolled; } //bestSSQRColumn = GetBestSSQR(data, LUD.uSymbol, nextExDate); //SSQRColumn bestSSQRColumn = LUD.intType==0 ? algo.GetBestCollar(algo, ref LUD) : LUD.SSQRMatrix.FirstOrDefault(); SSQRColumn bestSSQRColumn = new SSQRColumn(); if (LUD.intType==0) { bestSSQRColumn = algo.GetBestCollar(algo, ref LUD); } else bestSSQRColumn = LUD.SSQRMatrix.FirstOrDefault(); if (LUD.SSQRMatrix.Count == 0) { if (LUD.daysRemainingC <= 1) { // if at the last day of call expiration and haven't yet rolled, kill the collar. if (LUD.doTracing) algo.Log($" ********* END ITM CALL EVALUATION PROCESSING FOR {LUD.uSymbol} -- NO POTENTIAL COLLARS ON LAST DAY *************"); isRolled = killed = algo.KillTheCollar(this, ref LUD, "KILL IN ITM CALL EVALUATION -- NO POTENTIAL COLLARS ON LAST DAY", false, true); LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.intType == 0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } if (LUD.doTracing) algo.Log($" OOOOOOOOO TT END CHECK EVALUATION FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log("-----"); } // algo.Log($" ************** END ITM CALL CALC PROCESSING FOR {LUD.uSymbol} -- NO MATRICES ***"); return isRolled; // if no collars then return and loop around again } if (bestSSQRColumn == null || bestSSQRColumn.IsEmpty() ) { if (LUD.daysRemainingC <= 1) { // if at the last day of call expiration and haven't yet rolled, kill the collar. if (LUD.doTracing) algo.Log($" ********* END ITM CALL FORCED ASSIGNMENT PROCESSING FOR {LUD.uSymbol} -- bestSSQR null or empty ON LAST DAY"); killed = algo.KillTheCollar(this, ref LUD, "KILL IN ITM CALL EVALUATION -- EMPTY BEST COLLAR ON LAST DAY", false, true); LUD.SSQRMatrix.Clear(); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log($" TT END CHECK CALL EVALUATION FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log("-"); } else { if (LUD.doTracing) algo.Log($" ************** END ITM CALL CALC FOR {LUD.uSymbol} -- null bestSSQRColumn **** -- RETURN AND TRY AGAIN -- *********"); LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); } return isRolled; // return and exit OnData() } //bool goodThresh = (LUD.intVERating == 5 & LUD.decOneYearPriceTarget > 1.05m * stockPrice) | (LUD.intVERating > 3 & bestSSQRColumn.upsidePotential >=5); bool goodThresh = (((LUD.divdndAmt * 3m) + LUD.decOneYearPriceTarget) < .95m * stockPrice); if (goodThresh) // roll the position forward { if (LUD.doTracing) algo.Log($" ************** BEGIN ITM CALL ROLL FOR {LUD.uSymbol} ****************"); //decimal stockPrice = slD[bestSSQRColumn.uSymbol].Price; // 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 (currPutBidPrice!=0 &&bestSSQRColumn.putStrike - currPutBidPrice > stockPrice) // make sure that no one can buy the option for less than the stock { if (LUD.doTracing) algo.Log($"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ABORT ITM CALL ROLL TO PREVENT EXERCISE FOR {LUD.uSymbol} @@@@@@@@@@@"); if (LUD.doTracing) algo.Log("@@@@@@@@@@@@@@@@@@@ CALL ASK: " + slD[bestSSQRColumn.putSymbol].Price + " Strike: " + bestSSQRColumn.putStrike + " Stock Price: " + stockPrice +" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (LUD.doTracing) algo.Log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); if (LUD.doTracing) algo.Log("-"); if (LUD.daysRemainingC <= 1) // Risk of Dividend Assignment too high at Ex-Dividend Date so if haven't been able to get at RollTheCollar, kill it here { isRolled = killed = algo.KillTheCollar(this, ref LUD, "ABORT ITM CALL ROLL TO PREVENT EXERCISE ON LAST DAY", false, true ); // KillTheCollar may return to try again as well if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } } LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log($" TT END CHECK IMPLICIT CALL EXERCISE FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log($" ------------------------------------------------ "); return isRolled; } bool bRollable = LUD.intType==0 ? algo.symbolDataBySymbol[LUD.uSymbol].isRollable : algo.etfDataBySymbol[LUD.uSymbol].isRollable; if (!annHighFade && bRollable && (this.currSellPnL > 0 | this.uQty * bestSSQRColumn.upsidePotential > Math.Abs(this.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. isRolled = algo.RollTheCollar(LUD, this,ref bestSSQRColumn, "ROLLED -- ITM CALL EXPIRATION APPROACHMENT"); if (isRolled) { if (LUD.doTracing) algo.Log($" ************** ROLLED ITM CALLS COMPLETED FOR {LUD.uSymbol}*************"); var orderedSSQRMatrix = LUD.SSQRMatrix.OrderBy(p => p.upsidePotential); algo.IterateOrderedSSQRMatrix(orderedSSQRMatrix); //didTheTrade = false; LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); return isRolled; } else if (LUD.daysRemainingC <= 1) { if (LUD.doTracing) algo.Log($" ************** UNSUCCESSFUL ROLL FOR {LUD.uSymbol} -- KILL ITM PUT COLLAR ON LAST DAY **************"); killed = algo.KillTheCollar(this, ref LUD, "KILL- FAILED ROLL ON LAST DAY" , false, true); // Goto KillTheCollar and determine whether to close or allow CALL EXERCISE there LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } if (LUD.doTracing) algo.Log($" TT END CHECK ITM CALL EVALUATION FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log($" ------------------------------------------------ "); return isRolled; } else if (LUD.daysRemainingC <= 1) { if (LUD.doTracing) algo.Log($" ************** UNPROFITABLE ROLL FOR {LUD.uSymbol} -- KILL ITM PUT COLLAR ON LAST DAY **************"); killed = algo.KillTheCollar(this, ref LUD, "KILL- LOSS IN 1st PACKAGE AND UNPROFITABLE 2nd PACKAGE" , false, true); // Goto KillTheCollar and determine whether to close or allow CALL EXERCISE there LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if(LUD.intType==0) { if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (killed ){ algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } if (LUD.doTracing) algo.Log($" TT END CHECK ITM CALL EVALUATION FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log($" ------------------------------------------------ "); return isRolled; } } 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 (LUD.doTracing) algo.Log($" ************** END ITM CALL EVALUATION PROCESSING FOR {LUD.uSymbol}-- BAD THRESH ****************"); if (LUD.daysRemainingC <= 1) { if (LUD.doTracing) algo.Log($" ************** KILL COLLAR IN ITM CALL EVALUTAION PROCESSING FOR {LUD.uSymbol} -- BAD THRESH ON LAST DAY"); algo.KillTheCollar(this, ref LUD, "KILL ITM CALL -- PREVENT PUT ASSIGNMENT", false, true); if(LUD.intType==0){ if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; //algo.SymbolsToRemove.Add(this.uSymbol); } } else if (isRolled) { algo.etfDataBySymbol[this.uSymbol].isRollable = false; // algo.SymbolsToRemove.Add(this.uSymbol); } } LUD.SSQRMatrix.Clear();if (LUD.doTracing) bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log($" TT END CHECK IMPLICIT PUT EXERCISE FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log($" ------------------------------------------------ "); return isRolled; // exit OnData() and loop around again until last day. May get assigned! //ExerciseOption(shortedCallSymbol, Decimal.ToInt32(this.cQty)); // LEAN error, cannot exercise short options } } // end CheckCallRoll function LUD.SSQRMatrix.Clear(); bestSSQRColumn = new SSQRColumn(); if (LUD.doTracing) algo.Log($" TT END CHECK IMPLICIT CALL EXERCISE FOR {LUD.uSymbol} OOOOOOOOO"); if (LUD.doTracing) algo.Log($" ------------------------------------------------ "); return false; // endif ITM calls -10 -> Expiry. Probably do an elseif {ITM puts here to definitively trap all assignments } catch (Exception errMsg) { algo.Debug(" ERROR TradeLogging.CheckCallRoll.cs " + errMsg ); return false; //// 2022-02-15: replaced return with continue -- could have caused premature exits } } /// end CheckCallRoll //************************************************************************************************* //************** CloseTPR ******************************************************* //************************************************************************************************* public void CloseTPR() { this.isOpen = false; // effectively closes the tpr. called in looping through tprsToClose after processing them. } //************************************************************************************************* //************** CloseTPR ******************************************************* //************************************************************************************************* public void OpenTPR() { this.isOpen = true; // effectively opens the tpr. called in looping through tprsToOpen after processing them. } } /// ***** END CLASS TradePerformanceRecord 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 int counter = 0; // iterate this for processing } } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public bool IsOrphaned(Symbol testSymb){ Option testOpt; foreach(var kvp in Portfolio.Securities) { var security = kvp.Value; if (security.Invested && security.Type == SecurityType.Option ) { Debug($" |||| |||| |||| Testing: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}"); testOpt = (Option)security; if (testOpt.Right == OptionRight.Call & testOpt.Underlying.Symbol.Equals(testSymb)){ Debug($" |||| |||| |||| HOLDINGS: {testOpt.Right}: has underlying {testOpt.Underlying.Symbol.Value} not orphaned."); return false; } } } Debug($" |||| **** ||||| **** {testSymb.Value} is Orphaned."); return true; } public partial class TradePerfRec { public bool CheckRollingDown(CollarAlgorithm algo, LookupData LD){ try{ bool hasPut = false; bool hasCall = false; bool isCollar = false; Slice slc = algo.CurrentSlice; Symbol symbUndr = this.uSymbol; LD.uSymbol = this.uSymbol; int putCount = 0; decimal strikeStep = 0; //if (symbUndr.Value == "CNP") { // algo.Debug(" --- --- This is CNP Processing"); //} string strTckr = symbUndr.Value; decimal stkPrc = 0m; decimal putPrc = 0m; decimal callPrc = 0m; int monthsRemaining = 0; if(LD.intType!=0 && !algo.symbolDataBySymbol.ContainsKey(this.uSymbol)) { if(LD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is no longer in SDBS! *^*^*^*^ Check order flow *^*^*^"); algo.tprsToClose.Add(this); return false; } if(LD.intType!=0 && !algo.symbolDataBySymbol[this.uSymbol].isRollable){ if(LD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is not rollable Down -- check if this TPR is going to be closed."); return false; } if (LD.doTracing) algo.Log($" ************** TPR CheckRollingDown for {symbUndr.Value} @ {slc.Time.ToString()}" ); //if (slc.ContainsKey(symbUndr) ) if (algo.Securities.ContainsKey(symbUndr) ) { // var tryPrice = slc[symbUndr].Price; var tryPrice = algo.Securities[symbUndr].Price; if (tryPrice == null) { if (LD.doTracing) algo.Log(" ************** TPR CheckRollingDown found no price data for " + symbUndr.Value + " @" + slc.Time.ToString() ); return false; } stkPrc = Convert.ToDecimal(tryPrice); } else if(LD.doTracing){ algo.Log(" ************** TPR CheckRollingDown found no slice data for " + symbUndr.Value + " @" + slc.Time.ToString() ); return false; } if (this.cQty != 0){ if (!slc.OptionChains.TryGetValue(this.cSymbol.Canonical, out var cOptChain)) { if (LD.doTracing) algo.Log(" ************** TPR CheckRollingDown found no Call chains data for " + this.cSymbol.Value + " @" + slc.Time.ToString() ); return false; } else if (cOptChain.Contracts.TryGetValue(this.cSymbol, out var cContract)){ callPrc = cContract.BidPrice; } else { if (LD.doTracing) algo.Log(" ************** TPR CheckRollingDown found no Call Contract data for " + this.cSymbol.Value + " @" + slc.Time.ToString() ); return false; } hasCall = true; } if (this.pQty != 0){ if (!slc.OptionChains.TryGetValue(this.pSymbol.Canonical, out var pOptChain)) { if (LD.doTracing) algo.Log(" ************** TPR CheckRollingDown found no PUT chains data for " + this.pSymbol.Value + " @" + slc.Time.ToString() ); return false; } else if (pOptChain.Contracts.TryGetValue(this.pSymbol, out var pContract)){ putPrc = pContract.AskPrice; } else { if (LD.doTracing) algo.Log(" ************** TPR CheckRollingDown found no PUT Contract data for " + this.pSymbol.Value + " @" + slc.Time.ToString() ); return false; } if (LD.doTracing) algo.Log(" ************** TPR CheckRollingDown HAS A PUT Contract data for " + this.pSymbol.Value + " @" + slc.Time.ToString() ); hasPut = true; } var upChains = slc.OptionChains.get(algo.symbolDataBySymbol[this.uSymbol].optSymbol); if(upChains == null) { if(LD.doTracing) algo.Log($" ************** TPR CheckRollingDown failed to locate Contract data for {symbUndr.Value} @ {slc.Time.ToString()}" ); return false; } var atmCall = upChains.Where(o=>o.Right == OptionRight.Call) .OrderBy(o=> Math.Abs(o.Strike - stkPrc)) .FirstOrDefault(); var highCall = upChains.Where(o=>o.Right == OptionRight.Call) .OrderByDescending(o=>o.Strike) .FirstOrDefault(); var lowCall = upChains.Where(o=>o.Right == OptionRight.Call) .OrderBy(o=>o.Strike) .FirstOrDefault(); var strikesList = upChains.DistinctBy(o => o.Strike).ToList(); if (strikesList.Count() == 1){ strikeStep = 2.5m; } else { strikeStep = (highCall.Strike - lowCall.Strike)/(strikesList.Count() - 1m); } if (strikeStep % 0.5m != 0) strikeStep = Math.Round(strikeStep/0.5m) * 0.5m; if (strikeStep == 1) strikeStep = 2; if(LD.doTracing) algo.Log($" ***tprtprtpr**** TPR strikeStep equals: {strikeStep.ToString()}" ); if(algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate.Year > this.endDate.Year) { monthsRemaining = algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate.Month + 12 - this.endDate.Month; } else { monthsRemaining = algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate.Month - this.endDate.Month; } //LD.clearLD(algo); /// //// Already cleared in Main.cs OnData()... Don't clear here, will reset intType LD.loadVEData(algo); decimal oneYrTrgtPrice_Current = algo.symbolDataBySymbol[this.uSymbol].decOneYearPriceTarget_Current = LD.decOneYearPriceTarget; decimal oneYrTrgtPrice_Initial = algo.symbolDataBySymbol[this.uSymbol].decOneYearPriceTarget_Initial; decimal trgtPrice = oneYrTrgtPrice_Initial < oneYrTrgtPrice_Current ? oneYrTrgtPrice_Initial : oneYrTrgtPrice_Current; if (trgtPrice == 0) trgtPrice = oneYrTrgtPrice_Initial; /// //// Occasionally the VE file does not have a target price prediction /// //// //// //// SHOULD THIS CODE AND CHECK PRECEDE THE TRYGET OPTIONS CHAIN CODE /// /// /// ///// ??? ??? ??? if (stkPrc < 1.03M * trgtPrice && !algo.symbolDataBySymbol[this.uSymbol].ContinueTrade(algo)){ // /// // THE STOCK HAS DEPRECIATED TO WITHIN 97% OF INITIAL OR CURRENT TARGET PRICE (BELOW 1.03*TARGET) AND ALMA-X AND TSI CROSSED UP bool forceKill = true; this.GetPnLs(algo, LD, ref stkPrc, ref callPrc, ref putPrc); if(LD.doTracing) algo.Log($" ************** ************** {this.uSymbol.Value} decreased more than 97% of {trgtPrice.ToString("0.00")} target and no continuing"); bool didKill = algo.KillTheCollar(this, ref LD, "Attained 97% of current target price and no indication to continue.", forceKill, true); if(LD.intType==0 && algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & didKill){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; algo.SymbolsToRemove.Add(this.uSymbol); if (LD.doTracing) algo.Log( " tprtprtprtprtprtprtpr --- logging RollDown Attainment Kill --- tprtprtrptrptrptpr"); if (LD.doTracing) algo.Log( $" tprtprtprtprtprtprtpr --- ---- TPR.EndDate {this.expDate.ToString()} StockPrice: {stkPrc.ToFinancialFigures()} "); } return true; } OptionContract perkPut = null; if (monthsRemaining > 2) monthsRemaining = 2; if (LD.doTracing) algo.Log(" tprtprtprtprtprtprtpr --- logging downChains --- tprtprtrptrptrptpr"); if (LD.doTracing) algo.Log($" tprtprtprtprtprtprtpr --- ---- TPR.EndDate {this.expDate.ToString()} StockPrice: {stkPrc.ToFinancialFigures()} "); // foreach (var option in upChains){ // algo.Log($" symbol: {option.Symbol.Value} Expiry: {option.Expiry} Strike: {option.Strike}"); // } var targetCall = upChains.Where(o=>o.Right == OptionRight.Call & o.Strike >= atmCall.Strike & o.Strike < this.cStrike - strikeStep & /// //// 2024-01-7 :: make sure new strike is less than DateTime.Compare(o.Expiry, slc.Time.AddMonths(monthsRemaining))<=0 & o.AskPrice - callPrc <= 0.3m * (this.uStartPrice - stkPrc)) .OrderByDescending(o=>o.Expiry) .ThenBy(o=>Math.Abs(atmCall.Strike - o.Strike)) .FirstOrDefault(); if (targetCall == null || targetCall.Strike >= this.cStrike){ if (LD.doDeepTracing) algo.Log(" tprtprtprtprtprtprtpr --- ---- ---- Roll Down Target call not found 1X -- decrement to 1 month."); targetCall = upChains.Where(o=>o.Right == OptionRight.Call & o.Strike >= atmCall.Strike & o.Strike < this.cStrike & /// //// 2024-01-7 :: make sure new strike is less than DateTime.Compare(o.Expiry, slc.Time.AddMonths(1))>=0 & o.AskPrice - callPrc <= 0.3m * (this.uStartPrice - stkPrc)) .OrderByDescending(o=>o.Expiry) .ThenBy(o=>Math.Abs(atmCall.Strike - o.Strike)) .FirstOrDefault(); if (targetCall == null){ if (LD.doDeepTracing) algo.Log(" tprtprtprtprtprtprtpr --- ---- ---- Roll Down Target Call not found 2X -- exit."); return false; } } if( this.cSymbol.Equals(targetCall) | (hasPut && (this.pStrike > stkPrc | DateTime.Compare(this.pSymbol.ID.Date, algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate) < 0))) { // have an ITM put or a put that has a short expiry perkPut = upChains.Where( o=> o.Right == OptionRight.Put && DateTime.Compare(o.Expiry, algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate) <= 0 & // get close, but don't exceed 1 year target. Get as much call premium (theta) as possible o.Strike >= oneYrTrgtPrice_Current + strikeStep & o.Strike < targetCall.Strike) .OrderByDescending(o=>o.Expiry) .ThenBy(o=>Math.Abs(o.Strike-trgtPrice)).FirstOrDefault(); isCollar = true; } else if (!hasPut) { if (stkPrc < 1.03m * trgtPrice & !hasPut) /// //// changed criteria from trgtPrice+4 to 1.03* { if(LD.doTracing) algo.Log($" ************** ************** {this.uSymbol.Value} is within 2 months of PUT expiration adn attained greater than 105% of {trgtPrice.ToString("0.00")}. Write a put, capture profit "); perkPut = upChains.Where(o=>o.Right == OptionRight.Put & o.Strike <= stkPrc & o.Strike >= .95M * trgtPrice & o.Strike < targetCall.Strike & DateTime.Compare(o.Expiry, algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate)<0) .OrderByDescending(o=>o.Expiry) .ThenByDescending(o=> Math.Abs(o.Strike - trgtPrice)) .FirstOrDefault(); //if(perkPut != null) hasPut = true; } } SSQRColumn thisSSQRColumn = algo.buildSSQRColumn(perkPut, targetCall, algo, LD); if (thisSSQRColumn != null) { if (thisSSQRColumn.callSymbol == null){ algo.Log($" @@@@@ @@@@@ tprtprtprtpr -- null put in column {targetCall.Symbol.Value} for {this.uSymbol.Value}"); return false; } LD.SSQRMatrix.Add(thisSSQRColumn); } else if(LD.doTracing) algo.Debug($"@@@@@ @@@@@ -- failed to get VE4 SSSQRColumn for {LD.uSymbol}."); if (LD.doDeepTracing) algo.Log($" tprtprtprtprtprtprtpr --- ---- Calling RollCallDown for {LD.uSymbol} because the current price {stkPrc.ToString()} is less than start: {this.uStartPrice.ToString()}. "); //algo.RollPutUp(targetPut, perkCall, ref LD, this, stkPrc); algo.didTheTrade = algo.RollCallDown(thisSSQRColumn, ref LD, this, stkPrc, isCollar); //var orderedSSQRMatrix = LD.SSQRMatrix.OrderBy(p => p.upsidePotential); var orderedSSQRMatrix = LD.SSQRMatrix.OrderBy(p => p.ROR); algo.IterateOrderedSSQRMatrix(orderedSSQRMatrix); return algo.didTheTrade; } catch(Exception e) { algo.Debug($" CheckRollingDown Error {e}"); return algo.didTheTrade; } } public bool HandleCollapse(CollarAlgorithm algo, LookupData LUD, SymbolData sd){ bool hasPut = false; bool hasCall = false; bool isCollar = false; OrderTicket handlePutTicket; OrderTicket handleCallTicket; OrderTicket handleStockTicket; Slice slc = algo.CurrentSlice; Symbol symbUndr = this.uSymbol; LUD.uSymbol = this.uSymbol; string strTckr = symbUndr.Value; decimal stkPrc = 0m; decimal putPrc = 0m; decimal callPrc = 0m; if(LUD.doTracing) algo.Log($" ****** Handling Symbol {this.uSymbol.Value} price rise. {algo.Securities[this.uSymbol].Price} is more than {this.cStrike.ToString("0.00")}"); if(LUD.intType !=0 &&!algo.symbolDataBySymbol.ContainsKey(this.uSymbol)) { if(LUD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is no longer in SDBS! *^*^*^*^ Check order flow *^*^*^"); algo.tprsToClose.Add(this); return false; } stkPrc = LUD.stockPrice; if(this.pSymbol != null){ var tryPPrice = algo.Securities[this.pSymbol].Price; if (tryPPrice == null) { if (LUD.doTracing) algo.Log(" ************** TPR Handle Meltup found no put price data for " + symbUndr.Value + " @" + slc.Time.ToString() ); return false; } putPrc = Convert.ToDecimal(tryPPrice); hasPut = true; } if(this.cSymbol != null){ var tryCPrice = algo.Securities[this.cSymbol].Price; if (tryCPrice == null) { if (LUD.doTracing) algo.Log(" ************** TPR Handle Meltup found no call price data for " + symbUndr.Value + " @" + slc.Time.ToString() ); return false; } callPrc = Convert.ToDecimal(tryCPrice); hasCall = true; } LUD.dtTst = slc.Time; //LUD.GetNextExDate(algo); //// get NextExDate for this symbol LUD.getNxtIExDt(this.cSymbol.Value, algo); this.GetPnLs(algo, LUD, ref stkPrc, ref callPrc, ref putPrc); string sSBB = sd.bbTrigger.ToString(); string sVDI = sd.VDI_Signal.ToString(); string sTSI = sd.tsi_trigger.ToString(); string sALMA = sd.almas_crossing.ToString(); string sTriggers = " SBB " + sSBB + " VDI " + sVDI + " TSI " + sTSI + " ALMA " + sALMA; if (hasCall){ //// sell or exercise put if (this.currExrcsCallPnL > this.currSellPnL) { if (LUD.doDeepTracing) algo.Log($" HC ** HC ** HC ** EXERCISE {this.cQty.ToString()} CALL contracts of {this.cSymbol} at {this.cStrike.ToString()}."); handleCallTicket = algo.ExerciseOption(this.cSymbol, this.cQty); /// underlying and calls will be closed in onOrder() event this.grossPnL = this.currExrcsCallPnL; /// log the PnL used in runtime decision this.uEndPrice = this.cStrike; /// 2023-04-15 :: forgot this in previous versions this.endDate = slc.Time; this.reasonForClose = $" Underlying Meltup EXERCISE CALL {this.uEndPrice.ToString()} above {this.cStrike.ToString()}." + sTriggers; if (hasPut){ if(algo.Securities[this.pSymbol].Price > 0.05m) { handlePutTicket = algo.MarketOrder(this.pSymbol, -this.pQty); if (handlePutTicket.Status == OrderStatus.Filled) { this.pEndPrice = handleCallTicket.AverageFillPrice; } if (LUD.doDeepTracing) algo.Log($" HC ** HC ** HC ** MARKET ORDER TO BUY {this.pQty.ToString()} contracts of {this.pSymbol.Value} at the market. Filled at {handleCallTicket.AverageFillPrice.ToString("0.00")}"); if (LUD.doDeepTracing) algo.Log("-"); } } return true; } else { handleCallTicket = algo.MarketOrder(this.cSymbol, -this.cQty); // buy the calls if (LUD.doDeepTracing) algo.Log(" HC ** HC ** HC ** MARKET ORDER TO SELL " + this.pQty.ToString() + " contracts of " + this.cSymbol + " at the market."); if (LUD.doDeepTracing) algo.Log("-"); if (handleCallTicket.Status == OrderStatus.Filled) { this.cEndPrice = handleCallTicket.AverageFillPrice; this.grossPnL = this.currSellPnL; /// log the PnL used in runtime decision } if (LUD.doDeepTracing) algo.Log(" HC ** HC ** HC ** MARKET ORDER TO SELL " + this.uQty.ToString() + " shares of " + this.uSymbol + " at the market."); handleStockTicket = algo.MarketOrder(this.uSymbol, -this.uQty); if (handleStockTicket.Status == OrderStatus.Filled) { this.uEndPrice = handleStockTicket.AverageFillPrice; this.reasonForClose = $" Underlying MeltUp SELL CALLS {this.uEndPrice.ToString()} below {this.pStrike.ToString()}." + sTriggers; this.endDate = slc.Time; } if (hasPut){ //// buy back short call handlePutTicket = algo.MarketOrder(this.pSymbol, -this.pQty); // buy the calls if (LUD.doDeepTracing) algo.Log(" HC ** HC ** HC ** MARKET ORDER TO BUY " + this.pQty.ToString() + " contracts of " + this.pSymbol + " at the market."); if (LUD.doDeepTracing) algo.Log("-"); if (handlePutTicket.Status == OrderStatus.Filled) { this.pEndPrice = handlePutTicket.AverageFillPrice; } } } } if(LUD.intType !=0 && algo.symbolDataBySymbol.ContainsKey(this.uSymbol)){ algo.symbolDataBySymbol[this.uSymbol].isRollable = false; algo.symbolDataBySymbol[this.uSymbol].isTriggered = false; algo.SymbolsToRemove.Add(this.uSymbol); } algo.tprsToClose.Add(this); return true; } } public string ConvertTradePerfRec(List<TradePerfRec> tPR) { string tPRString = ""; string jasonString = ""; int counter = 0; jasonString = "{"; tPRString = ",^^^"; TradePerfRec tprLister = new TradePerfRec(); var tprType = tprLister.GetType(); var tprProperties = tprType.GetFields(); foreach (var property in tprProperties) { tPRString = tPRString + ", " + property.Name; } Debug(tPRString); var tPREnum = tPR.GetEnumerator(); /////// NOTE: Have to get the JASON formatted correctly. Need one long string. CHECK THIS while (tPREnum.MoveNext()) { try { counter += 1; TradePerfRec thisPerfRec = tPREnum.Current; //Debug(String.Format("{0:000}: ", counter) + thisPerfRec.uSymbol.Value); jasonString = "{"; tPRString = ",^^^"; tPRString = tPRString + ", " + thisPerfRec.uSymbol.Value; 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 + ", " + "-no theta-"; if (thisPerfRec.pSymbol != null){ tPRString = tPRString + ", " + thisPerfRec.pSymbol.Value; } else { tPRString = tPRString + ", " + "-null-"; } if (thisPerfRec.cSymbol != null) { tPRString = tPRString + ", " + thisPerfRec.cSymbol.Value; } else { tPRString = tPRString + ", " + "-null-"; } // tPRString = tPRString + ", " + thisPerfRec.cSymbol!=null ? thisPerfRec.cSymbol.Value : "-null-"; tPRString = tPRString + ", " + "-null-"; 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 + ", " + String.Format("{0:0.00}", thisPerfRec.pGamma); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.cGamma); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.wcGamma); 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.currSellPnL); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.currExrcsPutPnL); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.currExrcsCallPnL); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.grossPnL); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.SSQRnetProfit); tPRString = tPRString + ", " + String.Format("{0:0 }", thisPerfRec.VERating); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.momentum); tPRString = tPRString + ", " + String.Format("{0:0.00}", thisPerfRec.oneYearPriceTarget); tPRString = tPRString + ", " + String.Format("{0:0 }", thisPerfRec.momentumRank); /*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); } catch (Exception errMsg) { Debug(" ERROR Converting TPR to JSON " + errMsg); continue; } } return jasonString; } } }
#region imports using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Data; #endregion namespace QuantConnect { class StockDataSource : BaseData { private const string LiveUrl = @"https://www.dropbox.com/s/rfvg9ce040she5y/ValuEngine%20Annual%20Universes.csv?dl=1"; private const string BacktestUrl = @"https://www.dropbox.com/s/rfvg9ce040she5y/ValuEngine%20Annual%20Universes.csv?dl=1"; /// <summary> /// The symbols to be selected /// </summary> public List<string> Symbols { get; set; } /// <summary> /// Required default constructor /// </summary> public StockDataSource() { // initialize our list to empty Symbols = new List<string>(); } /// <summary> /// Return the URL string source of the file. This will be converted to a stream /// </summary> /// <param name="config">Configuration object</param> /// <param name="date">Date of this source file</param> /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param> /// <returns>String URL of source file.</returns> public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode) { //var url = isLiveMode ? LiveUrl : BacktestUrl; var url = BacktestUrl; return new SubscriptionDataSource(url, SubscriptionTransportMedium.RemoteFile); } /// <summary> /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object /// each time it is called. The returned object is assumed to be time stamped in the config.ExchangeTimeZone. /// </summary> /// <param name="config">Subscription data config setup object</param> /// <param name="line">Line of the source document</param> /// <param name="date">Date of the requested data</param> /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param> /// <returns>Instance of the T:BaseData object generated by this line of the CSV</returns> public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode) { try { // create a new StockDataSource and set the symbol using config.Symbol var stocks = new StockDataSource {Symbol = config.Symbol}; // break our line into csv pieces var csv = line.ToCsv(); if (isLiveMode) { // our live mode format does not have a date in the first column, so use date parameter stocks.Time = date; stocks.Symbols.AddRange(csv); } else { // our backtest mode format has the first column as date, parse it stocks.Time = DateTime.ParseExact(csv[0], "yyyyMMdd", null); // any following comma separated values are symbols, save them off stocks.Symbols.AddRange(csv.Skip(1)); } return stocks; } // return null if we encounter any errors catch { return null; } } } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Algorithm.CSharp { public partial class CollarAlgorithm : QCAlgorithm { public void ProcessOpenLimitOrders(Slice sld) { try { if (doDeepTracing) 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); var option = (Option)Securities[olo.oTicket.Symbol]; string oloMsg = ""; if (doTracing) Debug($" |()|()|() Found a {olo.oTicket.Status} limit order for {olo.oTicket.Symbol} with limit price and {olo.oTicket.OrderEvents.ToString()} "); if (doTracing) Debug($" |()|()|() Limit Price {olo.oTicket.Get(OrderField.LimitPrice)} Last Price {Securities[olo.oTicket.Symbol].Price} "); olo.counter += 1; /// increment processing counter 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); continue; } TradePerfRec updateTPR = olo.tpr; if (olo.oTicket.Status == OrderStatus.Filled) { 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 closing Long Call -- rolling or closing a collar updateTPR.cEndPrice = olo.oTicket.AverageFillPrice; if (doTracing) Debug(" |()|()|() Found a filled collar closing long call limit order and updated end price to " + olo.oTicket.AverageFillPrice); } else { updateTPR.cStartPrice = olo.oTicket.AverageFillPrice; if (doTracing) Debug(" |()|()|() Found a filled collar opening long call limit order and updated end price to " + olo.oTicket.AverageFillPrice); } } } else if (olo.oRight == OptionRight.Put) { // never buy ITM Puts -- forces taxable "synthetic sale" of underlying //updateTPR.pEndPrice = olo.oTicket.AverageFillPrice; //if (doTracing) Debug(" |()|()|() Found a filled collar ending long put limit order and updated end price to " + olo.oTicket.AverageFillPrice); if (olo.oTicket.Quantity < 0 ) { // Sell order for Short Put -- initializing a collar updateTPR.pStartPrice = olo.oTicket.AverageFillPrice; if (doTracing) Debug(" |()|()|() Found a filled collar initiating short put limit order and updated start price to " + olo.oTicket.AverageFillPrice); } else { updateTPR.pEndPrice = olo.oTicket.AverageFillPrice; if (doTracing) Debug(" |()|()|() Found a filled collar ending short put limit order and updated end price to " + olo.oTicket.AverageFillPrice); } } if (doTracing) Debug(" |()|()|() marking olo at " + currentLO + " for removal "); RemovableLOs.Add(currentLO); continue; ///// ///// ///// ---- Convert Limit Orders to Market Orders if they are more than 15 minutes old } else if (olo.oTicket.Status == OrderStatus.Submitted & ((int)sld.Time.Subtract(olo.oTicket.Time).TotalMinutes > 5) | olo.counter >= 5 ){ if (olo.oRight == OptionRight.Call) { //var option = (Option)Securities[olo.oTicket.Symbol]; var orderUnderlyingPrice = option.Underlying.Price; 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 sellback limit order. This contition will exist if the underlying price has moved up olo.oTicket.Cancel(); if (doTracing) Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); if (doTracing) Debug(" |()|()|() updating " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + " limit order to market order"); if (doTracing) Debug(" |()|()|() OLO HANDLING"); 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 closing or rolling sell long call order updateTPR.cEndPrice = callTkt.AverageFillPrice; if (doTracing) Debug(" |()|()|() Converted and filled long call SELL TO CLOSE limit to market order and updated END price to " + callTkt.AverageFillPrice); } else { updateTPR.cEndPrice = callTkt.AverageFillPrice; if (doTracing) Debug(" |()|()|() Converted and filled short call BUY TO OPEN limit to market order and updated end price to " + callTkt.AverageFillPrice); } } if (doTracing) Debug(" |()|()|() marking converted olo at " + currentLO + " for removal "); RemovableLOs.Add(currentLO); } } else { if (doTracing) Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); if (doTracing) 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 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 olo.oTicket.Cancel(); if (doTracing) Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); if (doTracing) Debug(" |()|()|() converting " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + " limit order to market order"); if (doTracing) Debug(" |()|()|() IN OLO PROCESSING"); 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 TO OPEN 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 { if (doTracing) Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); if (doTracing) Debug(" |()|()|() waiting on " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + "limit order fulfillment."); } } // OptionRight == call or put // ticket = submitted } else if (olo.oTicket.Status == OrderStatus.Submitted) { if (olo.oRight == OptionRight.Call) { //var option = (Option)Securities[olo.oTicket.Symbol]; var orderUnderlyingPrice = option.Underlying.Price; var orderLimitPrice = olo.oTicket.Get(OrderField.LimitPrice); var orderStrikePrice = olo.oTicket.Symbol.ID.StrikePrice; // var lPrice = orderUnderlyingPrice - orderStrikePrice + 0.10M; var lPrice = option.BidPrice + ((option.AskPrice - option.BidPrice)/2); if (orderLimitPrice < orderUnderlyingPrice - orderStrikePrice + 0.50M) { /// this is the criteria for placing a call buyback limit order. This contition will exist if the underlying price has moved up by 50cents olo.oTicket.UpdateLimitPrice(lPrice); /// update the limit price for the order to track the market. olo.counter = 0; /// reset the counter to try new limit price if (doTracing) Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); if (doTracing) Debug(" |()|()|() updating " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + " limit order to market order"); if (doTracing) Debug(" |()|()|() IN MAIN INVESTING"); } } else if (olo.oRight == OptionRight.Put) { //var option = (Option)Securities[olo.oTicket.Symbol]; var orderUnderlyingPrice = option.Underlying.Price; var orderLimitPrice = olo.oTicket.Get(OrderField.LimitPrice); var orderStrikePrice = olo.oTicket.Symbol.ID.StrikePrice; // var lPrice = orderStrikePrice - orderUnderlyingPrice + 0.10M; var lPrice = option.BidPrice + ((option.AskPrice - option.BidPrice)/2); if (orderLimitPrice > orderStrikePrice - orderUnderlyingPrice + 0.50M) { olo.oTicket.UpdateLimitPrice(lPrice); olo.counter = 0; /// reset the counter to try new limit price if (doTracing) Debug(" |()|()|() With " + olo.oTicket.Symbol.ID.Underlying.Symbol + " trading at " + orderUnderlyingPrice + " and limit price set to " + orderLimitPrice); if (doTracing) Debug(" |()|()|() updating " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + " limit order to market order"); if (doTracing) Debug(" |()|()|() IN MAIN INVESTING"); } } } } // 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); } } } 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); } } } } }