| Overall Statistics |
|
Total Trades 88 Average Win 0.13% Average Loss -0.07% Compounding Annual Return 1.593% Drawdown 0.600% Expectancy -0.159 Net Profit 1.593% Sharpe Ratio 1.147 Probabilistic Sharpe Ratio 56.578% Loss Rate 71% Win Rate 29% Profit-Loss Ratio 1.87 Alpha 0.007 Beta 0.042 Annual Standard Deviation 0.01 Annual Variance 0 Information Ratio -0.733 Tracking Error 0.105 Treynor Ratio 0.265 Total Fees $1275.00 Estimated Strategy Capacity $0 Lowest Capacity Asset TD WHEA9CSDEWZQ|TD R735QTJ8XC9X |
#region imports
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using QuantConnect.Data;
using QuantConnect.Util;
//using QuantConnect.Interfaces;
using QuantConnect.Securities;
using QuantConnect.Indicators;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
public partial class CollarAlgorithm : QCAlgorithm
{
public class LookupData {
// ********************** DividendRecord **************************************
// *** This structure contains the dividend information necessary to calculate trading
// *** decision. It is used to build a List<DividendRecord> that can be searched to
// *** produce the nextExDivDate and dividend amount along with VE data
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 List<SSQRColumn> SSQRMatrix = new List<SSQRColumn>();
public Symbol uSymbol; // underlying symbol in current processing
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 DateTime lastUpdated;
public List<DividendRecord> exDividendDates = new List<DividendRecord>();
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 int intMomentumRank;
public DateTime initialTargetEndDate;
public void InitializeData(QCAlgorithm algo)
{
this.exDividendDates = this.GetDividendDates(algo);
if (exDividendDates == null) algo.Log("|||||||||||||||||| MISSING DIV 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)
{
if (haltProcessing) {
//algo.Log(" HALTED IN loadVEData");
}
DateTime sliceTime = algo.CurrentSlice.Time;
//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)
.OrderBy(d => d.VEDate)
.FirstOrDefault();
///
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.intVERating = nextExDateRec.VERating;
this.decMomentum = nextExDateRec.momentum;
this.decOneMonthForecast = nextExDateRec.oneMonthForecast;
this.decOneYearPriceTarget = nextExDateRec.oneYearPriceTarget;
this.intMomentumRank = nextExDateRec.momentumRank;
this.initialTargetEndDate = nextExDateRec.VEDate.AddYears(1);
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.intVERating = 0;
this.decMomentum = 0;
this.decOneMonthForecast = 0;
this.decOneYearPriceTarget = 0;
this.intMomentumRank = 0;
this.initialTargetEndDate = default(DateTime);
this.exDivdnDate = default(DateTime);
this.divdndAmt = 0m;
//this.divdnFrequency = nextExDateRec.frequency;
}
//DateTime sliceTime = algo.CurrentSlice.Time;
// ********************** getNextExDate **************************************
// *** Use this to find and return the next ex-dividend date from
// *** the list exDividendDates given a Slice.DateTime
// ***********************************************************************************
public DateTime getBNDNxtExDt(string tickStr, DateTime sliceTime, List<DividendRecord> exDivRecs)
{
// // /// /// 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
// DividendRecord nextExDateRec = this.exDividendDates.Where(d => DateTime.Compare(sliceTime.Date, d.exDate.Date)<=0 &&
// // // //// NOTE: The LINQ will return the next ExDate whenever that may be in the future.
DividendRecord nextExDateRec = exDivRecs.Where(d => DateTime.Compare(sliceTime.Date, d.exDate.Date)<=0 &&
d.ticker == tickStr)
.OrderBy(d => d.exDate)
.FirstOrDefault();
// DateTime nextExDate = nextExDateRec.exDate;
return nextExDateRec.exDate;
}
// ********************** 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");
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)
{
// 2020-9-25 9:24 https://www.dropbox.com/s/ap8s120gksb858h/DividendData.csv?dl=1
// 2020-09-25 8:11 https://www.dropbox.com/s/ap8s120gksb858h/DividendData.csv?dl=1
// 2020-09-25 8:09 https://www.dropbox.com/sh/05qjk3o3y53fp4i/AAA6fEJg8J50xMQWm5nlg7M4a?dl=1 -- zip file
// 2021-01-14 8:33 var csvFile = Download("https://www.dropbox.com/s/ap8s120gksb858h/DividendData.csv?dl=1");
//var csvFile = algo.Download("https://www.dropbox.com/s/jv0aaajwsw8auwo/FiveYearDividends.csv?dl=1");
// 2022-11-13 : new ValueEngine/IEX MOS-linked fused Dividend-Price-Protections data
// var csvFile = algo.Download("https://www.dropbox.com/s/2ywhdbptls0ifn3/Dividend_Price_Projections.csv?dl=1");
// 2023-01-26 : new IEX/ValuEngine MOS-Right JOIN fused Dividend-Price-Protections data
var csvFile = algo.Download("https://www.dropbox.com/s/9ruqvhxixps96nt/Dividend_Price_Projections_RightJoin.csv?dl=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();
parsed = DateTime.TryParse(values[0], out exDateResult);
if (!parsed) {
divRec.exDate = default(DateTime);
// continue;
} else {
divRec.exDate = exDateResult;
}
parsed = DateTime.TryParse(values[1], out VEDateResult);
if (!parsed) {
continue;
} else {
divRec.VEDate = VEDateResult;
}
divRec.ticker = values[2];
if (values[3]=="Null" | values[3]=="") {
divRec.divAmt = 0;
} else {
divRec.divAmt = Convert.ToDecimal(values[3]);
}
divRec.frequency = values[4];
if (values[6]=="Null" | values[6]=="") {
divRec.VERating = 0;
} else {
divRec.VERating = Convert.ToInt32(values[6]);
//divRec.VERating = Int32.TryParse(values[5], out VERateResult);
}
if (values[7]=="Null" | values[7]=="") {
divRec.marketPrice = 0;
} else {
divRec.marketPrice = Convert.ToDecimal(values[7]);
}
if (values[8]=="Null" | values[8]=="") {
divRec.momentum = 0;
} else {
//divRec.momentum = Convert.ToDecimal(values[7]);
parsed = Decimal.TryParse(values[8], out VERateResult);
if (!parsed){
divRec.momentum = 0;
} else {
divRec.momentum = VERateResult;
}
}
/*if (string.IsNullOrEmpty(values[8])) {
divRec.oneMonthForecat = 0;
} else {
divRec.oneMonthForecat = Convert.ToDecimal(values[8]);
} */
if (values[9]=="Null" | values[9]=="") {
divRec.oneYearPriceTarget = 0;
} else {
divRec.oneYearPriceTarget = Convert.ToDecimal(values[9]);
}
if (values[10]=="Null" | values[10]=="") {
divRec.momentumRank = 0;
} else {
divRec.momentumRank = Convert.ToInt32(values[10]);
}
dividendDates.Add(divRec);
i++;
//algo.Log("i: " + i.ToString());
}
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.exDate.Date)<=0 &&
d.ticker == tickStr)
.OrderBy(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 (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.exDate.Date)<=0 &&
d.ticker == tickStr)
.OrderBy(d => d.exDate)
.FirstOrDefault();
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;
}
} /// end class lookupData
public class SymbolData {
private Symbol symbol;
public bool isRollable;
public bool currentPosition;
public Symbol optSymbol;
public int intTPRCntr = 0;
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 RollingWindow<decimal> closesWindow = new RollingWindow<decimal>(20);
public Momentum thisMOM = new Momentum(20);
//public Beta thisBeta = new Beta("wee",20, symbol, "SPY");
//private TradeBarConsolidator _MOMConsolidator;
public SymbolData(Symbol passedSymbol, bool rollable, bool position, Symbol symbOpt) {
symbol = passedSymbol;
isRollable = rollable;
currentPosition = position;
optSymbol = symbOpt;
intTPRCntr = 0;
}
} // end class SymbolData
public List<Symbol> tradedSymbols = new List<Symbol>();
}
}
#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.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
public partial class CollarAlgorithm : QCAlgorithm
{
// Initialize trade control variables used to intercept automated options exercise.
//public var uniThis;
int SecAddedCnt = 0;
int SecRemvdCnt = 0;
DateTime StartData;
DateTime EndData;
DateTime StartSDBS;
DateTime EndSDBS;
DateTime StartTPRs;
DateTime EndTPRs;
DateTime StartSecChng;
DateTime EndSecChng;
TimeSpan tspanData = new TimeSpan();
TimeSpan tspanSDBS = new TimeSpan();
TimeSpan tspanTPRs = new TimeSpan();
TimeSpan tspanSecChng = new TimeSpan();
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 doDeepTracing = false; // turn //if (doDeepTracing) Log() process tracing on/off
public static Symbol symbFilter = null;
public bool addedStocks = false;
public string filledOrdersForObjStore = ""; // string used to load orders into ObjectStore
//bool didTheTrade = false; // Flag that permits InterateOrderedSSQRMatrix only if a trade was done
OrderTicket closeCallTicket; // use this to track and manage collar rolling and killing trades
OrderTicket closePutTicket; // use this to track and manage collar rolling and killing trades
OrderTicket closeWCallTicket; // use this to track and manage collar rolling and killing trades
List<OpenLimitOrder> oLOs = new List<OpenLimitOrder>(); // maintain a list of open limit orders to manage
List<Symbol> SymbolsToRemove = new List<Symbol>();
bool iteratePortfolio = false; // Switch to toggle Iterating and Logging portfolio
decimal stockDollarValue; // get the dollar value for the position
decimal sharesToBuy = 5000; // Get the number shares to achieve the value
bool hasDividends = true; // Bool set (unset=false) to determine whether to add security to portfolio
decimal optionsToTrade; // Get the initial Options to trade (1/10th the sharesToBuy)
decimal callsToTrade; // Get the initial call options to trade in a variable call coverage strategy
//decimal maxPutOTM = 0.5M; // Instantiate and set maximum depth of PUT OTM -- percentage
int MinNmbrDivs = 1; // Instantiate and set minimum number of dividends acceptable in BestSSQRMatrix
decimal wingFactor = 0; // wing factor to multiply optionsToTrade to trade the wings
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;
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. // use this in OnData TPR-driven position updating
List<TradePerfRec> secTPRs = new List<TradePerfRec>();
List<TradePerfRec> thetaTPRs = new List<TradePerfRec>();
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();
decimal stockDividendAmount = 0M;
string divFrequency = "Quarterly";
decimal divPlotValue = 0M;
DateTime fmrNextExDate;
bool sellThePut = false; // ORDER MANAGEMENT CONTROL -- SET sellThePut whenever calls are exercised by LEAN
bool buyTheCall = false; // ORDER MANAGEMENT CONTROL -- SET buyThePut whenever puts are exercised by LEAN
// Holds multi ticker data
Dictionary<Symbol, SymbolData> symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
RollingWindow<decimal> closesWindow = new RollingWindow<decimal>(20);
// *** *** *** *** *** *** *** *** *** *** ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ *** *** *** *** *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** *** | PRICE AND DIVIDEND AND TRANSACTION PLOT |** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** *** ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ *** *** *** *** *** *** *** *** *** *** *** *** *** ***
// Instantiate and set plotting information
/*
Stochastic sto; // Stochastic
AccumulationDistribution ad; // Accumulation / Distribution
AccumulationDistributionOscillator adOsc; // Accumulation / Distribution Oscillator
AverageDirectionalIndex adx; // Average Directional Index
AverageDirectionalMovementIndexRating adxr; // Average Directional Index Rating
OnBalanceVolume obv; // On Balance Volumne indicator
Variance variance; // Variance of this stock
decimal lastSto; // store values from night before
decimal lastAd;
decimal lastAdOsc;
decimal lastAdx;
decimal lastAdxr;
decimal lastObv;
decimal lastVariance;
Chart stockPlot; // initialize Series Variables to reference during order processing and endofday plotting
Series buyOrders;
Series sellOrders;
Series rollOrders;
Series ptsOrders;
Series assetPrice;
Series varianceS;
Series stochastics;
Series dividendsS;
Series vixVals;
*/
// *** *** *** *** *** *** *** *** *** *** ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ *** *** *** *** *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** *** | END OF VARIABLE DECLARATION AND INSTANTIATION |** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** *** ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ ‾‾‾ *** *** *** *** *** *** *** *** *** *** *** *** *** ***
public override void Initialize()
{
DateTime startDate = DateTime.Parse(GetParameter("StartDate"));
DateTime endDate = DateTime.Parse(GetParameter("EndDate"));
SetStartDate(startDate.Year, startDate.Month, startDate.Day); //Set Start Date
SetEndDate(endDate.Year, endDate.Month, endDate.Day); // Set End Date
SetCash(10000000); ////==> set cash to $10,000,000 for VAR analysis //Set Strategy Cash $100,000,000 for ~500 positions @ $100,000 ea
//SetWarmup(TimeSpan.FromDays(31), Resolution.Daily);
UniverseSettings.Resolution = Resolution.Daily;
// Raw data for Equity
UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
// ABBV ADM BA BBY BMY CVS DOW GIS GM IBM IRM KO LVS M OHI OXY PM PG PSX QCOM SO T VZ WFC XOM
// Get RunTime control parameters for Minimum # of Dividends and Maximum OTM Put Depth
strFilterTkr = GetParameter("stockTicker");
if (strFilterTkr == "-"){ strFilterTkr = "";}
//if (strFilterTkr != "" ) {symbFilter = Symbol(strFilterTkr);}
ourUniverse = AddUniverse<StockDataSource>("VE-IEX-Combo", stockDataSource =>
{
return stockDataSource.SelectMany(x => x.Symbols).Take(30);
});
badDtParameter = GetParameter("CheckBadDate") == "true" ? true : false; // get this from parameters
stockDollarValue = Convert.ToDecimal(GetParameter("StockDollarValue"));
MinNmbrDivs = Convert.ToInt16(GetParameter("MinNmbrDivs")); // get and set minimum number of dividends acceptable in BestSSQRMatrix
useDeltas = GetParameter("UseDeltas") == "true" ? true : false; // get this from parameters
wingFactor = Convert.ToDecimal(GetParameter("wingFactor")); // get wing factor for multiplying optionsToTrade when putting on wing
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
// Chart - Master Container for the Chart:
/*
if (!string.IsNullOrEmpty(strFilterTkr)) {
stockPlot = new Chart("Stock Chart");
// On the Trade Plotter Chart we want 3 series: trades and price:
sto = STO(strFilterTkr, 14, Resolution.Daily); // Stochastic
//ad = AD(thisSymbol, Resolution.Daily); // Accumulation / Distribution
//adOsc = ADOSC(thisSymbol, 3, 14, Resolution.Daily); // Accumulation / Distribution Oscillator
adx = ADX(strFilterTkr, 7, Resolution.Daily); // Average Directional Index
adxr = ADXR(strFilterTkr, 7, Resolution.Daily); // Average Directional Index Rating
obv = OBV(strFilterTkr, Resolution.Daily); // On Balance Volume
variance = VAR(strFilterTkr, 14, Resolution.Daily); // Variance of this stock
buyOrders = new Series("Buys", SeriesType.Scatter, "$", Color.Green, ScatterMarkerSymbol.Triangle);
rollOrders = new Series("Rolls", SeriesType.Scatter, "$", Color.Blue, ScatterMarkerSymbol.Square);
ptsOrders = new Series("PTSs", SeriesType.Scatter, "$", Color.Crimson, ScatterMarkerSymbol.Square);
sellOrders = new Series("Sells", SeriesType.Scatter, "$", Color.Red, ScatterMarkerSymbol.TriangleDown);
dividendsS = new Series("Divs", SeriesType.Scatter, "$", Color.Pink, ScatterMarkerSymbol.Diamond);
assetPrice = new Series("EOD Price", SeriesType.Line, "$", Color.Purple);
varianceS = new Series("Variance", SeriesType.Line, "$", Color.Magenta);
assetPrice.Index = 0;
buyOrders.Index = 0;
rollOrders.Index = 0;
ptsOrders.Index = 0;
sellOrders.Index = 0;
dividendsS.Index = 0;
stockPlot.AddSeries(buyOrders);
stockPlot.AddSeries(rollOrders);
stockPlot.AddSeries(ptsOrders);
stockPlot.AddSeries(sellOrders);
stockPlot.AddSeries(dividendsS);
stockPlot.AddSeries(assetPrice);
AddChart(stockPlot);
}
*/
//SetSecurityInitializer(HistoricalSecurityInitializer);
//var uniThis = AddUniverse(CoarseSelectionFilter, FineSelectionFunction);
} // // // /// /// /// /// /// Initialize()
public void OnData(TradeBars tbData)
{
// Does this update the indicators?
}
public void OnData(Dividends dData) ///// //////// check this for completeness and cohesion with previous versions
{
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);
}
}
}
}
//if (doTracing) Debug(" DDDDDDDDDD DDDDDDDDDDD UPDATED " + k.ToString() + " TRADE PERF RECORDS DIVIDENDS. ");
//if (doTracing) Log("-");
//if (!string.IsNullOrEmpty(strFilterTkr)) Plot("Stock Chart", "Divs", divPlotValue);
}
} catch (Exception errMsg)
{
if (doTracing) Log(" DIV ERROR DIV ERROR DIV ERROR " + errMsg);
return;
}
}
public override void OnData(Slice data)
{
bool logPortfolio = false;
SecAddedCnt = SecRemvdCnt = 0;
// // // on the first trading day of each month in the 9:00 hour, build the candidate list
//if (data.Time.Minute % 15 != 0) return; // evaluate everything every 15 minutes
//if (data.Time.Minute != 30) return;
if (data.Time.Hour < 10) return;
if (IsMarketOpen("IBM")) {
if (oLOs != null && oLOs.Count > 0) ProcessOpenLimitOrders(data);
if(SymbolsToRemove.Count != 0) ProcessRemoveSecurities();
}
StartData = DateTime.Now;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FIRST -- CHECK FOR ADDED STOCKS
// UNIVERSE IS EVALUATED/LOADED MONTHLY AT MIDNIGHT ON 1ST DAY OF MONTH.
// THE FOLLOWING CODE EXECUTES SOLELY ON FIRST DAY OF MONTH -- DO IT BEFORE 10:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (IsMarketOpen("IBM") && addedStocks) {
addedStocks = false;
if (doTracing) Debug("--- --- Added Universe Members On: " + data.Time.ToString() + ", new Universe count is: " + ourUniverse.Members.Count); // logs S: 0
if (doTracing) {
foreach (Symbol s in ourUniverse.Members.Keys) {
if (s.Value == "HLT") SymbolsToRemove.Add(s);
Debug("--- --- " + s.Value);
}
}
foreach(var sdbs in symbolDataBySymbol) {
Symbol symbOIChk = sdbs.Key;
if(!data.ContainsKey(symbOIChk)) continue;
LUD.uSymbol = symbOIChk;
SymbolData symbDat = sdbs.Value;
OptionChain chnOICheck;
if ((symbDat.openInterestCheck == false) & (data.OptionChains.TryGetValue(symbDat.optSymbol, out chnOICheck))) {
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();
decThisOI = atmPutContract_r.OpenInterest + atmCallContract_r.OpenInterest;
if (doTracing) Debug("--- " + symbOIChk.Value + " has Open Interest of: " + decThisOI.ToString() + " contracts");
if((decThisOI < decOIThresh)) {
SymbolsToRemove.Add(atmCallContract_r.UnderlyingSymbol);
continue;
}
symbDat.openInterestCheck = true;
}
}
} else { /// addedStocks
//Debug("--- --- OnData: " + data.Time.ToShortDateString() + " @ " + data.Time.ToShortTimeString());
}
if (SymbolsToRemove.Count != 0 ) ProcessRemoveSecurities();
if (CheckBadDate(data.Time))
{
LUD.haltProcessing = true;
//Debug(" @@@@@@ BAD DATE @@@@@@@@@@ The price of " + thisSymbol + " is " + data[thisSymbol].Price);
foreach(var kvp in Securities)
{
var security = kvp.Value;
if (security.Invested)
{
//Debug($" |-|-|-|- HOLDINGS: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}");
}
}
} else LUD.haltProcessing = false;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SECOND -- CHECK TPRS TO MANAGE EXISTING PACKAGES
// ITERATE THROUGH TPRS AND EVALUATE OPPORTUNITIES EVERY 5 MINUTES AFTER 10:00 EST
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (data.Time.Minute % 10 !=0) return;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SECOND A -- PROCESS STOCK Collapse
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int k = 0;
if (tradeRecs.Any(tpr=> tpr!=null && tpr.isOpen && (Securities[tpr.uSymbol].Price < 0.99M * tpr.pStrike)) ) { //// careful here. only primary and secondary (non-theta) tprs have uSymbols
foreach(var tprec in tradeRecs.Where(tpr=> tpr.isOpen && (Securities[tpr.uSymbol].Price < 0.95M * Securities[tpr.pSymbol].BidPrice))) {
if (doDeepTracing) Log($" tprtprtprtprtprtprtpr --- ---- Calling HandleCollapse for {LUD.uSymbol} because the current price {Securities[tprec.uSymbol].Price.ToString()} is less than 99% Putstrike: {tprec.pStrike.ToString()}. ");
LUD.clearLD(this);
LUD.uSymbol = tprec.uSymbol;
if (!tprec.HandleCollapse(this, LUD)) continue;
k = k + 1;
}
}
//Debug("--- Starting TPR processing On: " + data.Time.ToShortDateString() + " @ " + data.Time.ToLongTimeString()); // logs S: 0
if (tprsToClose.Any(tpr=> tpr!=null)) { /// 2021-10-18 -- close TPRs here
tprsToClose.ForEach(tpr=>tpr.CloseTPR());
}
tprsToClose.Clear();
k = 0;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SECOND B -- PROCESS ROLL UPS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
k = 0;
if (tradeRecs.Any(tpr=> tpr!=null && tpr.isOpen && ((Securities[tpr.uSymbol].Price < 100) & (Securities[tpr.uSymbol].Price-tpr.uStartPrice>=5m)) | ((Securities[tpr.uSymbol].Price>=100) & (Securities[tpr.uSymbol].Price-tpr.uStartPrice>=15m)))) { //// careful here. only primary and secondary (non-theta) tprs have uSymbols
foreach(var tprec in tradeRecs.Where(tpr=> tpr.isOpen && (((Securities[tpr.uSymbol].Price<100) & Securities[tpr.uSymbol].Price - tpr.uStartPrice>=5m) | ((Securities[tpr.uSymbol].Price>=100) & (Securities[tpr.uSymbol].Price-tpr.uStartPrice>=15m))))) {
if (doDeepTracing) Log($" tprtprtprtprtprtprtpr --- ---- Calling RollPutUP for {LUD.uSymbol} because the current price {Securities[tprec.uSymbol].Price.ToString()} is greater than start: {tprec.uStartPrice.ToString()}. ");
LUD.clearLD(this);
LUD.uSymbol = tprec.uSymbol;
if (!tprec.CheckRollingUp(this, LUD)) continue;
k = k + 1;
}
}
//Debug("--- Starting TPR processing On: " + data.Time.ToShortDateString() + " @ " + data.Time.ToLongTimeString()); // logs S: 0
if (tprsToClose.Any(tpr=> tpr!=null)) { /// 2021-10-18 -- close TPRs here
tprsToClose.ForEach(tpr=>tpr.CloseTPR());
}
tprsToClose.Clear();
k = 0;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SECOND 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)) {
LUD.clearLD(this);
LUD.uSymbol = tprec.uSymbol;
if (!tprec.CheckRolling(this, LUD)) continue; /// 2021-10-18 -- modified TradeDetermination.cs so never sets .isClose=true. & never add and new TPR there **** NOTE: 2023-01-07::modified to continue loop and not increment k if tpr doesn't roll/kill collar
k = k + 1; /// 2023-01-07 continued :: previously, this had a break and the first successful roll/kill would end processing of any subsequent TPR's.
}
}
if (tprsToClose.Any(tpr=> tpr!=null)) { /// 2021-10-18 -- close TPRs here
tprsToClose.ForEach(tpr=>tpr.CloseTPR());
}
tprsToClose.Clear();
try{
if(tprsToOpen.Any(tpr=>tpr!=null)) { /// 2021-10-18 -- open TPRs here
foreach(var tprec in tprsToOpen) {
Debug(" --- ---- ---- ---- ----- --- Adding new TPR " + tprec.uSymbol.Value);
tprec.OpenTPR();
tradeRecs.Add(tprec);
}
}
} catch (Exception msg) {
Debug(" --- ---- ---- ---- ----- --- ERROR ADDING NEW TPR " + msg.ToString());
}
tprsToOpen.Clear();
///////////////////////////////////////////////////////////////////////////////////////////////////////
// THIRD -- 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
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// try to establish new positions. Check each viable SDBS prospect to attempt package initialization for un-invested Symbols
if (data.Time.Minute % 20 !=0) return;
// Log("*** Processing Slice Data on " + data.Time.ToShortDateString() + " at: " + data.Time.ToLongTimeString());
//Debug("--- --- Starting SDBS Processing @ " + data.Time.ToShortTimeString() + " the count is: " + symbolDataBySymbol.Count); // logs S: 0
if (symbolDataBySymbol.Any(sdbs => sdbs.Value.isRollable && sdbs.Value.openInterestCheck && !Portfolio[sdbs.Key].Invested))
{
StartSDBS = DateTime.Now;
try{
if (doTracing) Log($" --- SCANNING PROSPECTIVE PORTFOLIO CANDIDATES AT {data.Time.ToString()}.");
foreach(var pair in symbolDataBySymbol) {
k +=1;
if (pair.Key == null) {
Debug(" --- ---- ---- Found null key in foreach sdbs.");
continue;
}
Symbol thisSymbol = pair.Key;
if (doTracing) Log($" --- ---- 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
LUD.clearLD(this);
LUD.uSymbol = thisSymbol;
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
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;
//pair.Value.decOneMonthForecast = LUD.decOneMonthForecast;
/*
if (LUD.exDivdnDate.Equals(DateTime.MinValue)) {
if (doDeepTracing) Debug("--- --- " + tickerString + "----- NULL nextExDate -----");
hasDividends = false;
continue;
} else hasDividends = true; */
hasDividends = true;
// if (data.ContainsKey(thisSymbol)) {
// stockPrice = data[thisSymbol].Price;
// } else {
stockPrice = Securities[thisSymbol].Price;
// }
if (stockPrice == 0) {
if (doDeepTracing) Log($" --- ---- --- Securities price for {thisSymbol.Value} is zero. ");
continue;
}
if ((LUD.divdndAmt * 4m) + LUD.decOneYearPriceTarget < 1.05m * stockPrice) {
SymbolsToRemove.Add(thisSymbol); // don't trade and remove any symbol that isn't set to apprciate at least 5%
if (doTracing) Debug($" *^*^*^*^*^**^*^**^ REMOVING {thisSymbol.Value} due to less than 5% potential yield.");
continue;
}
// if (divPlotValue == 0) { divPlotValue = stockPrice - 5; }
thisROC = 0;
thisROR = 0;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IF NOT INVESTED AT ALL OR IF LESS THAN
// QUARTER'S ALLOCATION, CREATE AND TEST
// POTENTIAL OPTIONS COLLARS AND IF
// GOOD, ESTABLISH A POSITION
// heldValue = Portfolio[thisSymbol].HoldingsValue; // get the dollar value of thisSecurity's positition
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if (LUD.haltProcessing) {
// Debug(" ok");
// }
// // /// LEGACY VALUE BASED SHARES TO BUY
// var x = stockDollarValue/stockPrice/100m;
// sharesToBuy = Math.Round(x, 0) * 100m;
if (hasDividends & symbolData.isRollable & symbolData.openInterestCheck)
{
bestSSQRColumn = GetBestCollar(this, 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($"*** *** EXECUTING TRADE FOR NEW BESTSSQRCOLUMN -- {thisSymbol} --- on " + data.Time.ToShortDateString() + " at " + data.Time.ToShortTimeString()) ;
ExecuteTrade(data, bestSSQRColumn, ref symbolData);
if (didTheTrade) {
logPortfolio = true;
if (doTracing) Log($"*** *** *** DID TRADE -- {thisSymbol} --- Based upon this SSQR matrix: ");
tradedSymbols.Add(thisSymbol);
// var orderedSSQRMatrix = LUD.SSQRMatrix.OrderByDescending(p => p.upsidePotential);
var orderedSSQRMatrix = LUD.SSQRMatrix.OrderByDescending(p => p.ROR);
IterateOrderedSSQRMatrix(orderedSSQRMatrix);
if (doTracing && logPortfolio)
{
Log($"|||| |||| TRADE RESULTS/HOLDINGS: on {data.Time.ToShortDateString()} at {data.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;
}
} else {
if (doTracing) Log($"*** *** *** DIDN'T TRADE - {thisSymbol} --- ");
}
} /// !bestSSQRColumn.IsEmpty()
LUD.clearLD(this);
}
} else { // buyMoreShares & hasDividends & symbolData.isRollable & symbolData.openInterestCheck
if (doDeepTracing) Log($" --- ---- --- --- {thisSymbol.Value} FAILED ROLLABLE or OI TEST. ");
}
} // end ForEach(var pair in symbolDataBySymbol)
} catch (Exception forExcp) {
Log($" Error Prospect Evaluation in foreach sdbs in SDBS " + forExcp.Message);
}
}
//Debug("--- --- Processed " + k.ToString() + " Symbols in SDBS in: " + tspanSDBS.TotalMilliseconds);
// EndData = EndTPRs = DateTime.Now;
// tspanData = EndData - StartData;
// tspanTPRs = EndTPRs - StartTPRs;
//Debug("--- Processing Times-> OnData: " + tspanData.TotalMilliseconds + " | SDBS: " + tspanSDBS.TotalMilliseconds + " | TPRs: " + tspanTPRs.TotalMilliseconds);
} // OnData()
public void ProcessRemoveSecurities()
{
if (SymbolsToRemove.Count != 0) {
foreach(var symbol_r in SymbolsToRemove) {
RemoveLowOI_OptSymbs(symbol_r);
//Liquidate(symbol_r);
if (!Portfolio[symbol_r].Invested)
{
RemoveSecurity(symbol_r);
symbolDataBySymbol.Remove(symbol_r);
//if (doTracing) Debug("--- --- ***" + SecRemvdCnt.ToString() + ": Removing " + symbol_r.Value + " Collar killed or no OI.");
}
}
SymbolsToRemove.Clear();
}
}
public void RemoveLowOI_OptSymbs(Symbol symbol) {
if (symbolDataBySymbol.ContainsKey(symbol)) {
if (Portfolio[symbolDataBySymbol[symbol].optSymbol].Invested) Debug($" *^*^*^*^**^*^*^ *^*^*^*^*^*^*^^* OPTION BEING LIQUIDATED: {symbolDataBySymbol[symbol].optSymbol.Value} ||||||");
RemoveSecurity(symbolDataBySymbol[symbol].optSymbol);
symbolDataBySymbol.Remove(symbol);
}
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
StartSecChng = DateTime.Now;
if (CurrentSlice.Time.Date.Day == 5 & CurrentSlice.Time.Hour == 9 & CurrentSlice.Time.Minute == 35) {
//Debug("Boom");
}
//if (doDeepTracing) Debug(" --- sss Processing Changed Securities on: " + CurrentSlice.Time.ToShortDateString() + " at " + CurrentSlice.Time.ToShortTimeString());
foreach(var security in changes.AddedSecurities) {
// ---- >> Alex uses var thisSymbol = security.Symbol;
//Debug("Securities updated at " + Slice.Time.ToString());
SecAddedCnt += 1;
Symbol thisSymbol = security.Symbol;
if (security.Type == SecurityType.Equity)
{
if (symbolDataBySymbol.ContainsKey(thisSymbol)) {
if (doDeepTracing) Debug("--- --- " + SecAddedCnt.ToString() + " SDBS already has " + thisSymbol.ToString());
continue;
}
addedStocks = true;
// var sym = AddEquity(thisSymbol, Resolution.Minute);
//if (doDeepTracing) Debug("--- --- --- " + SecAddedCnt.ToString() + " Adding " + thisSymbol.Value + " after Universe changes.");
var opt = AddOption(thisSymbol, Resolution.Minute, Market.USA, true, 0m);
opt.PriceModel = OptionPriceModels.BjerksundStensland(); /// necessary for Greeks
//opt.SetFilter(u => u.Strikes(-3, +3).Expiration(0, 270).IncludeWeeklys());
// opt.SetFilter(u => u.Strikes(-LUD.maxPutOTM, LUD.maxCallOTM).Expiration(0, 270)); //.IncludeWeeklys());
/*opt.SetFilter(universe => from symbol in universe //.IncludeWeeklys()
.Expiration(TimeSpan.Zero, TimeSpan.FromDays(270))
where universe.Underlying.Price>=100 ? Math.Abs(universe.Underlying.Price - symbol.ID.StrikePrice) <= 20 : Math.Abs(universe.Underlying.Price - symbol.ID.StrikePrice) <= 15 select symbol);
*/
opt.SetFilter(universe => from symbol in universe //.IncludeWeeklys()
.Expiration(TimeSpan.Zero, TimeSpan.FromDays(270))
where Math.Abs(universe.Underlying.Price - symbol.ID.StrikePrice) <= .2M * universe.Underlying.Price select symbol);
symbolDataBySymbol.Add(thisSymbol, new SymbolData(thisSymbol, true, false, opt.Symbol));
} //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);
}
}
}
EndSecChng = DateTime.Now;
tspanSecChng = EndSecChng - StartSecChng;
// if (doDeepTracing) Debug("--- Processing Times-> SecChanges: " + tspanSecChng.TotalMilliseconds);
}
/// Rahul said this is beta and possibly inactive
public override void OnAssignmentOrderEvent(OrderEvent assignmentEvent)
{
if (doTracing) Log("AAAAAAAAAAAAAA ASSIGNMENT ==== " + assignmentEvent.Symbol.Value);
}
// ********************** OnOrderEvent ***********************************************
// *** Generalized function to iterate through and print members of an IEnumerable of Contracts
// *** This is used for debugging only tricky part is passing an IOrderedEnumerable into this
// ****************************************************************************************************
public override void OnOrderEvent(OrderEvent orderEvent) {
var order = Transactions.GetOrderById(orderEvent.OrderId);
var oeSymb = orderEvent.Symbol;
if (haltProcessing) {
if (doDeepTracing) Log(" Logging ONORDER()");
}
if (doDeepTracing) Log(" OO +++ " + order.Type + " order for " + oeSymb + ", Order Status: " + orderEvent.Status);
try {
if (orderEvent.Status == OrderStatus.Filled)
{
//var order = Transactions.GetOrderById(orderEvent.OrderId);
//var oeSymb = orderEvent.Symbol;
if (order.Type == OrderType.OptionExercise)
{
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;
if (doDeepTracing) Log(" OO OPTIONS ORDER FOR : " + oeSymb + " IS A " + (oeSymb.ID.OptionRight == OptionRight.Put ? "PUT. " : "CALL.") + "for underlying: " + stkSymbol);
// Get the open tradePerfRecord (if any still exists) ??? what is the order of exercise events ???
// tradePerfRec Call termination handled in code prior to PUT EXERCISE
// Execute TradePerfRec Underlying termination in OnOrder() upon Stock Assignment
if(oeSymb.ID.OptionRight == OptionRight.Put)
{
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" oo PUT OPTION EXERCISE ORDER FOR : " + oeSymb);
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (tradeRecs.Any(t => t!=null && t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity)) {
var pTPR = tradeRecs.Where(t => t!=null && t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity).FirstOrDefault();
pTPR.pEndPrice = orderEvent.FillPrice;
if (doDeepTracing) Log($" OO OO OO UPDATED {oeSymb.Value} END PRICE TO : {orderEvent.FillPrice}.");
if (pTPR.cSymbol != null) {
var shrtCall = (Option)Securities[pTPR.cSymbol];
TimeSpan daysToCallExpiry = shrtCall.Expiry.Subtract(orderEvent.UtcTime);
/*if (daysToCallExpiry.Days > 10 ) {
if (doDeepTracing) 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.
tradeRecs.Add(newThTPR);
pTPR.cSymbol = null; // eliminate the call from the existint TPR
pTPR.cStartPrice = 0;
pTPR.cQty = 0;
} else { */
if (doDeepTracing) Log(" OO OO SELLING THE CALL IF IT EXISTS");
if (doDeepTracing) Log(" OO OO IN MAIN INVESTING");
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) {
//var wingCall = (Option)Securities[pTPR.wcSymbol];
var closeWingTkt = MarketOrder(pTPR.wcSymbol, -pTPR.wcQty);
if (closeWingTkt.Status == OrderStatus.Filled) {
pTPR.wcEndPrice = closeWingTkt.AverageFillPrice;
}
}
} else { // 1st TPR in PUT EXERCISE
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" oo PUT OPTION ORDER FOR : " + oeSymb);
if (doDeepTracing) Log(" oo NOT SURE HOW THIS WAS ACCESSED - NO 1st TPR FOUND ");
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
//string jsonString = ConvertTradePerfRec(tradeRecs);
/* if (tradeRecs.Any(t => t!=null && t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity)) {
var cTPR = tradeRecs.Where(t => t!=null && t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity).FirstOrDefault();
cTPR.cEndPrice = orderEvent.FillPrice;
if (doDeepTracing) Log(" OO UPDATED CALL END PRICE TO : " + orderEvent.FillPrice);
if (doDeepTracing) Log(" OO SELLING THE PUT IF IT EXISTS");
if (cTPR.cSymbol != null) {
var closePTkt = MarketOrder(cTPR.pSymbol, -cTPR.pQty);
if (closePTkt.Status == OrderStatus.Filled) {
cTPR.pEndPrice = closePTkt.AverageFillPrice;
}
}
} */
}
} else if (oeSymb.ID.OptionRight == OptionRight.Call){
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" oo CALL OPTION EXERCISE ORDER FOR : " + oeSymb);
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (tradeRecs.Any(t => t!=null && t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity)) {
if (doDeepTracing) Log(" oo oo FOUND SHORT CALL 1ST TPR oo ");
var cTPR = tradeRecs.Where(t => t!=null && t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity).FirstOrDefault();
cTPR.cEndPrice = orderEvent.FillPrice;
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 + " OPTIONS ASSIGNMENT -- UNDERLYING CLOSED";
} else cTPR.reasonForClose = cTPR.reasonForClose + " 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 : {orderEvent.FillPrice}.");
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 if (tradeRecs.Any(t => t!=null && t.wcSymbol.Equals(oeSymb) & t.wcQty == order.Quantity)) {
var wcTPR = tradeRecs.Where(t => t!=null && t.wcSymbol.Equals(oeSymb) & t.wcQty == order.Quantity).FirstOrDefault();
if (doDeepTracing) Log(" oo FOUND SHORT CALL 1ST TPR oo ");
if (doDeepTracing) Log(" oo UPDATED WING CALL END PRICE TO : " + orderEvent.FillPrice);
wcTPR.wcEndPrice = orderEvent.FillPrice;
}
}
} 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." );
if (haltProcessing) {
if (doDeepTracing) Log(" oo oo oo oo => Logging OnOrder() ");
}
didTheTrade = true;
if(tradeRecs.Any(t => t!=null && t.isOpen & t.uSymbol.Equals(oeSymb) & t.uQty == order.Quantity*100M)) { /// this failed on 2/6/23 to find tpr
if (doDeepTracing) Log(" oo UPDATING TPR -- UNDERLYING END PRICE AND DATE");
var uTPR = tradeRecs.Where(t => t!=null && t.isOpen & t.uSymbol.Equals(oeSymb) & t.uQty == order.Quantity*100M).FirstOrDefault();
//if (symbFilter != null) Plot("Stock Chart", "Sells", orderEvent.FillPrice);
tradeRecCount = 0; // reset tradeRec Counter ??? may be obviated
//uTPR.isOpen = false;
tprsToClose.Add(uTPR);
uTPR.uEndPrice = orderEvent.FillPrice;
uTPR.endDate = orderEvent.UtcTime;
if (uTPR.reasonForClose !=null || uTPR.reasonForClose != "") {
uTPR.reasonForClose = uTPR.reasonForClose + " OPTIONS ASSIGNMENT -- UNDERLYING CLOSED";
} else uTPR.reasonForClose = " OPTIONS ASSIGNMENT -- UNDERLYING CLOSED";
if (Portfolio[uTPR.pSymbol].Invested && uTPR.pSymbol != null) {
var sellPutTicket = MarketOrder(uTPR.pSymbol, -uTPR.pQty);
if (doDeepTracing) Log(" oo oo oo oo ooo Selling the PUT and setting the TPR.EndPrice");
if (sellPutTicket.Status == OrderStatus.Filled) {
uTPR.pEndPrice = sellPutTicket.AverageFillPrice;
}
}
if (Portfolio[uTPR.wcSymbol].Invested && uTPR.wcSymbol != null) {
var sellWCallTicket = MarketOrder(uTPR.wcSymbol, -uTPR.wcQty);
if (doDeepTracing) Log(" oo oo oo oo ooo Selling the Wing Call and setting the TPR.wcEndPrice");
if (sellWCallTicket.Status == OrderStatus.Filled) {
uTPR.wcEndPrice = sellWCallTicket.AverageFillPrice;
}
}
/// NOTE: OPTIONS WILL EXPIRE OR EXERCISE AT ENDPRICE = 0. THEREFORE THESE VALUES ARE NOT SET HERE
/// BECAUSE THE END PRICES MAY BE SET OTHERWISE ELSEWHERE
} else {
if (doDeepTracing) Log($" oo oo oo oo => FAILED TO LOCATE {oeSymb.Value} TPR THAT HAS {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 oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" OO PUT OPTION LIMIT ORDER FOR : " + oeSymb);
if (doDeepTracing) Log(" OO PROCESSING TPR IN NEXT ON DATA oo oo oo oo ");
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
/*if (tradeRecs.Any(t => t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity)) {
var transRec = tradeRecs.Where(t => t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity).FirstOrDefault();
transRec.pEndPrice = orderEvent.FillPrice;
if (doDeepTracing) Log(" OO ** Setting pEndPrice.");
} */
//if (doDeepTracing) Log(" OO NOTE PUT EXPIRATION execute a market order to sell underlying");
} else if (oeSymb.ID.OptionRight == OptionRight.Call) {
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" OO CALL OPTION LIMIT ORDER FOR : " + oeSymb);
if (doDeepTracing) Log(" OO PROCESSING TPR IN NEXT ON DATA oo oo oo oo ");
// //if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
/*if (tradeRecs.Any(t => t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity)) {
var transRec = tradeRecs.Where(t => t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity).FirstOrDefault();
transRec.cEndPrice = orderEvent.FillPrice;
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 oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" OO PUT OPTION MARKET ORDER FOR : " + oeSymb);
if (doDeepTracing) Log(" OO PROCESSING TPR SYNCHRONOUSLY IN LINE oo oo oo ");
// //if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
/*if (tradeRecs.Any(t => t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity)) {
var transRec = tradeRecs.Where(t => t.pSymbol.Equals(oeSymb) & t.pQty == -order.Quantity).FirstOrDefault();
transRec.pEndPrice = orderEvent.FillPrice;
if (doDeepTracing) Log(" OO ** Setting pEndPrice.");
} */
if (doDeepTracing) Log(" OO NOTE ALGO-DRIVEN PUT market order");
} else if (oeSymb.ID.OptionRight == OptionRight.Call) {
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" OO CALL OPTION MARKET ORDER FOR : " + oeSymb);
if (doDeepTracing) Log(" OO PROCESSING TPR SYNCHRONOUSLY IN LINE oo oo oo ");
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
/*if (tradeRecs.Any(t => t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity)) {
var transRec = tradeRecs.Where(t => t.cSymbol.Equals(oeSymb) & t.cQty == -order.Quantity).FirstOrDefault();
transRec.cEndPrice = orderEvent.FillPrice;
if (doDeepTracing) Log(" OO ** Setting cEndPrice.");
}*/
if (doDeepTracing) Log(" OO NOTE ALGO-DRIVEN CALL MARKET ORDER");
}
} else if (!oeSymb.HasUnderlying) { // limit order
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" OO UNDERLYING ORDER FOR : " + oeSymb);
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
} else { // NON EXERCISE ORDER HAS UNDERLYING
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (doDeepTracing) Log(" OO UNKNOWN ALGO ORDER ORDER FOR : " + oeSymb);
//if (doDeepTracing) Log(" oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo oo ");
if (tradeRecs.Any(tpr => tpr.uSymbol.Equals(oeSymb) & tpr.uQty == order.Quantity*100M)) {
var transRec = tradeRecs.Where(tpr => tpr.uSymbol.Equals(oeSymb) & tpr.uQty == order.Quantity*100M).FirstOrDefault();
Debug (" OO ** THERE IS A TPR THAT IS " + (transRec.isOpen ? " OPEN" : " CLOSED"));
transRec.isOpen = false;
transRec.uEndPrice = orderEvent.FillPrice;
transRec.endDate = orderEvent.UtcTime;
transRec.reasonForClose = " Options Expiration";
//Plot("Stock Chart", "Sells", orderEvent.FillPrice);
}
}
if (doDeepTracing) Log(" ---------------------------------------------------------------------------");
} // non exercise optoin order
} // orderStatus = Filled
} catch (Exception errMsg)
{
//if (doTracing) Log(" ERROR in OnOrder() Event " + errMsg );
// if (errMsg.Data.Count > 0) {
//if (doTracing) Log(" Extra details:" );
//foreach (DictionaryEntry de in errMsg.Data) if (doTracing) Log(" Key: {0,-20} Value: {1}'" + de.Key.ToString() + "'" + de.Value);
// }
return;
}
}
//private void CheckExpiration() {
// foreach(var callOption in callOptions) {
// if(callOption.Symbol.ID.Date == Time.Date) {
// //if (doDeepTracing) Log($"{callOption.Symbol}");
// }
//}
// }
class StockDataSource : BaseData
{
//private const string LiveUrl = @"https://www.dropbox.com/s/ciukaohnkh0ip6n/ValuEngine%20Monthly%20Target%20Universes.csv?dl=1";
//private const string BacktestUrl = @"https://www.dropbox.com/s/ciukaohnkh0ip6n/ValuEngine%20Monthly%20Target%20Universes.csv?dl=1"; // 2023-01-8 includes only symbols with more than $2.5 one year appreciation
private const string LiveUrl = @"https://www.dropbox.com/s/06o0qavfeccbxn0/VE_Ranked_Tickers_By_Month.csv?dl=1";
//private const string BacktestUrl = @"https://www.dropbox.com/s/06o0qavfeccbxn0/VE_Ranked_Tickers_By_Month.csv?dl=1"; // 2023-01-15 --- MS Access VBA generated file of Sorted Symbols by month VEngineRanking, Price Appreciation
//private const string BacktestUrl = @"https://www.dropbox.com/s/az0ye8g91yels6n/VE_Ranked_3-5_Tickers_By_Month.csv?dl=1";
// /// /// VE 3Rank >4pct Sorted by total yield
//private const string BacktestUrl = @"https://www.dropbox.com/s/sj8wrgcpkx31cho/VE_Ranked_3_4pct_Tickers_By_Month.csv?dl=1";
// /// /// VE 4-5Rank >2pct Sorted by total yield
//private const string BacktestUrl = @"https://www.dropbox.com/s/c5namhpjocwwtle/VE_Ranked_4_5_2pct_Tickers_By_Month.csv?dl=1";
// /// /// VE (3 & >=4%Div) (4-5 & >2%Div) Ranking
private const string BacktestUrl = @"https://www.dropbox.com/s/n3szi94etlfa3ex/VE_Ranked_3_4_5_2pct_Tickers_By_Month.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
{
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.ParseExact(csv[0], "yyyy-MM-dd", null);
// stocks.Time = DateTime.ParseExact(csv[0], "dd/MM/yyyy", null);
//stocks.Symbols.AddRange(csv.Skip(1).TakeWhile(t=>t.ToString().CompareTo("M")>=0 && t.ToString().CompareTo("Y")<=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 OnEndOfDay(Symbol symbol)
{
if (symbol.SecurityType != SecurityType.Equity) return;
return;
}
public override void OnEndOfAlgorithm()
{
var dailyBars = "";
/// //// //// STORE DAILY BARS FOR EVERY UNDERLYING TRADED/ADDED TO PORTFOLIO /// /// ///
foreach (var symb in tradedSymbols){
foreach(var bar in History(symb, StartDate, EndDate, Resolution.Daily)) {
dailyBars += $"{bar.EndTime},{bar.Open},{bar.High},{bar.Low},{bar.Close}\n";
}
ObjectStore.Save($"ohcl_{symb}", dailyBars);
}
var doneOrders = Transactions.GetOrders();
foreach(var ord in doneOrders){
if (ord.Status == OrderStatus.Filled){
filledOrdersForObjStore += $"{ord.Time},{ord.Symbol}, {ord.Price}/n";
}
}
ObjectStore.Save("orders", filledOrdersForObjStore);
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}. ");
tpr.uEndPrice = Securities[tpr.uSymbol].Price;
}
if (tpr.pEndPrice == 0 && tpr.pSymbol != null) {
tpr.pEndPrice = Securities[tpr.pSymbol].Price;
}
if (tpr.cEndPrice == 0 && tpr.cSymbol != null) {
tpr.cEndPrice = Securities[tpr.cSymbol].Price;
}
tpr.endDate = Time;
}
}
string jsonString = ConvertTradePerfRec(tradeRecs);
}
private void HistoricalSecurityInitializer(Security security)
{
var bar = GetLastKnownPrice(security);
security.SetMarketPrice(bar);
if(security.Type == SecurityType.Option){
var openInterest = History<OpenInterest>(security.Symbol, TimeSpan.FromDays(1));
var tradeBar = History<TradeBar>(security.Symbol, TimeSpan.FromDays(1));
}
}
private bool CheckBadDate(DateTime checkDate)
{
DateTime badDate1 = Convert.ToDateTime(GetParameter("BadDate"));
DateTime badDate2 = badDate1.AddMinutes(1);
//DateTime badDate1 = new DateTime(2020, 1, 6, 9, 45, 0);
//DateTime badDate2 = new DateTime(2020, 11, 1, 13, 45, 0);
if(checkDate.Equals(badDate1) | checkDate.Equals(badDate2))
{
return badDtParameter;
} else {
return false;
}
}
// |||||||||||||||||||||||||||||||||||||||||||||||
// 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);
}
}
} // 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
//interestCost = (thisFFRate + ibkrRateAdj)/workingDays * (decimal) daysInTrade * stockPrice;
// create a range of expiration dates from the prior expiration to the whichExpiry date.
//callSymbolsForThisExpiry = allUnderlyingOptionsSymbols.Where( o=> DateTime.Compare(o.ID.Date, pastExpiry) > 0 && DateTime.Compare(o.ID.Date, whichExpiry)<=0 &&
if (stockPrice <= 100m & strikeStep == 5) strikeStep = 2.5m; /// make adjustment to standardize options placement
if (strikeStep <= 1m) strikeStep = 2m; /// make adjustment to widen unusual case of $1 strike steps
decimal VEAppreciation = 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;
VEAppreciation = LD.decOneYearPriceTarget - stockPrice;
} else {
VEAppreciation = symbolDataBySymbol[symbU].decOneYearPriceTarget_Initial - stockPrice;
LD.initialTargetEndDate = symbolDataBySymbol[symbU].initialTargetEndDate;
}
}
if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ -- Assembling {symbU.Value} SSQRs using VEAppreciation : {LD.decOneYearPriceTarget.ToString()} - {stockPrice.ToString()} = {VEAppreciation.ToString()} | StrikeStep: {strikeStep.ToString()}.");
if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ -- High -- Low -- ATM-C -- ITM-C --");
if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ -- {highStrike.ToString().PadLeft(4,' ')} -- {lowStrike.ToString().PadLeft(4,' ')} -- {atmStrike.ToString().PadLeft(4,' ')} -- {firstITMCallStrike.ToString().PadLeft(4,' ')} --");
//switch (LD.intVERating) {
switch (VEAppreciation){
case var _ when VEAppreciation <= 0M: // Stock is predicted to lose value, do a bear collar, write ITM call, but its Total YLD including Dividends is positive and ranked in monthly file, or this is a roll
LD.VECase = "Case 1";
//estTrgtCallStrk = atmStrike - strikeStep;
// estTrgtCallStrk = atmStrike + strikeStep;
estTrgtCallStrk = atmStrike; /// Allow for ATM & ITM calls -
estTrgtPutStrk = atmStrike - 2m * strikeStep;
if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ @@@@ Case 1: {VEAppreciation.ToString()} less than 0. Call target: {estTrgtCallStrk.ToString()} / Put Target: {estTrgtPutStrk.ToString()} ");
callContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Call &&
DateTime.Compare(o.Expiry, LUD.initialTargetEndDate)<=0 & // get close, but don't exceed 1 year target. Get as much call premium (theta) as possible
o.Strike <= estTrgtCallStrk)
.OrderByDescending(o=>o.Expiry)
.ThenBy(o => Math.Abs(estTrgtCallStrk - o.Strike))
.ToList();
putContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Put &&
DateTime.Compare(o.Expiry, LUD.initialTargetEndDate)<=0 &
o.Strike <= estTrgtPutStrk)
.OrderByDescending(o => o.Strike)
.ToList();
break;
case var _ when VEAppreciation > 0M & VEAppreciation <=10M: // Stock is predicted to gain some value. Do a standard collar, place call as close to VEAppreciation as possible.
LD.VECase = "Case 2";
estTrgtCallStrk = atmStrike + VEAppreciation;
//estTrgtPutStrk = atmStrike - 3M * strikeStep;
estTrgtPutStrk = atmStrike - 2M * strikeStep;
if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ @@@@ Case 2: {VEAppreciation.ToString()} grtr than 0. Call target: {estTrgtCallStrk.ToString()} / Put Target: {estTrgtPutStrk.ToString()} ");
callContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Call &&
DateTime.Compare(o.Expiry, LUD.initialTargetEndDate)<=0 & // get close, but don't exceed 1 year target. Get as much call premium (theta) as possible
Math.Abs(estTrgtCallStrk - o.Strike) <= 0.10M * estTrgtCallStrk)
.OrderByDescending(o=>o.Expiry)
.ThenBy(o => Math.Abs(estTrgtCallStrk - o.Strike))
.ToList();
putContracts = allUnderlyingOptions.Where( o=> o.Right == OptionRight.Put &&
DateTime.Compare(o.Expiry, LUD.initialTargetEndDate)<=0 &
o.Strike <= estTrgtPutStrk)
.OrderByDescending(o => o.Strike)
.ToList();
break;
case var _ when VEAppreciation > 10M:
LD.VECase = "Case 3";
estTrgtCallStrk = atmStrike + strikeStep; // /// //// DO NOT WRITE A CALL WHEN INITIALIZING
estTrgtPutStrk = atmStrike - 2M * strikeStep;
if (LD.doDeepTracing) algo.Debug($" @@@@@ @@@@@ @@@@ Case 3: {VEAppreciation.ToString()} grtr than 10. Call target: --no call--/ Put Target: {estTrgtPutStrk.ToString()} ");
var pContracts = allUnderlyingOptions.Where( o=> DateTime.Compare(o.Expiry, justDate.AddMonths(2))>0 &
o.Strike <= estTrgtPutStrk &
o.Right == OptionRight.Put)
.OrderBy(o => o.Expiry)
.ThenByDescending(o => o.Strike);
//.FirstOrDefault();
putContract = pContracts.FirstOrDefault();
if (LD.doTracing) algo.Debug($"@@@@@ -- @@@@ -- @@@@ next EX DATE for {LUD.uSymbol.Value} is {LUD.exDivdnDate.ToString()} ");
if (putContract == null) {
if (LD.doTracing) algo.Debug("@@@@@ -- get putContract for VE4 failed 1st attempt -> increment month. "); /// use the expiries[1] date as the seed and find the subsequent 4 3-month expirations
}
callContract = null;
if (LD.doTracing) algo.Debug("@@@@@ -- get putContract for VE4 Succeeded -> BuildSSQR for MarriedPut strategy. "); /// use the expiries[1] date as the seed and find the subsequent 4 3-month expirations
thisSSQRColumn = buildSSQRColumn(putContract, callContract, algo, LD);
if (thisSSQRColumn != null)
{
LD.SSQRMatrix.Add(thisSSQRColumn);
} else if(LD.doTracing) algo.Debug($"@@@@@ @@@@@ -- failed to get VE4 SSSQRColumn for {LD.uSymbol}.");
return;
} /// end switch
// /// /// COLLAR PROCESSING BEGINS HERE // /// /// ///
if (callContracts == null | callContracts.Count() == 0) // **************** Check if any calls are returned.
{
if (LD.doTracing) algo.Debug("@@@@@ -- get callContracts failed new paradigm in VE Rank 3. "); /// use the expiries[1] date as the seed and find the subsequent 4 3-month expirations
return;
}
if( putContracts == null | putContracts.Count() == 0) /// **************** Check if no puts 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("@@@@@ @@@@@ -- PUT Expiries failed 1st Pass try decrementing ----------------------------");
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"));
//if (doDeepTracing);(callSymbolsForThisExpiry, "callSymbols");
// }
/*
if (LD.intVERating == 4 | LD.intVERating == 5) ////
{
putExpiries[1] = FindNextOptionsExpiry(putExpiries[1],-1); //// looking for first dividend only
} else {
// putExpiries[1] = FindNextOptionsExpiry(putExpiries[1], -1); /// //// //// *** *** *** *** used this for first time in 6F.... changed results
}
*/
/// use the expiries[1] date as the seed and find the subsequent 4 3-month expirations
putExpiries[2] = FindNextOptionsExpiry(putExpiries[1], 4);
putExpiries[3] = FindNextOptionsExpiry(putExpiries[1], 7);
putExpiries[4] = FindNextOptionsExpiry(putExpiries[1], 10);
putExpiries[5] = FindNextOptionsExpiry(putExpiries[1], 13);
putContracts = allUnderlyingOptions.Where( o=> ( DateTime.Compare(o.Expiry, putExpiries[2])==0 |
DateTime.Compare(o.Expiry, putExpiries[3])==0 |
DateTime.Compare(o.Expiry, putExpiries[4])==0 |
DateTime.Compare(o.Expiry, putExpiries[5])==0 ) &
o.Strike < atmStrike &
o.Right == OptionRight.Put)
.OrderByDescending(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[2] = FindNextOptionsExpiry(putExpiries[1], 2);
putExpiries[3] = FindNextOptionsExpiry(putExpiries[1], 3);
putExpiries[4] = FindNextOptionsExpiry(putExpiries[1], 4);
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=> ( DateTime.Compare(o.Expiry, putExpiries[1])==0 |
DateTime.Compare(o.Expiry, putExpiries[2])==0 |
DateTime.Compare(o.Expiry, putExpiries[3])==0 |
DateTime.Compare(o.Expiry, putExpiries[4])==0 |
DateTime.Compare(o.Expiry, putExpiries[5])==0 |
DateTime.Compare(o.Expiry, putExpiries[6])==0 |
DateTime.Compare(o.Expiry, putExpiries[7])==0 |
DateTime.Compare(o.Expiry, putExpiries[8])==0 ) &
o.Strike < atmStrike &
o.Right == OptionRight.Put)
.OrderByDescending(o => 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"));
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();
// Now iterate through the puts and sub-iterate through the calls to assemble the SSQRMatrix
// for pricing, puts are bought at the offer and calls are sold at the bid prices.
// Each price should be the midpoint between the open and close.
//if (LD.doDeepTracing) algo.Debug($" ---- ---- Assembling SSQRs for {symbU.Value} : ");
while (pEnumerator.MoveNext())
{
var cEnumerator = callContracts.GetEnumerator();
putContract = pEnumerator.Current;
//if (LD.doDeepTracing) algo.Debug($" ---- ---- ---- : {putContract.ToString()} target strike: {estTrgtPutStrk.ToString()}.");
//atmCall = callContracts.Where(s => DateTime.Compare(s.Expiry, putContract.Expiry)==0) /// get atmCall for this Put Option Expiration
// .OrderBy(s => Math.Abs(s.Strike - stockPrice))
// .FirstOrDefault();
//wCallContracts.Clear();
//wCallContracts = callContracts.Where( o=> ( DateTime.Compare(o.Expiry, putContract.Expiry)==0) &
// o.Strike >= atmStrike).Distinct().ToList(); /// & o.Strike <= (decimal)1.1 * atmCall.Strike
//wCallContracts.Sort((x,y) => x.Strike.CompareTo(y.Strike));
//var wcEnumerator = wCallContracts.GetEnumerator();
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);
//}
//}
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 5-params **************************************
// *** This sub routine takes in the variables for the iterated put and call Options Lists
// *** as well as the dividends count, dividend amount, and stock price
// *** and returns an SSQRColumt to be added to the SSQRMatrix list
// ***********************************************************************************
public SSQRColumn buildSSQRColumn(OptionContract thisPutOpt, OptionContract thisCallOpt, OptionContract wcOpt, QCAlgorithm algo, LookupData LD)
//public SSQRColumn buildSSQRColumn(Option thisPutOpt, Option thisCallOpt, OptionContract pGrks, OptionContract cGrks, DateTime whichExpiry, DateTime tradeDate, DateTime exDate, int dividends, decimal amtDividend, decimal stockPrice, int daysInTrade, decimal intCost)
{
decimal thisSpread = 1M;
decimal targetAppreciation = 0m;
decimal wingFactor = .2M; // factor to determine wings contract load
decimal wingPremium = 1; // added premium to do the wings
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 (thisPutOpt.AskPrice == 0 | thisCallOpt.BidPrice == 0) return thisColumn; // don't build SSQRColumns with missing premium values
DateTime tradeDate = algo.CurrentSlice.Time;
daysInTrade = (thisPutOpt.Expiry - tradeDate).Days;
decimal intCost = (LD.thisFFRate + LD.ibkrRateAdj)/LD.workingDays * (decimal) daysInTrade * stockPrice;
//if (haltProcessing) {
if (LUD.doDeepTracing) algo.Log($" BSSQR BSSQR - Logging buildSSQRColumn processing for {thisPutOpt.Symbol.Value}/{thisCallOpt.Symbol.Value}") ;
//}
if (DateTime.Compare(thisPutOpt.Expiry, LD.exDivdnDate)<=0) {
monthsInTrade = 0;
} else if (thisPutOpt.Expiry.Year > LD.exDivdnDate.Year) {
monthsInTrade = thisPutOpt.Expiry.Month - LD.exDivdnDate.Month + 12;
} else if (thisPutOpt.Expiry.Month == LD.exDivdnDate.Month) {
monthsInTrade = 1;
} else monthsInTrade = thisPutOpt.Expiry.Month - LD.exDivdnDate.Month;
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 = thisPutOpt.Symbol;
thisColumn.callSymbol = thisCallOpt.Symbol;
thisColumn.wCallSymbol = wcOpt.Symbol; // atm call for this column (based upon put)
thisColumn.putPremium = thisPutOpt.AskPrice;
thisColumn.callPremium = thisCallOpt.BidPrice;
thisColumn.wCallPremium = wcOpt.AskPrice; //
thisColumn.putStrike = thisPutOpt.Strike;
thisColumn.callStrike = thisCallOpt.Strike;
thisColumn.wCallStrike = wcOpt.Strike;
thisColumn.exDate = LD.exDivdnDate;
thisColumn.putExpiry = thisPutOpt.Expiry;
thisColumn.callExpiry = thisCallOpt.Expiry;
thisColumn.putDelta = thisPutOpt.Greeks.Delta;
thisColumn.callDelta = thisCallOpt.Greeks.Delta;
thisColumn.wcDelta = wcOpt.Greeks.Delta;
thisColumn.putGamma = thisPutOpt.Greeks.Gamma;
thisColumn.callGamma = thisCallOpt.Greeks.Gamma;
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.putImpliedVol = thisPutOpt.ImpliedVolatility;
thisColumn.callImpliedVol = thisCallOpt.ImpliedVolatility;
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;
//if(LD.intVERating < 4) { // *^*^*^*^* try using solely married puts without calls on appreciating stocks *^*^*^*^*
thisSpread = stockPrice <= thisCallOpt.Strike ? stockPrice - thisPutOpt.Strike : thisCallOpt.Strike - thisPutOpt.Strike;
//} else if (LD.intVERating > 3) {
// thisSpread = stockPrice - thisPutOpt.Strike;
//}
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;
thisColumn.haircut = LD.ibkrHairCuts[thisSpread];
}
}else
{
thisColumn.haircut = 20M;
}
targetAppreciation = LD.decOneYearPriceTarget - stockPrice;
decimal divDollars = LD.divdndAmt * dividends;
thisColumn.divDollars = divDollars;
decimal stockLossIfCalled = (thisCallOpt.Strike>stockPrice) ? 0 : (thisColumn.putPremium>thisColumn.callPremium) ? (thisColumn.callStrike - stockPrice) : 0; // loss=0 if cStrike>stkPrice, otherwise negative ***Loss (negative value) if ITM calls are assigned (0 if #calls<#puts)
decimal netOptions = -thisColumn.putPremium + thisColumn.callPremium; /// netOptions equals negative putPrem (expense) plus positive call premium (income)
thisColumn.netOptions = netOptions;
thisColumn.netIncome = divDollars + netOptions - intCost; // Net Income in SSQR.xls subtracts interest cost but does not allow for appreciation to OTM call strike /// obviated in Wing System which has an upside long call
wingFactor = (netOptions + divDollars - intCost) / wingPremium; // wing factor defined as income(loss) from options plus dividend minus interest cost divided by the premium paid for the wings
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.putStrike) ? stockPrice - netOptions - thisColumn.putStrike + thisColumn.interestCost: thisColumn.interestCost; // 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 = (thisColumn.callStrike>stockPrice) ? ((thisColumn.callStrike > LD.decOneYearPriceTarget) ? LD.decOneYearPriceTarget - stockPrice + divDollars + netOptions - intCost : thisColumn.callStrike - stockPrice + divDollars + netOptions - intCost) : divDollars + netOptions - intCost; // When writing OTM calls, there is a potential upside appreciation from net collar cost to the call 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 = 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 + "," + 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) + ", "
+ String.Format("{0:0.00}", thisColumn.wCallStrike) + ", " + String.Format("{0:0.00}", thisColumn.wCallPremium) + ", "
+ String.Format("{0:0.00}",thisColumn.putDelta) + ", " + String.Format("{0:0.00}", thisColumn.callDelta) + ", "
+ String.Format("{0:0.00}",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 ) + ","
+ String.Format("{0:0.00}", thisColumn.wingFactor) + "," + thisColumn.putSymbol + "," + thisColumn.callSymbol;
return thisColumn;
}
// ********************** buildSSQRColumn 3-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 thisPutOpt, OptionContract perkCallOpt, QCAlgorithm algo, LookupData LD)
//public SSQRColumn buildSSQRColumn(Option thisPutOpt, Option thisCallOpt, OptionContract pGrks, OptionContract cGrks, DateTime whichExpiry, DateTime tradeDate, DateTime exDate, int dividends, decimal amtDividend, decimal stockPrice, int daysInTrade, decimal intCost)
{
decimal thisSpread = 1M;
decimal wingFactor = .2M; // factor to determine wings contract load
decimal wingPremium = 1; // added premium to do the wings
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 (thisPutOpt == null) return thisColumn; // 2023-02-10 -- trap null thisPutOpt -- crashed in new differentiated VE model
if (thisPutOpt.AskPrice == 0) return thisColumn; // don't build SSQRColumns with missing premium values
DateTime tradeDate = algo.CurrentSlice.Time;
daysInTrade = (thisPutOpt.Expiry - tradeDate).Days;
decimal intCost = (LD.thisFFRate + LD.ibkrRateAdj)/LD.workingDays * (decimal) daysInTrade * stockPrice;
if (haltProcessing) {
algo.Log(" Logging buildSSQRColumn processing") ;
}
monthsInTrade = thisPutOpt.Expiry.Month - LD.exDivdnDate.Month;
if( thisPutOpt.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 = thisPutOpt.Symbol;
// thisColumn.wCallSymbol = wcOpt.Symbol; // atm call for this column (based upon put)
thisColumn.putPremium = thisPutOpt.AskPrice;
// thisColumn.wCallPremium = wcOpt.AskPrice; //
// thisColumn.wCallStrike = wcOpt.Strike;
thisColumn.exDate = LD.exDivdnDate;
thisColumn.putExpiry = thisPutOpt.Expiry;
thisColumn.putStrike = thisPutOpt.Strike;
thisColumn.putDelta = thisPutOpt.Greeks.Delta;
// thisColumn.wcDelta = wcOpt.Greeks.Delta;
thisColumn.putGamma = thisPutOpt.Greeks.Gamma;
if (perkCallOpt!=null) {
thisColumn.callSymbol = perkCallOpt.Symbol;
thisColumn.callPremium = perkCallOpt.BidPrice;
thisColumn.callStrike = perkCallOpt.Strike;
thisColumn.callDelta = perkCallOpt.Greeks.Delta;
thisColumn.callGamma = perkCallOpt.Greeks.Gamma;
thisColumn.callImpliedVol = perkCallOpt.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.putImpliedVol = thisPutOpt.ImpliedVolatility;
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(stockPrice - thisPutOpt.Strike);
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;
thisColumn.haircut = LD.ibkrHairCuts[thisSpread];
}
}else
{
thisColumn.haircut = 35m;
}
decimal divDollars = LD.divdndAmt * dividends;
thisColumn.divDollars = divDollars;
decimal stockLossIfCalled = 0; // loss=0 if cStrike>stkPrice, otherwise negative ***Loss (negative value) if ITM calls are assigned (0 if #calls<#puts)
decimal netOptions = -thisColumn.putPremium; /// netOptions equals negative putPrem (expense) plus positive call premium (income)
thisColumn.netOptions = netOptions;
thisColumn.netIncome = divDollars + netOptions - intCost; // Net Income in SSQR.xls subtracts interest cost but does not allow for appreciation to OTM call strike /// obviated in Wing System which has an upside long call
// wingFactor = (netOptions + divDollars - intCost) / wingPremium; // wing factor defined as income(loss) from options plus dividend minus interest cost divided by the premium paid for the wings
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.putStrike) ? stockPrice - netOptions - thisColumn.putStrike + thisColumn.interestCost: thisColumn.interestCost; // 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(perkCallOpt!=null) {
upsidePotential = LD.decOneYearPriceTarget < perkCallOpt.Strike ? LD.decOneYearPriceTarget - stockPrice + divDollars + netOptions - intCost : perkCallOpt.Strike - stockPrice + divDollars + netOptions - intCost ; // When writing OTM calls, there is a potential upside appreciation from net collar cost to the call strike.
} else {
upsidePotential = LD.decOneYearPriceTarget - stockPrice + divDollars + netOptions - intCost; // When writing OTM calls, there is a potential upside appreciation from net collar cost to the call 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 = 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 + "," + 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") + ", " + "-no call-" + ", "
+ String.Format("{0:0.00}",thisColumn.putStrike) + ", " + String.Format("{0:0.00}",thisColumn.putPremium) + ", "
+ "-no call-" + ", " + "-no call-" + ", "
+ "-no call-" + ", " + "-no call-" + ", "
+ String.Format("{0:0.00}",thisColumn.putDelta) + ", " + "-no call-" + ", "
+ 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 ) + ","
+ String.Format("{0:0.00}", thisColumn.wingFactor) + "," + thisColumn.putSymbol + "," + "-no call-";
return thisColumn;
}
// ********************** AddCorrespondingPut *******************************************
// *** This code will add the put option which corresponds to the call shorted
// *** for purposes of evaluating it in ex-dividend approachment.
// *** Option must be constructed with correct parameters before it can be added
// ******************************************************************************************
public Option AddCorrespondingPut(Symbol tradableCall, ref List<Symbol> currentOptions)
{
int indexOfC = tradableCall.ToString().LastIndexOf("C");
char[] charArrayC = tradableCall.ToString().ToCharArray();
char[] charArrayP = charArrayC;
charArrayP[indexOfC] = 'P';
string putString = new string(charArrayP);
var putSymbol = QuantConnect.Symbol.CreateOption(
tradableCall.Underlying,
Market.USA,
OptionStyle.American,
OptionRight.Put,
tradableCall.ID.StrikePrice,
tradableCall.ID.Date);
Option correspondingPut = AddOptionContract(putSymbol);
currentOptions.Add(correspondingPut.Symbol);
return correspondingPut;
}
// ********************** GetCorrespondingPut *******************************************
// *** This code will get the put option which corresponds to the call shorted
// *** for purposes of evaluating it in ex-dividend approachment --
// *** ??? return Symbol or string?
// ******************************************************************************************
public string GetCorrespondingPut(Symbol tradableCall)
{
int indexOfC = tradableCall.ToString().LastIndexOf("C");
char[] charArrayC = tradableCall.ToString().ToCharArray();
char[] charArrayP = charArrayC;
charArrayP[indexOfC] = 'P';
string putString = new string(charArrayP);
return putString;
}
public SSQRColumn fillSSQRColumn ()
{
SSQRColumn anotherSSQRColumn = new SSQRColumn();
return anotherSSQRColumn;
}
// ********************** GetOptionsExpiries **************************************
// *** Use this to find and return the next 4 options expirations expirations dates
// *** Function will determine if a date is a holiday and subtract 1 day
// *** Target next 3 Ex-Datas whether 1st is Slice.Time.Month or later.
// ***********************************************************************************
public Dictionary<int, DateTime> GetOptionExpiries(DateTime tradeD, DateTime nextExDate, DateTime thisMonthExpiry, bool isPrimary, bool isCall){
// Initialize expiration date variables //
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, 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, 1);
secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 4);
thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 7);
fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 10);
fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 13);
} else
{
secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 3);
thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 6);
fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 9);
fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 12);
}
} else
{
firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, 1);
secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 4);
thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 7);
fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 10);
fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 13);
}
} else { // this is for 2ndTPRs -- monthly options every month to catch some
if (DateTime.Compare(nextExDate, thisMonthExpiry) <= 0)
{
firstExpiry = FindNextOptionsExpiry(thisMonthExpiry, 0);
secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 1);
thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 2);
fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 3);
fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 4);
sixthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 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, 1);
secondExpiry = FindNextOptionsExpiry(thisMonthExpiry, 2);
thirdExpiry = FindNextOptionsExpiry(thisMonthExpiry, 3);
fourthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 4);
fifthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 5);
sixthExpiry = FindNextOptionsExpiry(thisMonthExpiry, 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, 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
while (USHoliday.Dates.Contains(findDate)) findDate = findDate.AddDays(-1);
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}");
}
}
// ********************** 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(" ---------------------------------------------------------------------------------------------");
}
// ********************** IterateContracts *******************************************************
// *** Generalized function to iterate through and print members of an IEnumerable of Contracts
// *** This is used for debugging only
// ********************************************************************************************
public void IterateContracts(List<Option> thisOptionsList)
{
int k = 1;
Option thisOption;
var enumerator = thisOptionsList.GetEnumerator();
Debug(" |||||||||||||||||||||||||||||||| NEW OPTION CONTRACTS LIST |||||||||||||||||||||||||||||||");
Debug("There are " + thisOptionsList.Count() + " contracts in this options list.");
while (enumerator.MoveNext())
{
thisOption = enumerator.Current;
//Debug("Iterated " + k + " times");
//Debug("Option Chain: " + thisOption.ToString());
//Debug(thisOption.StrikePrice + " " + thisOption.Expiry + " " + thisOption.Right + " " + thisOption.GetLastData());
Debug(thisOption.StrikePrice + " " + thisOption.Expiry + " " + thisOption.Right + " BID: " + thisOption.BidPrice + " ASK: " + thisOption.AskPrice);
k++;
}
//Debug(" ---------------------------------------------------------------------------------------------");
}
// ********************** Iterate Matrix *******************************************************
// *** Generalized function to iterate through and print members of an IEnumerable of Contracts
// *** This is used for debugging only
// ********************************************************************************************
public void IterateSSQRMatrix(List<SSQRColumn> thisMatrix)
{
int k = 1;
SSQRColumn thisColumn;
var matrixEnum = thisMatrix.GetEnumerator();
Debug(" |||||||||||||||||||||||||||||||| NEW OPTION SSQRMatrix |||||||||||||||||||||||||||||||");
Debug("There are " + thisMatrix.Count() + " columns in this SSQRMatrix.");
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
Debug(",Ticker,Stock Price,Ex-Date,# Dividends,Dividend,Dollars,Days In,Interest,PExpiry, CExpiry, PutStrike,PutASK,CallStrike,CallBid, atmStrike, atmCallAsk, PutDelta, CallDelta, NetOptions,Net Income,Haircut,ROC,VE Rating, VE Momentum, VE 1 Yr, Upside,Downside,ROR,CCOR, wingFactor, PutSymb, CallSymb");
while (matrixEnum.MoveNext())
{
thisColumn = matrixEnum.Current;
//Debug("Iterated " + k + " times");
Debug(thisColumn.description2);
k++;
}
Debug(" ---------------------------------------------------------------------------------------------");
}
// ********************** Iterate Ordered Matrix ***********************************************
// *** Generalized function to iterate through and print members of an IEnumerable of Contracts
// *** This is used for debugging only tricky part is passing an IOrderedEnumerable into this
// ****************************************************************************************************
public void IterateOrderedSSQRMatrix(IOrderedEnumerable<SSQRColumn> thisOrdMatrix)
{
int k = 1;
Debug(" |||||||||||||||||||||||||||||||| NEW TRADABLE SSQRMatrix |||||||||||||||||||||||||||||||");
Debug("There are " + thisOrdMatrix.Count() + " columns in this SSQRMatrix.");
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
Debug(",Ticker,Stock Price,Ex-Date,# Dividends,Dividend,Dollars,Days In,Interest,PExpiry, CExpiry, PutStrike,PutASK,CallStrike,CallBid, wCStrike, wCallAsk, PutDelta, CallDelta, NetOptions,Net Income,VE Rating, VE Momentum, VE 1 Yr, Haircut,ROC,Upside,Downside,ROR,CCOR, wingFactor, PutSymb, CallSymb");
foreach (SSQRColumn thisColumn in thisOrdMatrix)
{
//Debug("Iterated " + k + " times");
Debug(thisColumn.description2);
//Debug(" ");
k++;
if (k == 21) break;
}
}
// ********************** Iterate Ordered PutSpread **********************************************
// *** Generalized function to iterate through and print members of an IEnumerable of PutSpreads
// *** This is used for debugging only tricky part is passing an IOrderedEnumerable into this
// ****************************************************************************************************
public void IterateOrderedPutSpreadList(IOrderedEnumerable<PutSpread> thisOrdSpreads)
{
string logLine = ""; // for writing the logs
int k = 1;
Debug(" |||||||||||||||||||||||||||||||| NEW TRADABLE PutSpreads List |||||||||||||||||||||||||||||||");
Debug(",¶¶,There are " + thisOrdSpreads.Count() + " PutSpreads in this List.");
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
//Debug("¶¶,Stock Price, Ex-Date, Trade Date, pExpiry, oldPutSymb, newPutSymb, oldBid, newAsk, oldStrike, newStrike, Open Interst, Div Amt, # Dividends, Div Dollars, stock Incr,Interest,DownSide, Upside, Net Income, NetOptions, Haircut, Descr
logLine = ",¶¶";
foreach (PutSpread thisSpread in thisOrdSpreads)
{
if (k==1){ // iterate field names
foreach (var fieldN in typeof(PutSpread).GetFields())
{
logLine = logLine + "," + fieldN.Name;
}
Debug(logLine);
logLine = ",¶¶";
//k = k + 1;
}
foreach (var fieldV in typeof(PutSpread).GetFields())
{
if (fieldV.GetType() == typeof(decimal)) {
logLine = logLine + "," + String.Format("{0:0.00}", fieldV.GetValue(thisSpread));
}
else if (fieldV.GetType() == typeof(DateTime)) {
logLine = logLine + "," + String.Format("{0:MM/dd/yy H:mm:ss}", fieldV.GetValue(thisSpread));
}
else logLine = logLine + "," + fieldV.GetValue(thisSpread);
}
Debug(logLine);
logLine = ",¶¶";
//Debug("Iterated " + k + " times");
//Debug(thisSpread.description1);
//Debug(" ");
k++;
//if (k == 11) break;
}
}
}
}#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;
////////////////////////////////////////////////////////////////////////////////////
//
// ExecuteTrade
//
////////////////////////////////////////////////////////////////////////////////////
public void ExecuteTrade(Slice data, SSQRColumn bestSSQRColumn, ref SymbolData symbData)
{
thisCCOR = bestSSQRColumn.CCOR;
decimal maxWingFactor = 0;
decimal thisWingFactor = 0;
decimal wingPremium = 0;
decimal thisNetOptions = bestSSQRColumn.netOptions;
if (haltProcessing)
{
//if (doDeepTracing) Log(" Logging ExecuteTheTrade() ");
}
//goodThresh = (thisCCOR >= CCORThresh);
goodThresh = true;
if (goodThresh)
{
//sharesToBuy = Math.Round(stockDollarValue/stockPrice/100, 0) * 100; // set in top of OnData()
optionsToTrade = sharesToBuy/100;
//callsToTrade = Decimal.Round(optionsToTrade * bestSSQRColumn.putPremium / bestSSQRColumn.callPremium); /// legacy VCCPTS code
//Log(tradableColumn.ToString());
Symbol tradablePut = bestSSQRColumn.putSymbol;
Symbol tradableCall = bestSSQRColumn.callSymbol;
Symbol tradableWCall = bestSSQRColumn.wCallSymbol;
if (bestSSQRColumn.callSymbol!=null && Securities[tradableCall].AskPrice + bestSSQRColumn.callStrike < 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 CALL ASK: " + Securities[tradableCall].AskPrice + " Strike: " + bestSSQRColumn.callStrike + " 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;
}
if (doTracing) Log($"@E@E@E@E@E@E EXECUTING MARRIED PUT INITIALIZATION FOR {bestSSQRColumn.uSymbol} @E@E@E@E@E@E");
//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";
thisNewCollar.isOpen = true;
thisNewCollar.isInitializer = true;
thisNewCollar.tradeRecCount = collarIndex;
thisNewCollar.index = symbData.intTPRCntr;;
thisNewCollar.startDate = data.Time;
thisNewCollar.expDate = bestSSQRColumn.putExpiry;
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 = LUD.intVERating;
thisNewCollar.momentum = LUD.decMomentum;
thisNewCollar.oneYearPriceTarget = LUD.decOneYearPriceTarget;
thisNewCollar.momentumRank = LUD.intMomentumRank;
doTheTrade = true;
if(bestSSQRColumn.callSymbol!=null){
if (thisNewCollar.cStrike < thisNewCollar.uStartPrice) {
var limitPrice = (Securities[tradableCall].AskPrice - Securities[tradableCall].BidPrice) / 2M; // get the mid point for the limit price
var callTicket = LimitOrder(tradableCall, -optionsToTrade, limitPrice); // sell limit order
thisNewCollar.cQty = -(int)optionsToTrade;
OpenLimitOrder oLO = new OpenLimitOrder();
oLO.oTicket = callTicket;
oLO.tpr = thisNewCollar;
oLO.oRight = OptionRight.Call;
oLOs.Add(oLO);
//if (closePutTicket.Status == OrderStatus.Submitted) oldTradeRec.pEndPrice = limitPrice;
} else {
var callTicket = MarketOrder(tradableCall, -optionsToTrade);
if (callTicket.Status == OrderStatus.Filled)
{
thisNewCollar.cStartPrice = callTicket.AverageFillPrice;
thisNewCollar.cQty = (int)callTicket.QuantityFilled;
}
}
}
if(bestSSQRColumn.wCallSymbol!=null) thisWingFactor = bestSSQRColumn.wingFactor;
//var putTicket = MarketOrder(tradablePut, (1 + thisWingFactor) * optionsToTrade);
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 ");
} // marketOrder(bestSSQRColumn.uSymbol) == filled
} // goodThresh is TRUE
return;
}
///////////////////////////////////////////////////////////////////////////////////
// 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;
//Debug(" C2 ** C2 ** C2 ** C2 ** KILLING 2nd TPR ** C2 ** C2 ** C2 ** C2 ** C2 ** ");
//Log(" C2 ** Stock Price: " + stockPrice.ToString() + " Call Bid/Offer: " + closeRec.cSymbol.BidPrice.ToString() + "/" + closeRec.cSymbol.AskPrice.ToString());
/*if (closeRec.pStrike >= stockPrice) /// ITM Put -- use limit order
{
limitPrice = closeRec.pStrike - stockPrice + 0.10M;
closePutTicket = LimitOrder(closeRec.pSymbol, -closeRec.pQty, limitPrice); // sell the puts
OpenLimitOrder oLO = new OpenLimitOrder();
oLO.oTicket = closePutTicket;
oLO.tpr = closeRec;
oLO.oRight = OptionRight.Put;
oLOs.Add(oLO);
//if (doDeepTracing) Log(" C2 ** LIMIT ORDER TO SELL " + closeRec.pQty.ToString() + " contracts of " + closeRec.pSymbol + " at " + limitPrice.ToString());
//if (doDeepTracing) Log("-");
} else { */
closePutTicket = MarketOrder(closeRec.pSymbol, -closeRec.pQty); // sell the puts
//if (doDeepTracing) Log(" C2 ** MARKET ORDER TO SELL " + closeRec.pQty.ToString() + " contracts of " + closeRec.pSymbol + " at the market." );
//if (doDeepTracing) Log("-");
//}
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 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");
}
//// CRAIG LOOK FOR THIS
try {
if (Securities[killRec.uSymbol].HasData)
{
var tryPrice = Securities[killRec.uSymbol].Price;
if (tryPrice == null) return false;
decimal currUPrice = Convert.ToDecimal(tryPrice);
} else { 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) Debug($" KK ** STARTING KILLTHECOLLAR PROCESSING FOR {killRec.uSymbol} ** KK ** KK ");
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));
}
*/
doTheTrade = true;
// determine if this is an ITM call or ITM put and within 1 day of expiry
if (killRec.pSymbol != null && stockPrice <= putStrike && LUD.daysRemainingP <= 1) { /// ITM PUT -- Exercise
// determine if it's more expensive to sell or exercise ***** remember, killRec.cQty is negative for collars (sold calls)
if (killRec.currExrcsPutPnL > killRec.currSellPnL) { // for an ITM PUT, both costs should be negative
if (doDeepTracing) Log($" KK ** KK ** KK ** EXERCISING PUTS IN KILLTHECOLLAR FOR {thisSymbol} ** KK ** KK ");
if (killRec.cSymbol != null) { // Exercise the PUTs. Let longer expiry calls ride to attempt theta decay
//var shrtCall = (Option)Securities[killRec.cSymbol];
//TimeSpan daysToCallExpiry = shrtCall.Expiry.Subtract(killDate);
/*if (daysToCallExpiry.Days > 10 ) {
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 CALLS IN KILLTHECOLLAR ** KK ** KK ");
if (killRec.cQty != 0){
closeCallTicket = MarketOrder(killRec.cSymbol, -killRec.cQty); // buy the calls
if (doDeepTracing) Log(" KK ** KK ** KK ** MARKET ORDER TO BUY " + killRec.cQty.ToString() + " contracts of " + killRec.cSymbol + " at the market.");
if (doDeepTracing) Log("-");
if (closeCallTicket.Status == OrderStatus.Filled)
{
killRec.cEndPrice = closeCallTicket.AverageFillPrice;
}
} ///// killRec.cSymbol != null
}
//if (doDeepTracing) Log(" ------- ");
//if (doDeepTracing) Log(" KK ** KK ** EXERCISING PUTS IN KILLTHECOLLAR ** KK ** KK ");
closePutTicket = ExerciseOption(killRec.pSymbol, killRec.pQty); /// underlying and calls will be closed in onOrder() event
killRec.grossPnL = currExrcsPutPnL; /// log the PnL used in runtime decision
//potentialCollars.Clear();
bestSSQRColumn = new SSQRColumn();
bKTC = true;
return bKTC;
} else { //// ITM PUT but more profitable to sell the collar
if (doDeepTracing) Log(" KK ** KK ** ITM PUT MORE PROFITABLE TO SELL COLLAR THAN EXERCISE ** KK ** KK");
goto noExercise;
}
} else { /// ITM PUT ON LAST DAY -->> GET HERE IF PUT IS OTM OR THERE IS NO PUT
if (doDeepTracing) Log(" KK ** KK ** OTM PUT -- CHECKING CALL MONEY ** KK ** KK ");
}
if (killRec.cSymbol != null && stockPrice >= callStrike && LUD.daysRemainingC <= 1) {
if (doDeepTracing) Log(" KK ** KK ** CHECKING CALL STRATEGY P&L ** KK ** TT ");
killRec.grossPnL = currExrcsCallPnL; // log the PnL used in runtime decision
if (currExrcsCallPnL > currSellPnL) { // for an ITM CALL, both costs should be positive
//if (doDeepTracing) Log(" KK ** KK ** EXIT KILLTHECOLLAR AND AWAIT CALL EXERCISE** KK ** TT ");
/// /// /// /// --- --- --- --- make sure puts are sold in call exercise
/* if (killRec.pSymbol != null) {
closePutTicket = MarketOrder(killRec.pSymbol, -killRec.pQty); // sell the puts
if (doDeepTracing) Log(" KK ** MARKET ORDER TO SELL TO CLOSE " + killRec.pQty.ToString() + " contracts of " + killRec.pSymbol + " at the market." );
if (doDeepTracing) Log("-");
if (closePutTicket.Status == OrderStatus.Filled)
{
killRec.pEndPrice = closePutTicket.AverageFillPrice;
if (doDeepTracing) Log(" KK ** UPDATING PUT PRICE TO " + killRec.pEndPrice + " ** KK ** KK");
}
}
if (doDeepTracing) Log(" KK ** KK ** CLOSING POSITIONS IN KILLTHECOLLAR ** KK ** TT ");
*/
if (doDeepTracing) Log(" KK ** KK ** KK ** ITM CALL AWAITING LEAN EXERCISE -- CLOSING POSITIONS IN OnOrder() Processing ** KK ** TT ");
bKTC = true;
return bKTC;
} else {
if (doDeepTracing) Log(" KK ** KK ** KK ** ITM CALL MORE PROFITABLE TO SELL COLLAR THAN EXERCISE ** KK ** KK");
goto noExercise;
}
} else { // ITM CAll and 3rd Friday
if (doDeepTracing) Log(" KK ** KK ** KK ** OTM CALL -- POSITIONS IN KILLTHECOLLAR ** KK ** TT ");
}
//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 ** KK ** TT ");
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;
killRec.reasonForClose = reason;
killRec.endDate = CurrentSlice.Time; // set the end date of this collar
killRec.grossPnL = currSellPnL; // for logging and analysis of runtime conditions
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);
//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 (doDeepTracing) Log(" KK ** Stock Price: " + stockPrice.ToString() + " Call Bid/Offer: " + Securities[killRec.cSymbol].BidPrice.ToString() + "/" + Securities[killRec.cSymbol].AskPrice.ToString());
if (killRec.cSymbol != null) { // Buy back any calls if possible
var shrtCall = (Option)Securities[killRec.cSymbol];
// -- 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.cStrike <= stockPrice) { /// ITM Call -- use limit order
limitPrice = stockPrice - killRec.cStrike + 0.10M;
closeCallTicket = LimitOrder(killRec.cSymbol, -killRec.cQty, limitPrice);
OpenLimitOrder oLO = new OpenLimitOrder();
oLO.oTicket = closeCallTicket;
oLO.tpr = killRec;
oLO.oRight = OptionRight.Call;
oLOs.Add(oLO);
if (doDeepTracing) Log(" KK ** LIMIT ORDER TO BUY TO CLOSE SHORT CALL " + killRec.cQty.ToString() + " contracts of " + killRec.cSymbol + " at " + limitPrice.ToString());
} else {
closeCallTicket = MarketOrder(killRec.cSymbol, -killRec.cQty); // buy the calls
if (doDeepTracing) Log(" KK ** KK ** KK ** KK ** MARKET ORDER TO BUY TO CLOSE SHORT CALL" + killRec.cQty.ToString() + " contracts of " + killRec.cSymbol + " at the market.");
if (closeCallTicket.Status == OrderStatus.Filled)
{
killRec.cEndPrice = closeCallTicket.AverageFillPrice;
}
}
}
//if (doDeepTracing) Log("---------------------------------------");
if(killRec.pSymbol != null) {
if (killRec.pStrike >= stockPrice) /// ITM Put -- use limit order
{
limitPrice = killRec.pStrike - stockPrice + 0.10M;
closePutTicket = LimitOrder(killRec.pSymbol, -killRec.pQty, limitPrice); // sell the puts
OpenLimitOrder oLO = new OpenLimitOrder();
oLO.oTicket = closePutTicket;
oLO.tpr = killRec;
oLO.oRight = OptionRight.Put;
oLOs.Add(oLO);
//if (doDeepTracing) Log(" KK ** LIMIT ORDER TO SELL TO CLOSE " + killRec.pQty.ToString() + " contracts of " + killRec.pSymbol + " at " + limitPrice.ToString());
//if (doDeepTracing) Log("-");
} else {
closePutTicket = MarketOrder(killRec.pSymbol, -killRec.pQty); // sell the puts
if (doDeepTracing) Log(" KK ** KK ** KK ** MARKET ORDER TO SELL TO CLOSE " + killRec.pQty.ToString() + " contracts of " + killRec.pSymbol + " at the market." );
if (doDeepTracing) Log("-");
if (closePutTicket.Status == OrderStatus.Filled)
{
killRec.pEndPrice = closePutTicket.AverageFillPrice;
if (doDeepTracing) Log(" KK ** KK ** KK ** UPDATING PUT PRICE TO " + killRec.pEndPrice + " ** 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("-");
}
///////////////////////////////////////////////////////////////////////////////////
// RollPutUp
////////////////////////////////////////////////////////////////////////////////////
public void RollPutUp(SSQRColumn bestSSQRColumn, ref LookupData LUD, TradePerfRec oldTPR, decimal sPrice, bool isCollar){
int rollQty = oldTPR.pQty; // 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;
OrderTicket closePutTicket; // used to close the open puts
OrderTicket rollPutTicket; // used to open (roll up) new puts
OrderTicket closeCallTicket;
OrderTicket rollCallTicket;
if (haltProcessing) {
Log(" RP ** RP ** RP ** Logging ROLLPUT RR ** RR ** RR **");
}
// 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[LUD.uSymbol].intTPRCntr =+ 1;
//if (doDeepTracing) Log(" RP ** MARKET ORDER TO SELL " + rollQty + " contracts of " + oldTPR.pSymbol + " at market");
closePutTicket = MarketOrder(oldTPR.pSymbol, -rollQty); // sell the puts
//if (doDeepTracing) Log(" RP ** MARKET ORDER TO BUY " + rollQty + " contracts of " + bestPutSpread.newPutSymb + " at market");
rollPutTicket = MarketOrder(bestSSQRColumn.putSymbol, rollQty); // buy the higher puts
// first adjust the old tradePerfRec to decrement pQty and uQty. It remains open to be processed for the remaining covered, collared stock.
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)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// // // 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();
if (closePutTicket.Status == OrderStatus.Filled)
{
oldTPR.pEndPrice = closePutTicket.AverageFillPrice;
}
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 = oldTPR.index + 1; // 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.pSymbol = bestSSQRColumn.putSymbol;
newTPR1.pStrike = bestSSQRColumn.putStrike;
newTPR1.expDate = bestSSQRColumn.putExpiry;
newTPR1.pQty = oldTPR.pQty;
newTPR1.startDate = CurrentSlice.Time;
newTPR1.isInitializer = false;
newTPR1.isSecondary = false;
newTPR1.numDividends = oldTPR.numDividends;
newTPR1.divIncome = oldTPR.divIncome;
newTPR1.tradeRecCount = oldTPR.tradeRecCount + 1;
newTPR1.ROR = oldTPR.ROR;
newTPR1.ROC = oldTPR.ROC;
newTPR1.CCOR = oldTPR.CCOR;
newTPR1.tradeCriteria = oldTPR.tradeCriteria;
newTPR1.strtngCndtn = "PUT ROLL UP" + (bestSSQRColumn.callSymbol!=null ? " WITH PERK CALL" : "");
if (rollPutTicket.Status == OrderStatus.Filled)
{
newTPR1.pStartPrice = rollPutTicket.AverageFillPrice;
}
oldTPR.reasonForClose = $"P ROLLUP STOCK APPRECIATION: {oldTPR.uSymbol.Value} : {String.Format("{0:0.00}",sPrice-oldTPR.uStartPrice)} COSTINg {(newTPR1.pStartPrice - oldTPR.pEndPrice).ToString()}";
if (doDeepTracing) Log($" RP ** RP ** STARTING ROLL PUT UP PROCESSING ** RP ** RP ");
if (doDeepTracing) Log($" RP ** RP ** {oldTPR.uSymbol.Value} ** RP ** RP ");
if (doDeepTracing) Log($" RP ** Appreciation: {String.Format("{0:0.00}",sPrice-oldTPR.uStartPrice)} COSTING {(newTPR1.pStartPrice - oldTPR.pEndPrice).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.cSymbol!=null){ // buy back old calls /// NOTE: NEED TO VERIFY THIS SITUATION IS HANDLED PROPERLY
if (oldTPR.cStrike < sPrice) {
var limitPrice = (Securities[oldTPR.cSymbol].AskPrice - Securities[oldTPR.cSymbol].BidPrice) / 2M; // get the mid point for the limit price
rollCallTicket = LimitOrder(oldTPR.cSymbol, -oldTPR.cQty, limitPrice); // sell limit order
OpenLimitOrder oLO = new OpenLimitOrder();
oLO.oTicket = rollCallTicket;
oLO.tpr = oldTPR;
oLO.oRight = OptionRight.Call;
oLOs.Add(oLO);
//if (closePutTicket.Status == OrderStatus.Submitted) oldTradeRec.pEndPrice = limitPrice;
} else {
rollCallTicket = MarketOrder(oldTPR.cSymbol, -oldTPR.cQty);
if (rollCallTicket.Status == OrderStatus.Filled)
{
oldTPR.cEndPrice = rollCallTicket.AverageFillPrice;
}
}
if (doDeepTracing) Log($" RP ** RP ** {bestSSQRColumn.callSymbol.Value} ** RP ** RP ");
}
if(bestSSQRColumn.callSymbol!=null){ // sell calls to generate income /// NOTE: NEED TO VERIFY THIS SITUATION IS HANDLED PROPERLY
newTPR1.cStrike = bestSSQRColumn.callStrike;
newTPR1.thetaExpiration = bestSSQRColumn.callExpiry;
newTPR1.cSymbol = bestSSQRColumn.callSymbol;
newTPR1.cQty = -(int)-rollQty;
if (bestSSQRColumn.callStrike < newTPR1.uStartPrice) {
var limitPrice = (Securities[bestSSQRColumn.callSymbol].AskPrice - Securities[bestSSQRColumn.callSymbol].BidPrice) / 2M; // get the mid point for the limit price
rollCallTicket = LimitOrder(bestSSQRColumn.callSymbol, -rollQty, limitPrice); // sell limit order
OpenLimitOrder oLO = new OpenLimitOrder();
oLO.oTicket = rollCallTicket;
oLO.tpr = newTPR1;
oLO.oRight = OptionRight.Call;
oLOs.Add(oLO);
//if (closePutTicket.Status == OrderStatus.Submitted) oldTradeRec.pEndPrice = limitPrice;
} else {
rollCallTicket = MarketOrder(bestSSQRColumn.callSymbol, -rollQty);
if (rollCallTicket.Status == OrderStatus.Filled)
{
newTPR1.cStartPrice = rollCallTicket.AverageFillPrice;
newTPR1.cQty = (int)rollCallTicket.QuantityFilled;
}
}
if (doDeepTracing) Log($" RP ** RP ** ROLLING COLLAR CALLS ** RP ** RP ");
if (doDeepTracing) Log($" RP ** RP ** BUYING: {(oldTPR.cSymbol.Value!=null ? oldTPR.cSymbol.Value : "-- no call --")} | SELLING: {bestSSQRColumn.callSymbol.Value} ** RP ** RP ");
if (doDeepTracing) Log($" RP ** Call Appreciation: {(oldTPR.cSymbol != null ? String.Format("{0:0.00}",Securities[oldTPR.cSymbol].AskPrice - oldTPR.cStartPrice) : "-- NA --")} COSTING {(oldTPR.cSymbol != null ? (Securities[newTPR1.cSymbol].BidPrice - Securities[oldTPR.cSymbol].AskPrice).ToString() : newTPR1.cStartPrice)} ** RP ** RP ");
}
if (doDeepTracing) Log(" -- ");
if (doTracing) Log(" RP ** RP ** END PUT UP ** RP ** RP ** ");
tprsToClose.Add(oldTPR);
tprsToOpen.Add(newTPR1);
return;
}
///////////////////////////////////////////////////////////////////////////////////
//
// RollTheCollar
//
////////////////////////////////////////////////////////////////////////////////////
public bool RollTheCollar(ref 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;
decimal maxWingFactor = 0;
decimal thisWingFactor = 0;
decimal wingPremium = 0;
OrderTicket closeCallTicket;
OrderTicket closePutTicket;
OrderTicket closeWCallTicket;
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.pStrike >= stockPrice) /// ITM Put -- use limit order to close
{
limitPrice = oldTradeRec.pStrike - stockPrice + 0.10M;
if (doDeepTracing) Log(" @R @R @R LIMIT ORDER TO SELL " + oldTradeRec.pQty.ToString() + " contracts of " + oldTradeRec.pSymbol + " at " + limitPrice.ToString());
closePutTicket = LimitOrder(oldTradeRec.pSymbol, -oldTradeRec.pQty, limitPrice); // sell the puts
// closePutTicket = MarketOrder(oldTradeRec.pSymbol, -oldTradeRec.pQty); // sell the puts
OpenLimitOrder oLO = new OpenLimitOrder();
oLO.oTicket = closePutTicket;
oLO.tpr = oldTradeRec;
oLO.oRight = OptionRight.Put;
oLOs.Add(oLO);
//if (closePutTicket.Status == OrderStatus.Submitted) oldTradeRec.pEndPrice = limitPrice;
} else {
if (doDeepTracing) Log(" @R @R @R @R MARKET ORDER TO SELL TO CLOSE " + oldTradeRec.pQty.ToString() + " contracts of " + oldTradeRec.pSymbol + " at market");
closePutTicket = MarketOrder(oldTradeRec.pSymbol, -oldTradeRec.pQty); // sell the puts
}
if (closePutTicket.Status == OrderStatus.Filled)
{
oldTradeRec.pEndPrice = closePutTicket.AverageFillPrice;
if (doDeepTracing) Log(" @R @R @R @R UPDATING PUT " + oldTradeRec.pSymbol + " END PRICE @ " + oldTradeRec.pEndPrice );
}
if (doDeepTracing) Log("-");
// 2nd, buy back the long call
doTheTrade = true;
if(oldTradeRec.cSymbol != null){
if (oldTradeRec.cStrike <= stockPrice) /// ITM Call -- use limit order
{ /// call QTY should be negative from the opening short trade
limitPrice = stockPrice - oldTradeRec.cStrike + 0.10M;
if (doDeepTracing) Log(" @R @R @R @R LIMIT ORDER TO BUY TO CLOSE " + oldTradeRec.cQty.ToString() + " contracts of " + oldTradeRec.cSymbol + " at " + limitPrice.ToString());
closeCallTicket = LimitOrder(oldTradeRec.cSymbol, -oldTradeRec.cQty, limitPrice);
//if (closeCallTicket.Status == OrderStatus.Submitted) oldTradeRec.cEndPrice = limitPrice;
OpenLimitOrder oLO = new OpenLimitOrder();
oLO.oTicket = closeCallTicket;
oLO.tpr = oldTradeRec;
oLO.oRight = OptionRight.Call;
oLOs.Add(oLO);
} else {
if (doDeepTracing) Log(" @R @R @R @R MARKET ORDER TO BUY TO CLOSE " + oldTradeRec.cQty.ToString() + " contracts of " + oldTradeRec.cSymbol + " at market");
closeCallTicket = MarketOrder(oldTradeRec.cSymbol, -oldTradeRec.cQty); // buy the calls
if (doDeepTracing) Log(" @R @R @R @R UPDATING CALL " + oldTradeRec.cSymbol + "END PRICE @ " + oldTradeRec.cEndPrice );
}
if (closeCallTicket.Status == OrderStatus.Filled)
{
oldTradeRec.cEndPrice = closeCallTicket.AverageFillPrice;
if (doDeepTracing) Log(" @R @R @R @R UPDATING CALL END PRICE @ " + oldTradeRec.cEndPrice );
}
}
// 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.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.pStrike = bestSSQRColumn.putStrike;
thisNewTPRec.cStrike = bestSSQRColumn.callStrike;
thisNewTPRec.wcStrike = bestSSQRColumn.wCallStrike;
thisNewTPRec.expDate = bestSSQRColumn.putExpiry; // 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 );
var putTicket = MarketOrder(tradablePut, (1 + thisWingFactor) * optionsToTrade);
if (putTicket.Status == OrderStatus.Filled)
{
thisNewTPRec.pSymbol = tradablePut;
thisNewTPRec.pStartPrice = putTicket.AverageFillPrice;
thisNewTPRec.pQty = (int)putTicket.QuantityFilled;
//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 SELL 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, 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.exDivdnDate, thisMonthExpiry, true, false);
Dictionary<int, DateTime> callExpiries = GetOptionExpiries(thisSlice.Time, LD.exDivdnDate, 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($" TD ** TD ** TD ** TD *** 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 (bestTradableColumn.ROR < 1m){
if(LD.doTracing) algo.Debug($" TD ** TD ** TD ** TD *** 0 or empty SSQR in BestCollar 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;
}
}
}#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 bool CheckRolling(CollarAlgorithm algo, LookupData LUD)
{
try {
bool hasPut = false;
bool hasCall = false;
Slice slc = algo.CurrentSlice;
Symbol symbUndr = this.uSymbol;
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 (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.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is no longer in SDBS.");
return true;
}
// if (LUD.haltProcessing)
// {
// algo.Debug("we are here");
// }
if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling for " + symbUndr.Value + " @" + slc.Time.ToString() );
if (slc.ContainsKey(symbUndr) )
{
// var tryPrice = slc[symbUndr].Price;
var tryPrice = algo.Securities[symbUndr].Price;
LUD.initialTargetEndDate = algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate;
LUD.decOneYearPriceTarget = algo.symbolDataBySymbol[this.uSymbol].decOneYearPriceTarget_Initial;
if (tryPrice == null)
{
if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling found no price data for " + symbUndr.Value + " @" + slc.Time.ToString() );
return false;
}
stkPrc = Convert.ToDecimal(tryPrice);
if (this.pQty != 0){
if (!slc.OptionChains.TryGetValue(this.pSymbol.Canonical, out var pOptChain))
{
if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling 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.BidPrice;
} else {
if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling found no Put Contract data for " + this.pSymbol.Value + " @" + slc.Time.ToString() );
return false;
}
hasPut = true;
}
if (this.cQty != 0){
if (!slc.OptionChains.TryGetValue(this.cSymbol.Canonical, out var cOptChain))
{
if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling found no Call chains data for " + this.pSymbol.Value + " @" + slc.Time.ToString() );
return false;
} else if (cOptChain.Contracts.TryGetValue(this.cSymbol, out var cContract)){
callPrc = cContract.AskPrice;
} else {
if (LUD.doTracing) algo.Log(" ************** TPR CheckRolling found no Call Contract data for " + this.pSymbol.Value + " @" + slc.Time.ToString() );
return false;
}
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;
LUD.GetNextExDate(algo); //// get NextExDate for this symbol
this.GetPnLs(algo, ref LUD, ref stkPrc, ref callPrc, ref putPrc);
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 (hasCall && LUD.daysRemainingDiv < 4 && LUD.daysRemainingDiv > 0)
{
LUD.SSQRMatrix.Clear();
if (LUD.doTracing) algo.Debug(" ************** TPR CheckDivRoll " + symbUndr.Value + " @" + slc.Time.ToString() );
if (this.CheckDivRoll(algo, ref stkPrc, ref 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(" ************** TPR CheckCallRoll " + symbUndr.Value + " @" + slc.Time.ToString() );
if (this.CheckCallRoll(algo, ref 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(" ************** TPR CheckPutRoll " + symbUndr.Value + " @" + slc.Time.ToString() );
if (this.CheckPutRoll(algo, ref 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(" ************** TPR CheckOTMRoll " + symbUndr.Value + " @" + slc.Time.ToString() );
if (CheckOTMRoll(algo, ref 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.
//*************************************************************************************************
private void GetPnLs(CollarAlgorithm algo, ref LookupData LUD, ref decimal stockPrice, ref decimal currCallAskPrice, ref decimal currPutBidPrice)
{
this.currSellPnL = (this.uQty*(stockPrice-this.uStartPrice)) + (100*this.pQty*(currPutBidPrice - this.pStartPrice)) +
(-100*this.cQty*(this.cStartPrice - currCallAskPrice)); /// + (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*(this.cStartPrice - currCallAskPrice)); /// + (100*this.wcQty*(this.wcEndPrice - this.wcStartPrice));
} else {this.currExrcsPutPnL = -1;}
if (this.cStrike < stockPrice ) {
this.currExrcsCallPnL = (this.uQty*(this.cStrike-this.uStartPrice)) + (100*this.pQty*(currPutBidPrice - this.pStartPrice)) +
(-100*this.cQty*(this.cStartPrice - 0)); /// + (100*this.wcQty*(this.wcEndPrice - this.wcStartPrice));
} else {this.currExrcsCallPnL = -1;}
}
//*************************************************************************************************
//************** GetCorrspndingPut *******************************************************
//*************************************************************************************************
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 , ref LookupData LUD, ref decimal stockPrice, ref decimal currCallAskPrice, ref decimal currPutBidPrice)
{
bool killed = false;
try {
bool isRolled = false;
// risk of options expiration WITHOUT EXERCISE
if (LUD.doDeepTracing) algo.Debug($" ************** BEGIN OTM OPTIONS CALC FOR {LUD.uSymbol} ****************");
LUD.loadVEData(algo);
algo.symbolDataBySymbol[LUD.uSymbol].decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget;
Slice slD = algo.CurrentSlice;
/* if (this.uSymbol.Value == "CNP"){
foreach(var kvp in algo.Securities) /// make sure there's no leaking of abandoned stocks or options
{
try{
var security = kvp.Value;
if (security.Invested)
{
algo.Debug($"|||| |||| |||| Package: {security.Symbol} : {security.Holdings.Quantity} @ {security.BidPrice} by {security.AskPrice}");
}
} catch (Exception errMsg)
{
algo.Debug(" ERROR at 283 in TradeLogging.cs OTMRoll" + errMsg );
}
}
algo.Debug("ha");
}
*/ //bestSSQRColumn = GetBestSSQR(data, LUD.uSymbol, nextExDate);
SSQRColumn bestSSQRColumn = algo.GetBestCollar(algo, LUD);
if (LUD.haltProcessing)
{
algo.Log(" Logging OTM OPTIONS CALC ");
}
if (LUD.SSQRMatrix.Count == 0) {
if (LUD.daysRemainingC <= 1 | LUD.daysRemainingP <= 1) {
killed = isRolled = algo.KillTheCollar(this, ref LUD, "ABORT OTM ROLL -- NO POT COLLARS FOR " + LUD.uSymbol );
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){
algo.symbolDataBySymbol[this.uSymbol].isRollable = false;
algo.SymbolsToRemove.Add(this.uSymbol);
}
LUD.SSQRMatrix.Clear();
bestSSQRColumn = new SSQRColumn();
if (LUD.doDeepTracing) algo.Debug($" ************** END OTM OPTIONS KILL FOR {LUD.uSymbol} ****************");
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} -- 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.Debug($" ************** null bestSSQRColumn in OTM Expiry Approachment FOR {LUD.uSymbol} *************");
if (LUD.doTracing) algo.Log($" ************** END OTM OPTIONS CALC FOR {LUD.uSymbol} ****************");
if (LUD.daysRemainingC <= 1 | LUD.daysRemainingP <= 1) {
killed = isRolled = algo.KillTheCollar(this, ref LUD, "KILLED IN OTM PROCESSING -- NO VIABLE SSQRS FOR " + LUD.uSymbol );
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){
algo.symbolDataBySymbol[this.uSymbol].isRollable = false;
algo.SymbolsToRemove.Add(this.uSymbol);
}
LUD.SSQRMatrix.Clear();
bestSSQRColumn = new SSQRColumn();
if (LUD.doDeepTracing) algo.Debug($" ************** 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.Debug($" ************** 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 * 4m) + LUD.decOneYearPriceTarget) > 1.05m * stockPrice);
if (goodThresh) // roll the position forward
{
if (LUD.doTracing) algo.Debug($" ************** BEGIN OTM OPTIONS ROLL FOR {LUD.uSymbol} ****************");
bool bRollable = algo.symbolDataBySymbol[LUD.uSymbol].isRollable;
if (bRollable && (this.currSellPnL > 0 | this.uQty * bestSSQRColumn.netIncome > Math.Abs(this.currSellPnL))){ // only roll the collar if the current record may be closed profitably-- otherwise seek exercise in kill
//if (currSellPnL > 0) {
if (algo.RollTheCollar(ref LUD, this, ref bestSSQRColumn, "ROLLED IN OTM EXPIRATION ")) {
isRolled = true;
if (LUD.doDeepTracing) algo.Debug($" ************** 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.Debug($" ************** 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 );
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){
algo.symbolDataBySymbol[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
killed = isRolled = algo.KillTheCollar(this, ref LUD, "KILLED IN OTM PROCESSING -- UNPROFITABLE ROLL FOR " + LUD.uSymbol + " ON THE LAST DAY" );
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){
algo.symbolDataBySymbol[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");
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & killed){
algo.symbolDataBySymbol[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 , ref LookupData LUD, ref decimal stockPrice, ref decimal currCallAskPrice, ref decimal currPutBidPrice)
{
try{
bool isRolled = 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);
algo.symbolDataBySymbol[LUD.uSymbol].decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget;
SSQRColumn bestSSQRColumn = algo.GetBestCollar(algo, LUD);
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");
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 CHECK IMPLICIT 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");
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){
algo.symbolDataBySymbol[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("--------");
} 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 * 4m) + LUD.decOneYearPriceTarget) > 1.05m * stockPrice);
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 (currCallAskPrice + bestSSQRColumn.callStrike < 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: " + currCallAskPrice + " Strike: " + bestSSQRColumn.callStrike + " 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 CALL ASSIGNMENT");
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){
algo.symbolDataBySymbol[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 = algo.symbolDataBySymbol[LUD.uSymbol].isRollable;
if (bRollable && (this.currSellPnL > 0 | this.uQty * bestSSQRColumn.netIncome > 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 (algo.RollTheCollar(ref 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;
} else {
if (LUD.daysRemainingP <= 1) {
isRolled = algo.KillTheCollar(this, ref LUD, "KILLED IN FAILED ITM PUT ROLL");
if( isRolled & algo.symbolDataBySymbol.ContainsKey(this.uSymbol)) {
algo.symbolDataBySymbol[this.uSymbol].isRollable = false;
algo.SymbolsToRemove.Add(this.uSymbol);
}
}
}
LUD.SSQRMatrix.Clear();
bestSSQRColumn = new SSQRColumn();
} else { // un profitable roll
if (LUD.daysRemainingP <= 1) {
if (LUD.doTracing) algo.Log($" ************** UNPROFITABLE ITM PUT ROLL FOR {LUD.uSymbol} ON LAST DAY -- ATTEMPT KILL");
isRolled = algo.KillTheCollar(this, ref LUD, "KILL- LOSS IN 1st TPR IN ITM PUT ROLL" );
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){
algo.symbolDataBySymbol[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 ");
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){
algo.symbolDataBySymbol[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, ref LookupData LUD)
{
int daysRemaining = LUD.daysRemainingDiv;
try{
if (LUD.doTracing) algo.Debug("//************** CheckDividendRoll ****************************" );
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 (!slc.OptionChains.TryGetValue(symbCorrSpndngPut.Canonical, out var cpOptChain))
{
if (LUD.doTracing) algo.Log(" ************** TPR CheckDividendRol found no Put chains data for corresponding put " + symbCorrSpndngPut.Value + " on " + slc.Time.ToString() );
return false;
}
if (cpOptChain.Contracts.TryGetValue(symbCorrSpndngPut, out var cpContract)){
if (LUD.doTracing) algo.Log(" ************** TPR CheckDividendRoll found Put Contract data for corresponding put " + symbCorrSpndngPut.Value + " on " + slc.Time.ToString() + " @ " + decCrrSpndgPutPrice.ToString());
decCrrSpndgPutPrice = cpContract.AskPrice;
} else {
if (LUD.doTracing) algo.Log(" ************** TPR CheckDividendRoll found no Put Contract data for corresponding put " + symbCorrSpndngPut.Value + " on " + slc.Time.ToString() );
/*var cPutSymbol = QuantConnect.Symbol.CreateOption(
this.uSymbol,
Market.USA,
OptionStyle.American,
OptionRight.Put,
this.cSymbol.ID.StrikePrice,
this.cSymbol.ID.Date);
////*.
if (LUD.doTracing) algo.Log(" ************** TPR CheckDividendRoll found no Put Contract data for corresponding put " + symbCorrSpndngPut.Value + " on " + slc.Time.ToString() );
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);
algo.symbolDataBySymbol[LUD.uSymbol].decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget;
bestSSQRColumn = algo.GetBestCollar(algo, 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" );
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 * 4m) + LUD.decOneYearPriceTarget) > 1.05m * stockPrice);
if (goodThresh) // roll the position forward
{
//newRollDate = slD.Time.Date;
// don't do the roll if one has just been done --
// sometimes ex-dividend dates are within 10 days of options expiration and a roll has already been done
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 (this.currSellPnL > 0 | this.uQty * bestSSQRColumn.netIncome > Math.Abs(this.currSellPnL)) { // Roll solely if we can sell the current collar profitably
bool didTheTrade = algo.RollTheCollar(ref 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" ); // 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);
}
}
}
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" ); // 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, ref 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;
Slice slD = algo.CurrentSlice;
if (LUD.doTracing) algo.Log($" ************** BEGIN ITM CALL CALC FOR {LUD.uSymbol} ****************");
LUD.loadVEData(algo);
algo.symbolDataBySymbol[LUD.uSymbol].decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget;
//bestSSQRColumn = GetBestSSQR(data, LUD.uSymbol, nextExDate);
SSQRColumn bestSSQRColumn = algo.GetBestCollar(algo, LUD);
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 FORCED ASSIGNMENT PROCESSING FOR {LUD.uSymbol} -- NO POTENTIAL COLLARS ON LAST DAY *************");
isRolled = algo.KillTheCollar(this, ref LUD, "KILL IN ITM CALL ASSIGNMENT -- NO POTENTIAL COLLARS ON LAST DAY");
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($" OOOOOOOOO TT END CHECK IMPLICIT CALL ASSIGNMENT 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");
algo.KillTheCollar(this, ref LUD, "KILL IN ITM CALL ASSIGNMENT -- EMPTY BEST COLLAR ON LAST DAY");
LUD.SSQRMatrix.Clear();
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){
algo.symbolDataBySymbol[this.uSymbol].isRollable = false;
algo.SymbolsToRemove.Add(this.uSymbol);
}
bestSSQRColumn = new SSQRColumn();
if (LUD.doTracing) algo.Log($" TT END CHECK IMPLICIT CALL ASSIGNMENT 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 * 4m) + LUD.decOneYearPriceTarget) > 1.05m * 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 (currCallAskPrice + bestSSQRColumn.callStrike < 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.callSymbol].Price + " Strike: " + bestSSQRColumn.callStrike + " 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
{
algo.KillTheCollar(this, ref LUD, "ABORT ITM CALL ROLL TO PREVENT EXERCISE ON LAST DAY" ); // 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);
}
}
LUD.SSQRMatrix.Clear();
bestSSQRColumn = new SSQRColumn();
if (LUD.doTracing) algo.Log($" TT END CHECK IMPLICIT CALL ASSIGNMENT FOR {LUD.uSymbol} OOOOOOOOO");
if (LUD.doTracing) algo.Log($" ------------------------------------------------ ");
return isRolled;
}
bool bRollable = algo.symbolDataBySymbol[LUD.uSymbol].isRollable;
if (bRollable && (this.currSellPnL > 0 | this.uQty * bestSSQRColumn.netIncome > 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(ref 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 **************");
algo.KillTheCollar(this, ref LUD, "KILL- FAILED ROLL ON LAST DAY" ); // Goto KillTheCollar and determine whether to close or allow call assignment there
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($" TT END CHECK IMPLICIT CALL ASSIGNMENT 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 **************");
algo.KillTheCollar(this, ref LUD, "KILL- LOSS IN 1st PACKAGE AND UNPROFITABLE 2nd PACKAGE" ); // Goto KillTheCollar and determine whether to close or allow call assignment there
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($" TT END CHECK IMPLICIT CALL ASSIGNMENT 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 FORCED ASSIGNMENT PROCESSING FOR {LUD.uSymbol}-- BAD THRESH ****************");
if (LUD.daysRemainingC <= 1) {
if (LUD.doTracing) algo.Log($" ************** KILL COLLAR IN ITM CALL FORCED ASSIGNMENT PROCESSING FOR {LUD.uSymbol} -- BAD THRESH ON LAST DAY");
algo.KillTheCollar(this, ref LUD, "KILL ITM CALL -- PREVENT ASSIGNMENT");
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol) & isRolled){
algo.symbolDataBySymbol[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 CALL ASSIGNMENT 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 ASSIGNMENT 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 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;
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 partial class TradePerfRec
{
public bool CheckRollingUp(CollarAlgorithm algo, LookupData LUD){
bool hasPut = false;
bool hasCall = false;
bool isCollar = false;
Slice slc = algo.CurrentSlice;
Symbol symbUndr = this.uSymbol;
LUD.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(!algo.symbolDataBySymbol.ContainsKey(this.uSymbol)) {
if(LUD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is no longer in SDBS! *^*^*^*^ Check order flow *^*^*^");
return false;
}
if(!algo.symbolDataBySymbol[this.uSymbol].isRollable){
if(LUD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is not rollable up -- check if this TPR is going to be closed.");
return false;
}
// if (LUD.haltProcessing)
// {
// algo.Debug("we are here");
// }
if (LUD.doTracing) algo.Log(" ************** TPR CheckRollingUp for " + symbUndr.Value + " @" + slc.Time.ToString() );
if (slc.ContainsKey(symbUndr) )
{
// var tryPrice = slc[symbUndr].Price;
var tryPrice = algo.Securities[symbUndr].Price;
if (tryPrice == null)
{
if (LUD.doTracing) algo.Log(" ************** TPR CheckRollingUp found no price data for " + symbUndr.Value + " @" + slc.Time.ToString() );
return false;
}
stkPrc = Convert.ToDecimal(tryPrice);
} else if(LUD.doTracing){
algo.Log(" ************** TPR CheckRollingUp found no slice data for " + symbUndr.Value + " @" + slc.Time.ToString() );
return false;
}
if (this.pQty != 0){
if (!slc.OptionChains.TryGetValue(this.pSymbol.Canonical, out var pOptChain))
{
if (LUD.doTracing) algo.Log(" ************** TPR CheckRollingUp 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.BidPrice;
} else {
if (LUD.doTracing) algo.Log(" ************** TPR CheckRollingUp found no Put Contract data for " + this.pSymbol.Value + " @" + slc.Time.ToString() );
return false;
}
hasPut = true;
}
if (this.cQty != 0){
if (!slc.OptionChains.TryGetValue(this.cSymbol.Canonical, out var cOptChain))
{
if (LUD.doTracing) algo.Log(" ************** TPR CheckRollingUp found no Call chains data for " + this.pSymbol.Value + " @" + slc.Time.ToString() );
return false;
} else if (cOptChain.Contracts.TryGetValue(this.cSymbol, out var cContract)){
callPrc = cContract.AskPrice;
} else {
if (LUD.doTracing) algo.Log(" ************** TPR CheckRollingUp found no Call Contract data for " + this.pSymbol.Value + " @" + slc.Time.ToString() );
return false;
}
if (LUD.doTracing) algo.Log(" ************** TPR CheckRollingUp HAS A CALL Contract data for " + this.pSymbol.Value + " @" + slc.Time.ToString() );
hasCall = true;
}
var upChains = slc.OptionChains.get(algo.symbolDataBySymbol[this.uSymbol].optSymbol);
if(upChains == null) {
if(LUD.doTracing) algo.Log($" ************** TPR CheckRollingUp failed to locate Contract data for {symbUndr.Value} @ {slc.Time.ToString()}" );
return false;
}
var atmPut = upChains.Where(o=>o.Right == OptionRight.Put)
.OrderBy(o=> Math.Abs(o.Strike - stkPrc))
.FirstOrDefault();
var highPut = upChains.Where(o=>o.Right == OptionRight.Put)
.OrderByDescending(o=>o.Strike)
.FirstOrDefault();
var lowPut = upChains.Where(o=>o.Right == OptionRight.Put)
.OrderBy(o=>o.Strike)
.FirstOrDefault();
var strikesList = upChains.DistinctBy(o => o.Strike).ToList();
if (strikesList.Count() == 1){
strikeStep = 2.5m;
} else {
strikeStep = (highPut.Strike - lowPut.Strike)/(strikesList.Count() - 1m);
}
if (strikeStep % 0.5m != 0) strikeStep = Math.Round(strikeStep/0.5m) * 0.5m;
if(LUD.doTracing) algo.Log($" ************** 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;
}
LUD.loadVEData(algo);
decimal oneYrTrgtPrice_Current = algo.symbolDataBySymbol[this.uSymbol].decOneYearPriceTarget_Current = LUD.decOneYearPriceTarget;
decimal oneYrTrgtPrice_Initial = algo.symbolDataBySymbol[this.uSymbol].decOneYearPriceTarget_Initial;
decimal trgtPrice = oneYrTrgtPrice_Initial > oneYrTrgtPrice_Current ? oneYrTrgtPrice_Initial : oneYrTrgtPrice_Current;
OptionContract perkCall = null;
if (monthsRemaining <=2 & !hasCall) {
if (stkPrc > 0.90m * trgtPrice & !hasCall)
{
perkCall = upChains.Where(o=>o.Right == OptionRight.Call &
o.Strike <= stkPrc &
DateTime.Compare(o.Expiry, algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate)<0)
.OrderByDescending(o=>o.Expiry)
.ThenByDescending(o=> Math.Abs(o.Strike - stkPrc))
.FirstOrDefault();
}
}
if (monthsRemaining > 2) monthsRemaining = 2;
if (LUD.doTracing) algo.Log( " tprtprtprtprtprtprtpr --- logging upChains --- tprtprtrptrptrptpr");
if (LUD.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 targetPut = upChains.Where(o=>o.Right == OptionRight.Put &
o.Strike <= atmPut.Strike - strikeStep &
DateTime.Compare(o.Expiry, slc.Time.AddMonths(monthsRemaining))>=0 &
o.AskPrice - putPrc <= 0.3m * (stkPrc - this.uStartPrice))
.OrderByDescending(o=>o.Expiry)
.ThenByDescending(o=>o.Strike)
.FirstOrDefault();
if (targetPut == null || targetPut.Strike <= this.pSymbol.ID.StrikePrice){
if (LUD.doDeepTracing) algo.Log(" tprtprtprtprtprtprtpr --- ---- ---- Target put not found 1X -- decrement to 1 month.");
targetPut = upChains.Where(o=>o.Right == OptionRight.Put &
o.Strike <= atmPut.Strike - strikeStep &
DateTime.Compare(o.Expiry, slc.Time.AddMonths(1))>=0 &
o.AskPrice - putPrc <= 0.3m * (stkPrc - this.uStartPrice))
.OrderByDescending(o=>o.Expiry)
.ThenByDescending(o=>o.Strike)
.FirstOrDefault();
if (targetPut == null){
if (LUD.doDeepTracing) algo.Log(" tprtprtprtprtprtprtpr --- ---- ---- Target put not found 2X -- exit.");
return false;
}
}
if(hasCall && (this.cStrike < stkPrc | DateTime.Compare(this.cSymbol.ID.Date, algo.symbolDataBySymbol[this.uSymbol].initialTargetEndDate) < 0)) { // have an ITM call or a Call that has a short expiry
perkCall = upChains.Where( o=> o.Right == OptionRight.Call &&
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)
.OrderByDescending(o=>o.Expiry)
.ThenBy(o=>Math.Abs(o.Strike-trgtPrice)).FirstOrDefault();
isCollar = true;
}
SSQRColumn thisSSQRColumn = algo.buildSSQRColumn(targetPut, perkCall, algo, LUD);
if (thisSSQRColumn != null)
{
LUD.SSQRMatrix.Add(thisSSQRColumn);
} else if(LUD.doTracing) algo.Debug($"@@@@@ @@@@@ -- failed to get VE4 SSSQRColumn for {LUD.uSymbol}.");
if (LUD.doDeepTracing) algo.Log($" tprtprtprtprtprtprtpr --- ---- Calling RollPutUP for {LUD.uSymbol} because the current price {stkPrc.ToString()} is greater than start: {this.uStartPrice.ToString()}. ");
//algo.RollPutUp(targetPut, perkCall, ref LUD, this, stkPrc);
algo.RollPutUp(thisSSQRColumn, ref LUD, this, stkPrc, isCollar);
var orderedSSQRMatrix = LUD.SSQRMatrix.OrderBy(p => p.upsidePotential);
algo.IterateOrderedSSQRMatrix(orderedSSQRMatrix);
//algo.didTheTrade = false;
return true;
}
public bool HandleCollapse(CollarAlgorithm algo, LookupData LUD){
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(!algo.symbolDataBySymbol.ContainsKey(this.uSymbol)) {
if(LUD.doTracing) algo.Log($" ************** Symbol {this.uSymbol.Value} is no longer in SDBS! *^*^*^*^ Check order flow *^*^*^");
return false;
}
var tryPrice = algo.Securities[symbUndr].Price;
if (tryPrice == null)
{
if (LUD.doTracing) algo.Log(" ************** TPR HandleCollapse found no U price data for " + symbUndr.Value + " @" + slc.Time.ToString() );
return false;
}
stkPrc = Convert.ToDecimal(tryPrice);
if(this.pSymbol != null){
tryPrice = algo.Securities[this.pSymbol].Price;
if (tryPrice == null)
{
if (LUD.doTracing) algo.Log(" ************** TPR HandleCollapse found no put price data for " + symbUndr.Value + " @" + slc.Time.ToString() );
return false;
}
putPrc = Convert.ToDecimal(tryPrice);
hasPut = true;
}
if(this.cSymbol != null){
tryPrice = algo.Securities[this.cSymbol].Price;
if (tryPrice == null)
{
if (LUD.doTracing) algo.Log(" ************** TPR HandleCollapse found no call price data for " + symbUndr.Value + " @" + slc.Time.ToString() );
return false;
}
callPrc = Convert.ToDecimal(tryPrice);
hasCall = true;
}
LUD.dtTst = slc.Time;
LUD.GetNextExDate(algo); //// get NextExDate for this symbol
this.GetPnLs(algo, ref LUD, ref stkPrc, ref callPrc, ref putPrc);
if (hasCall){ //// buy back short call
handleCallTicket = algo.MarketOrder(this.cSymbol, -this.cQty); // buy the calls
if (LUD.doDeepTracing) algo.Log(" HC ** HC ** HC ** MARKET ORDER TO BUY " + this.cQty.ToString() + " contracts of " + this.cSymbol + " at the market.");
if (LUD.doDeepTracing) algo.Log("-");
if (handleCallTicket.Status == OrderStatus.Filled)
{
this.cEndPrice = handleCallTicket.AverageFillPrice;
}
}
if (hasPut){ //// sell or exercise put
if (this.currExrcsPutPnL > this.currSellPnL) {
if (LUD.doDeepTracing) algo.Log($" HC ** HC ** HC ** EXERCISE {this.pQty.ToString()} PUT contracts of {this.pSymbol} at {this.pStrike.ToString()}.");
handlePutTicket = algo.ExerciseOption(this.pSymbol, this.pQty); /// underlying and calls will be closed in onOrder() event
this.grossPnL = this.currExrcsPutPnL; /// log the PnL used in runtime decision
} else {
handlePutTicket = algo.MarketOrder(this.pSymbol, -this.pQty); // buy the calls
if (LUD.doDeepTracing) algo.Log(" HC ** HC ** HC ** MARKET ORDER TO SELL " + this.pQty.ToString() + " contracts of " + this.pSymbol + " at the market.");
if (LUD.doDeepTracing) algo.Log("-");
if (handlePutTicket.Status == OrderStatus.Filled)
{
this.pEndPrice = handlePutTicket.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 Collapse {this.uEndPrice.ToString()} below {this.pStrike.ToString()}.";
}
}
}
if(algo.symbolDataBySymbol.ContainsKey(this.uSymbol)){
algo.symbolDataBySymbol[this.uSymbol].isRollable = false;
algo.SymbolsToRemove.Add(this.uSymbol);
}
algo.tprsToClose.Add(this);
return true;
}
}
}
}
#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);
string oloMsg = "";
if (doTracing) Debug($" |()|()|() Found a {olo.oTicket.Status} limit order for {olo.oTicket.Symbol} with limit price and ");
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 Short Call -- initializing a collar
updateTPR.cStartPrice = olo.oTicket.AverageFillPrice;
if (doTracing) Debug(" |()|()|() Found a filled collar initiating short call limit order and updated start price to " + olo.oTicket.AverageFillPrice);
} else {
updateTPR.cEndPrice = olo.oTicket.AverageFillPrice;
if (doTracing) Debug(" |()|()|() Found a filled collar ending short call limit order and updated end price to " + olo.oTicket.AverageFillPrice);
}
}
} else if (olo.oRight == OptionRight.Put) { // never buy ITM Puts -- forces taxable "synthetic sale" of underlying
updateTPR.pEndPrice = 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) {
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 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(" |()|()|() updating " + olo.oTicket.Quantity + " of " + olo.oTicket.Symbol + " limit order to market order");
if (doTracing) Debug("IN MAIN INVESTING");
var callTkt = MarketOrder(olo.oTicket.Symbol, olo.oTicket.Quantity);
if (callTkt.Status == OrderStatus.Filled ){
if (olo.isWingCall) {
updateTPR.wcEndPrice = callTkt.AverageFillPrice;
if (doTracing) Debug(" |()|()|() Converted and filled wing call limit to market order and updated end price to " + callTkt.AverageFillPrice);
} else {
if (olo.oTicket.Quantity < 0){ // this is a collar initiating sell short call order
updateTPR.cStartPrice = callTkt.AverageFillPrice;
if (doTracing) Debug(" |()|()|() Converted and filled short call sell limit to market order and updated end price to " + callTkt.AverageFillPrice);
} else {
updateTPR.cEndPrice = callTkt.AverageFillPrice;
if (doTracing) Debug(" |()|()|() Converted and filled short call sell limit to market order and updated end price to " + callTkt.AverageFillPrice);
}
}
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 MAIN INVESTING");
var sellPutTkt = MarketOrder(olo.oTicket.Symbol, olo.oTicket.Quantity);
if (sellPutTkt.Status == OrderStatus.Filled ){
updateTPR.pEndPrice = sellPutTkt.AverageFillPrice;
if (doTracing) Debug(" |()|()|() Converted and filled long put sell limit to market order and updated end price to " + sellPutTkt.AverageFillPrice);
if (doTracing) Debug(" |()|()|() marking converted olo at " + currentLO + " for removal ");
RemovableLOs.Add(currentLO);
}
} else {
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;
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.
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;
if (orderLimitPrice > orderStrikePrice - orderUnderlyingPrice + 0.50M) {
olo.oTicket.UpdateLimitPrice(lPrice);
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);
}
}
}
}
}