| Overall Statistics |
|
Total Orders 15 Average Win 0.12% Average Loss -2.00% Compounding Annual Return -5.347% Drawdown 8.700% Expectancy -1 Start Equity 100000 End Equity 91628 Net Profit -8.372% Sharpe Ratio -1.447 Sortino Ratio -0.9 Probabilistic Sharpe Ratio 0.001% Loss Rate 100% Win Rate 0% Profit-Loss Ratio 0.06 Alpha -0.015 Beta -0.104 Annual Standard Deviation 0.027 Annual Variance 0.001 Information Ratio -1.919 Tracking Error 0.143 Treynor Ratio 0.38 Total Fees $11.00 Estimated Strategy Capacity $77000.00 Lowest Capacity Asset BGZ XVD806C6S8KM|BGZ U7EC123NWZTX Portfolio Turnover 0.04% |
# region imports
from AlgorithmImports import *
from datetime import timedelta
# endregion
class LETFShorter(QCAlgorithm):
def initialize(self):
#Set up initialization parameters
self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
self.set_start_date(int(self.get_parameter('Start Year')), int(self.get_parameter('Start Month')), int(self.get_parameter('Start Day')))
self.set_end_date(int(self.get_parameter('End Year')), int(self.get_parameter('End Month')), int(self.get_parameter('End Day')))
self.StartingBalance = int(self.get_parameter('Starting Balance'))
self.set_cash(self.StartingBalance)
self.set_warm_up(timedelta(1))
#Set up the benchmark equity for comparison
self.BMequity = self.add_equity(self.get_parameter('Benchmark Equity'), Resolution.MINUTE)
self.set_benchmark(self.BMequity.symbol)
self.BM_priceHistory = []
self.schedule.on(self.date_rules.every_day(), self.time_rules.before_market_close(self.BMequity.symbol, 0), self.plotBM)
#Add the LETFs
self.longLETF = self.add_equity(self.get_parameter('Long 3X LETF'), Resolution.MINUTE)
self.longLETF.set_data_normalization_mode(DataNormalizationMode.Raw)
self.shortLETF = self.add_equity(self.get_parameter('Short 3X LETF'), Resolution.MINUTE)
self.shortLETF.set_data_normalization_mode(DataNormalizationMode.Raw)
#self.add_option(self.get_parameter('Long 3X LETF'))
#self.add_option(self.get_parameter('Short 3X LETF'))
self.longLETF_Ratio = 0
self.shortLETF_Ratio = 0
self.longLETF_CALL = None
self.longLETF_PUT = None
self.shortLETF_CALL = None
self.shortLETF_PUT = None
self.options_Exp = None
#Add reserve equity
self.reserveEquity = self.add_equity(self.get_parameter("Reserve Equity"), Resolution.MINUTE)
def on_data(self, data: Slice):
#default
if self.is_warming_up: return
#liquidate options if they are too close to expiration
if not self.options_Exp == None:
if (self.options_Exp - self.time) <= timedelta(int(self.get_parameter('Min Expiration DTE'))):
self.LiquidateAllOptions()
#self.log('Closed options positions: too close to expiration.')
#liquidate options if a split is impending
if data.Splits.contains_key(str(self.longLETF.symbol)):
if data.Splits[self.longLETF.symbol].Type == SplitType.Warning:
self.LiquidateAllOptions()
self.log('Closed options positions: impending stock split in ' + str(self.longLETF.symbol) + '.')
elif data.Splits.contains_key(str(self.shortLETF.symbol)):
if data.Splits[self.shortLETF.symbol].Type == SplitType.Warning:
self.LiquidateAllOptions()
self.log('Closed options positions: impending stock split in ' + str(self.shortLETF.symbol) + '.')
if not self.portfolio.invested:
#self.set_holdings(self.reserveEquity.symbol, float(self.get_parameter("Reserve Allocation")))
self.BuyContracts(data)
#Calculate synthetic option position ratios
self.longLETF_Ratio = (int(self.get_parameter('Long Weighting')) * self.longLETF.price) / ((self.longLETF.price + self.shortLETF.price) * (int(self.get_parameter('Long Weighting')) + int(self.get_parameter('Short Weighting'))))
self.shortLETF_Ratio = (int(self.get_parameter('Short Weighting')) * self.shortLETF.price) / ((self.longLETF.price + self.shortLETF.price) * (int(self.get_parameter('Long Weighting')) + int(self.get_parameter('Short Weighting'))))
def debugPlot(self):
return
def plotBM(self):
#Plot benchmark on strategy equity graph and plot alpha on alpha graph
self.BM_priceHistory.append(self.History(self.BMequity.symbol, 2, Resolution.DAILY)['close'].unstack(level= 0).dropna()[self.BMequity.symbol].iloc[-1])
self.Plot('Strategy Equity', self.BMequity.symbol, self.BM_priceHistory[-1] / self.BM_priceHistory[0] * self.StartingBalance)
self.Plot('Alpha', 'Alpha', self.portfolio.total_portfolio_value / self.StartingBalance - self.BM_priceHistory[-1] / self.BM_priceHistory[0])
def BuyContracts(self, data):
if (self.options_Exp == None):
self.GetOptions(data)
return
elif not (self.Portfolio[self.longLETF_CALL].invested and \
self.Portfolio[self.longLETF_PUT].invested and \
self.Portfolio[self.shortLETF_CALL].invested and \
self.Portfolio[self.shortLETF_PUT].invested):
if data.contains_key(self.longLETF_CALL) and \
data.contains_key(self.longLETF_PUT) and \
data.contains_key(self.shortLETF_CALL) and \
data.contains_key(self.shortLETF_PUT):
self.buy(self.longLETF_PUT, 1)
self.sell(self.longLETF_CALL, 1)
self.buy(self.shortLETF_PUT, 1)
self.sell(self.shortLETF_CALL, 1)
def GetOptions(self, data):
#get prices of the underlying
longLETF_underlyingPrice = self.Securities[self.longLETF.symbol].price
shortLETF_underlyingPrice = self.Securities[self.shortLETF.symbol].price
#get the list of contracts for each underlying
longLETF_contracts = self.option_chain_provider.get_option_contract_list(self.longLETF.symbol, data.time)
shortLETF_contracts = self.option_chain_provider.get_option_contract_list(self.shortLETF.symbol, data.time)
#first filter the options for calls with an OTM strike greater than the parameter setting and at least the parameter setting for opening DTE
longLETF_calls = [i for i in longLETF_contracts if i.ID.OptionRight == OptionRight.CALL and \
i.ID.StrikePrice - longLETF_underlyingPrice > (1 + float(self.get_parameter('OTM Percentage'))) * longLETF_underlyingPrice and \
int(self.get_parameter('Open Expiration DTE')) < (i.ID.Date - data.time).days]
shortLETF_calls = [i for i in shortLETF_contracts if i.ID.OptionRight == OptionRight.CALL and \
i.ID.StrikePrice - shortLETF_underlyingPrice > (1 + float(self.get_parameter('OTM Percentage'))) * shortLETF_underlyingPrice and \
int(self.get_parameter('Open Expiration DTE')) < (i.ID.Date - data.time).days]
#temporarily store old expiration value
old_exp = self.options_Exp
self.options_Exp = None
#now we need to find a common expiration date between both option chains for both underlyings
if len(longLETF_calls) > 0 and len(shortLETF_calls) > 0:
for i in sorted(longLETF_calls, key = lambda x: abs((x.ID.Date - self.Time).days)):
for z in sorted(shortLETF_calls, key = lambda x: abs((x.ID.Date - self.Time).days)):
if i.ID.Date == z.ID.Date:
self.options_Exp = i.ID.Date
break
else:
continue
break
if not self.options_Exp == None:
#now we sort the calls to find the appropriate call to sell for each underlying, using the contract date and then sort again based on the strike price
longLETF_call_contract = sorted(sorted(longLETF_calls, key = lambda x: abs((x.ID.Date - self.Time).days - (self.options_Exp - self.Time).days)), key = lambda x: x.ID.StrikePrice - longLETF_underlyingPrice)[0]
shortLETF_call_contract = sorted(sorted(shortLETF_calls, key = lambda x: abs((x.ID.Date - self.Time).days - (self.options_Exp - self.Time).days)), key = lambda x: x.ID.StrikePrice - shortLETF_underlyingPrice)[0]
#now that we have the calls, we have to get puts with matching strikes and expirations. This is easier because we did most of the work to select the call.
longLETF_puts = [i for i in longLETF_contracts if i.ID.OptionRight == OptionRight.PUT and i.ID.StrikePrice == longLETF_call_contract.ID.StrikePrice and i.ID.Date == self.options_Exp]
shortLETF_puts = [i for i in shortLETF_contracts if i.ID.OptionRight == OptionRight.PUT and i.ID.StrikePrice == shortLETF_call_contract.ID.StrikePrice and i.ID.Date == self.options_Exp]
if len(longLETF_puts) > 0 and len(shortLETF_puts) > 0:
self.longLETF_CALL = longLETF_call_contract
self.add_option_contract(longLETF_call_contract, Resolution.MINUTE)
self.longLETF_PUT = longLETF_puts[0]
self.add_option_contract(longLETF_puts[0], Resolution.MINUTE)
self.shortLETF_CALL = shortLETF_call_contract
self.add_option_contract(shortLETF_call_contract, Resolution.MINUTE)
self.shortLETF_PUT = shortLETF_puts[0]
self.add_option_contract(shortLETF_puts[0], Resolution.MINUTE)
else:
self.options_Exp = old_exp
def LiquidateAllOptions(self):
if not self.longLETF_CALL == None:
self.remove_option_contract(self.longLETF_CALL)
if not self.longLETF_PUT == None:
self.remove_option_contract(self.longLETF_PUT)
if not self.shortLETF_CALL == None:
self.remove_option_contract(self.shortLETF_CALL)
if not self.shortLETF_PUT == None:
self.remove_option_contract(self.shortLETF_PUT)
self.longLETF_CALL = None
self.longLETF_PUT = None
self.shortLETF_CALL = None
self.shortLETF_PUT = None
self.options_Exp = None
def OnOrderEvent(self, orderEvent):
self.log(str(orderEvent))
order = self.Transactions.GetOrderById(orderEvent.OrderId)
if order.Type == OrderType.OptionExercise:
self.Liquidate(orderEvent.Symbol.Underlying)
self.LiquidateAllOptions()