Overall Statistics |
Total Trades 5 Average Win 0.00% Average Loss -0.03% Compounding Annual Return 134.780% Drawdown 0.600% Expectancy -0.471 Net Profit 0.312% Sharpe Ratio 9.165 Loss Rate 50% Win Rate 50% Profit-Loss Ratio 0.06 Alpha 0 Beta 0 Annual Standard Deviation 0.029 Annual Variance 0.001 Information Ratio 0 Tracking Error 0 Treynor Ratio 0 Total Fees $6.00 |
from datetime import datetime,timedelta import pandas as pd import numpy as np import sys import math from QuantConnect.Algorithm.Framework.Risk import TrailingStopRiskManagementModel from Execution.StandardDeviationExecutionModel import StandardDeviationExecutionModel from QuantConnect.Securities.Option import OptionPriceModels from datetime import timedelta import decimal as d from my_calendar import last_trading_day ''' implements selling a 1 month call above price and using it to fund a put below price ''' class MomentumAlgorithm(QCAlgorithm): def Initialize(self): self.VERBOSEMODE = True self.VERBOSEMODE2 = False self.SetStartDate(2019,5,10) self.SetEndDate(2019,5,12) self.SetCash(100000) self.benchmark = "SPY" self.SetRiskManagement(TrailingStopRiskManagementModel(0.0172)) self.symbols = ["SPY","TLT","GLD"] self.lastSymbol = None self.MAX_EXPIRY = 30 # max num of days to expiration => for uni selection self.MIN_EXPIRY = 3 # max num of days to expiration => for uni selection self._no_K = 6 # no of strikes around ATM => for uni selection self.resol = Resolution.Minute # Resolution.Minute .Hour .Daily self.previous_delta, self.delta_threshhold = d.Decimal(0.0), d.Decimal(0.05) self.Lev = d.Decimal(0.5) self._assignedOption = False # add in globally accessible dictionaries that are keyed on symbol self.allOptions = {} self.allEquities = {} self.allEquitySymbols= {} self.allOptionSymbols = {} self.putSymbols = {} self.callSymbols = {} self.orderCallIds= {} self.orderPutIds= {} self.last_call_days = {} self.last_put_days = {} self.Deltas= {} self.atr15={} self.atrMultiplier = float(20) # factor by which to multiply average range to determine what is a "significant" move up or down self.stopPrices = {} self.lastStandards= {} self.protectedPositions = {} self.slice = None for s in self.symbols: #instantiate the equities and bind raw data to each needed for options populating the allEquities dictionary self.allEquities[s] = self.AddEquity(s,Resolution.Minute) self.allEquitySymbols[s] = self.allEquities[s].Symbol self.allEquities[s].SetDataNormalizationMode(DataNormalizationMode.Raw) # instantiate the options into a dictionary self.allOptions[s] = self.AddOption(s,Resolution.Minute) # get the options symbol as a handle into a dictionary self.allOptionSymbols[s] = self.allOptions[s].Symbol # set our strike/expiry filter for this option chain in list self.allOptions[s].SetFilter(self.UniverseFunc) # option.SetFilter(-2, +2, timedelta(0), timedelta(30)) # for greeks and pricer (needs some warmup) - https://github.com/QuantConnect/Lean/blob/21cd972e99f70f007ce689bdaeeafe3cb4ea9c77/Common/Securities/Option/OptionPriceModels.cs#L81 self.allOptions[s].PriceModel = OptionPriceModels.CrankNicolsonFD() # both European & American, automatically self.callSymbols[s] = None self.putSymbols[s] = None self.Deltas[s] = None self.last_call_days[s] = None self.last_put_days[s] = None self.stopPrices[s] = None self.lastStandards[s] = None self.orderCallIds[s] = None self.orderPutIds[s] = None self.protectedPositions[s] = False self.atr15[s] = self.ATR(s, 15, MovingAverageType.Simple,Resolution.Minute) self.Securities[s].FeeModel = ConstantFeeModel(2.00) # need to parse security from options in Keys to run history requests or it will bomb dict = self.parseKeysToTypes(self.Securities.Keys) self.optionKeys = dict["options"] self.securityKeys = dict["securities"] # this is needed for Greeks calcs self.SetWarmUp(TimeSpan.FromDays(15)) # add in execution model that only buys when price has dropped by 2 standard deviations #self.SetExecution(StandardDeviationExecutionModel(10, 2, Resolution.Daily)) #schedule functions to run at repeated intervals #self.Schedule.On(self.DateRules.WeekStart("SPY"), self.TimeRules.AfterMarketOpen("SPY", 10), self.Rebalance) self.Schedule.On(self.DateRules.EveryDay("SPY"),self.TimeRules.AfterMarketOpen("SPY", 10), self.Rebalance) def Rebalance(self): #self.EmitInsights(Insight.Price(self.allEquitySymbols["SPY"], timedelta(1), InsightDirection.Up)) self.SetHoldings(self.allEquitySymbols["SPY"],self.Lev) # need to get the options contracts 1 time self.get_contracts(self.slice) self.get_greeks(self.slice) self.tradeOptions(self.allEquitySymbols["SPY"]) return def tradeOptions(self,symbol): breakout = False # make sure call and put options have already been defined for c in self.callSymbols: if c is None: breakout = True for c in self.putSymbols: if c is None: breakout = True if breakout: self.Log("unable to setup options. Values have not yet been assigned") return symbolStr = symbol.Value if (self.callSymbols[symbolStr] is None) or (self.putSymbols[symbolStr] is None) : self.Log("cannot trade options as none are defined yet") return qnty = self.calcOptionContracts(symbol) contract = self.callSymbols[symbolStr].Symbol if not self.Portfolio[contract].Invested: self.Log("buying {} in tradeOptions " . format(contract)) self.orderCallIds[symbolStr] = self.Sell(contract, qnty) contract = self.putSymbols[symbolStr].Symbol if not self.Portfolio[contract].Invested: self.Log("buying {} in tradeOptions " . format(contract)) self.orderPutIds[symbolStr] = self.Buy(contract, qnty) def calcOptionContracts(self,symbol): symbolStr = symbol.Value quantity = float(self.Portfolio[symbolStr].Quantity) optionContractCount = math.ceil(quantity / 100) if self.VERBOSEMODE: self.Log("contract count for symbol {} quantity {} is {} " . format(symbolStr,quantity, optionContractCount)) return optionContractCount def closeOpenOptionContracts(self,symbol): symbolStr = symbol.Value if self.Securities.ContainsKey(self.callSymbols[symbolStr] ) and self.Portfolio[self.callSymbols[symbolStr] ].Invested: self.Log("closing {} in closeOptionsContracts " . format(self.callSymbols[symbolStr])) self.Liquidate(self.callSymbols[symbolStr] ) def OnData(self, slice): self.slice = slice return def get_contracts(self, slice): """ Get ATM call and put """ for kvp in slice.OptionChains: for optionSymbol in self.allOptionSymbols: if not kvp.Key.Contains(optionSymbol): continue chain = kvp.Value # option contracts for each 'subscribed' symbol/key spot_price = chain.Underlying.Price # self.Log("spot_price {}" .format(spot_price)) # prefer to do in steps, rather than a nested sorted # 1. get furthest expiry contracts_by_T = sorted(chain, key = lambda x: x.Expiry, reverse = True) if not contracts_by_T: return self.expiry = contracts_by_T[0].Expiry.date() # furthest expiry self.last_call_days[optionSymbol] = last_trading_day(self.expiry) self.early_expiry = contracts_by_T[-1].Expiry.date() # earliest date from dates retrieved self.last_put_days[optionSymbol] = last_trading_day(self.early_expiry) # get contracts with further expiry and sort them by strike slice_T = [i for i in chain if i.Expiry.date() == self.expiry] sorted_contracts = sorted(slice_T, key = lambda x: x.Strike, reverse = False) # self.Log("Expiry used: {} and shortest {}" .format(self.expiry, contracts_by_T[0].Expiry.date()) ) # 2a. get the ATM closest CALL to short calls = [i for i in sorted_contracts \ if i.Right == OptionRight.Call and i.Strike >= spot_price] self.callSymbols[optionSymbol] = calls[0] if calls else None # self.Log("delta call {}, self.call type {}" .format(self.call.Greeks.Delta, type(self.call))) # self.Log("implied vol {} " .format(self.call.ImpliedVolatility)) # get contracts with further expiry and sort them by strike slice_T = [i for i in chain if i.Expiry.date() == self.early_expiry] sorted_contracts = sorted(slice_T, key = lambda x: x.Strike, reverse = False) # self.Log("Expiry used: {} and shortest {}" .format(self.early_expiry, contracts_by_T[-1].Expiry.date()) ) # 2b. get the ATM closest put to short puts = [i for i in sorted_contracts \ if i.Right == OptionRight.Put and i.Strike <= spot_price] self.putSymbols[optionSymbol] = puts[-1] if puts else None def get_greeks(self, slice): """ Get greeks for invested option: self.call and self.put """ breakout = False for c in self.callSymbols: if c is None: breakout = True for c in self.putSymbols: if c is None: breakout = True if breakout: return for kvp in slice.OptionChains: for optionSymbol in self.allOptionSymbols: symbolStr = optionSymbol if not kvp.Key.Contains(optionSymbol): continue chain = kvp.Value # option contracts for each 'subscribed' symbol/key traded_contracts = filter(lambda x: x.Symbol == self.callSymbols[symbolStr].Symbol or x.Symbol == self.putSymbols[symbolStr].Symbol, chain) if not traded_contracts: self.Log("No traded cointracts"); return deltas = [i.Greeks.Delta for i in traded_contracts] # self.Log("Delta: {}" .format(deltas)) self.Deltas[symbolStr]=sum(deltas) # self.Log("Vega: " + str([i.Greeks.Vega for i in contracts])) # self.Log("Gamma: " + str([i.Greeks.Gamma for i in contracts])) def UniverseFunc(self, universe): return universe.IncludeWeeklys()\ .Strikes(-self._no_K, self._no_K)\ .Expiration(timedelta(self.MIN_EXPIRY), timedelta(self.MAX_EXPIRY)) # ---------------------------------------------------------------------- # Other ancillary fncts # ---------------------------------------------------------------------- def OnOrderEvent(self, orderEvent): self.Log("Order Event -> {}" .format(orderEvent)) order = self.Transactions.GetOrderById(orderEvent.OrderId) self.Log("{0} - {1}:TEST: {2}".format(self.Time, order.Type, orderEvent)) pass def OnAssignmentOrderEvent(self, assignmentEvent): self.Log("we got assigned" + str(assignmentEvent)) self._assignedOption = True # if self.isMarketOpen(self.equity_symbol): # self.Liquidate(self.equity_symbol) def TimeIs(self, day, hour, minute): return self.Time.day == day and self.Time.hour == hour and self.Time.minute == minute def HourMinuteIs(self, hour, minute): return self.Time.hour == hour and self.Time.minute == minute def parseKeysToTypes(self,keys): ''' # options must be fetched from history with difference mechanism see in research option_history = qb.GetOptionHistory(spy_option.Symbol, datetime(2017, 1, 11, 10, 10), datetime(2017, 1, 13, 12, 10)) #print(dir(option_history.GetAllData())) this helper function allows you to separate out those types into sepparate arrays, stored in a dictionary. call with d = self.parseKeysToTypes(self.Securities.Keys) you can then send following history call which will now work instead of bombing. but will not give you history of options optionKeys = d["options"] securityKeys = d["securities"] h1 = self.History(securityKeys, 20, Resolution.Minute) ''' options = [] securities = [] forex = [] futures = [] cfds = [] cryptos = [] unknowns = [] for el in keys: if el.SecurityType == 1: securities.append(el) elif el.SecurityType == 2: options.append(el) elif el.SecurityType == 4: forex.append(el) elif el.SecurityType == 5: futures.append(el) elif el.SecurityType == 6: cfds.append(el) elif el.SecurityType == 7: cryptos.append(el) else: unknowns.append(el) dict = {"securities":securities, "options":options,"forex":forex,"futures":futures,"cfds":cfds,"cryptos":cryptos,"unknowns":unknowns} return dict
# ------------------------------------------------------------------------------ # Business days # ------------------------------------------------------------------------------ from datetime import timedelta #, date from pandas.tseries.holiday import (AbstractHolidayCalendar, # inherit from this to create your calendar Holiday, nearest_workday, # to custom some holidays # USMartinLutherKingJr, # already defined holidays USPresidentsDay, # " " " " " " GoodFriday, USMemorialDay, # " " " " " " USLaborDay, USThanksgivingDay # " " " " " " ) class USTradingCalendar(AbstractHolidayCalendar): rules = [ Holiday('NewYearsDay', month=1, day=1, observance=nearest_workday), USMartinLutherKingJr, USPresidentsDay, GoodFriday, USMemorialDay, Holiday('USIndependenceDay', month=7, day=4, observance=nearest_workday), USLaborDay, USThanksgivingDay, Holiday('Christmas', month=12, day=25, observance=nearest_workday) ] # TODO: to be tested def last_trading_day(expiry): # American options cease trading on the third Friday, at the close of business # - Weekly options expire the same day as their last trading day, which will usually be a Friday (PM-settled), [or Mondays? & Wednesdays?] # # SPX cash index options (and other cash index options) expire on the Saturday following the third Friday of the expiration month. # However, the last trading day is the Thursday before that third Friday. Settlement price Friday morning opening (AM-settled). # http://www.daytradingbias.com/?p=84847 dd = expiry # option.ID.Date.date() # if expiry on a Saturday (standard options), then last trading day is 1d earlier if dd.weekday() == 5: dd -= timedelta(days=1) # dd -= 1 * BDay() # check that Friday is not an holiday (e.g. Good Friday) and loop back while USTradingCalendar().holidays(dd, dd).tolist(): # if list empty (dd is not an holiday) -> False dd -= timedelta(days=1) return dd