Overall Statistics |
Total Trades 448 Average Win 0.63% Average Loss -0.53% Compounding Annual Return 13.558% Drawdown 22.700% Expectancy 0.113 Net Profit 12.378% Sharpe Ratio 0.622 Probabilistic Sharpe Ratio 31.881% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 1.18 Alpha 0 Beta 0 Annual Standard Deviation 0.175 Annual Variance 0.031 Information Ratio 0.622 Tracking Error 0.175 Treynor Ratio 0 Total Fees $418.00 Estimated Strategy Capacity $69000.00 Lowest Capacity Asset TSLA 31TOO71Y13QLI|TSLA UNU3P8Y3WFAD |
from System.Drawing import Color from AlgorithmImports import * class Benchmark: def __init__(self, algo, underlying, shares, indicators): self.algo = algo self.underlying = underlying # Variable to hold the last calculated benchmark value self.benchmarkCash = None self.benchmarkShares = shares self.indicators = indicators self.tradingChart = Chart('Trade Plot') # On the Trade Plotter Chart we want 3 series: trades and price: self.tradingChart.AddSeries(Series('Call', SeriesType.Scatter, '$', Color.Blue, ScatterMarkerSymbol.Circle)) self.tradingChart.AddSeries(Series('Put', SeriesType.Scatter, '$', Color.Red, ScatterMarkerSymbol.Triangle)) self.tradingChart.AddSeries(Series('Price', SeriesType.Scatter, '$', Color.Green)) def PrintBenchmark(self): self.__PrintBuyHold() self.__PrintTrades() self.__PrintCash() self.__PrintIndicators() def PrintTrade(self, option, option_type): ''' Prints the price of the option on our trade chart. ''' self.algo.Plot('Trade Plot', 'Call' if option_type == OptionRight.Call else 'Put', option.ID.StrikePrice) def __PrintIndicators(self): ''' Prints the indicators array values to the Trade Plot chart. ''' for indicator in self.indicators: if indicator.IsReady: self.algo.Plot('Trade Plot', indicator.Name, indicator.Current.Value) def __PrintCash(self): ''' Prints the cash in the portfolio in a separate chart. ''' self.algo.Plot('Cash', 'Options gain', self.algo.Portfolio.Cash) def __PrintTrades(self): ''' Prints the underlying price on the trades chart. ''' self.algo.Plot('Trade Plot', 'Price', self.__UnderlyingPrice()) def __PrintBuyHold(self): ''' Simulate buy and hold the shares. We use the same number of shares as the backtest. In this situation is 100 shares + the cash of the portfolio.''' if not self.benchmarkCash: self.benchmarkCash = self.algo.Portfolio.TotalPortfolioValue - self.benchmarkShares * self.__UnderlyingPrice() self.algo.Plot("Strategy Equity", "Buy & Hold", self.benchmarkCash + self.benchmarkShares * self.__UnderlyingPrice()) def __UnderlyingPrice(self): return self.algo.Securities[self.underlying].Close
#region imports from AlgorithmImports import * #endregion from datetime import timedelta class OptionsAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2014, 11, 1) self.SetEndDate(2017, 11, 1) self.SetCash(20000) self.syl = 'IBM' equity = self.AddEquity(self.syl, Resolution.Minute) equity.SetDataNormalizationMode(DataNormalizationMode.Raw) self.macd = self.MACD(self.syl, 12, 26, 9, MovingAverageType.Exponential, Resolution.Daily) self.underlyingsymbol = equity.Symbol # use the underlying equity as the benchmark self.SetBenchmark(equity.Symbol) def OnData(self,slice): if self.macd.IsReady: if self.Portfolio[self.syl].Quantity == 0 and self.macd.Current.Value > self.macd.Signal.Current.Value: self.Buy(self.syl,100) # # <1> if there is a MACD short signal, liquidate the stock # elif self.Portfolio[self.syl].Quantity > 0 and self.macd.Current.Value < self.macd.Signal.Current.Value: # self.Liquidate() # # <2> if today's close < lowest close of last 30 days, liquidate the stock # history = self.History([self.syl], 30, Resolution.Daily).loc[self.syl]['close'] # self.Plot('Stock Plot','stop loss frontier', min(history)) # if self.Portfolio[self.syl].Quantity > 0: # if self.Securities[self.syl].Price < min(history): # self.Liquidate() # <3> if there is a MACD short signal, trade the options elif self.Portfolio[self.syl].Quantity > 0 and self.macd.Current.Value < self.macd.Signal.Current.Value: try: if self.Portfolio[self.syl].Invested and not self.Portfolio[self.contract].Invested \ and self.Time.hour != 0 and self.Time.minute == 1: self.SellCall() except: if self.Portfolio[self.syl].Invested and self.Time.hour != 0 and self.Time.minute == 1: self.SellCall() def BuyPut(self): contracts = self.OptionChainProvider.GetOptionContractList(self.underlyingsymbol, self.Time.date()) if len(contracts) == 0: return filtered_contracts = self.InitialFilter(self.underlyingsymbol, contracts, -3, 3, 0, 30) put = [x for x in filtered_contracts if x.ID.OptionRight == 1] # sorted the contracts according to their expiration dates and choose the ATM options contracts = sorted(sorted(put, key = lambda x: abs(self.Securities[self.syl].Price - x.ID.StrikePrice)), key = lambda x: x.ID.Date, reverse=True) self.contract = contracts[0] self.AddOptionContract(self.contract, Resolution.Minute) self.Buy(self.contract, 1) def SellCall(self): contracts = self.OptionChainProvider.GetOptionContractList(self.underlyingsymbol, self.Time.date()) if len(contracts) == 0: return filtered_contracts = self.InitialFilter(self.underlyingsymbol, contracts, -3, 3, 0, 30) put = [x for x in filtered_contracts if x.ID.OptionRight == 0] # sorted the contracts according to their expiration dates and choose the ATM options contracts = sorted(sorted(put, key = lambda x: abs(self.Securities[self.syl].Price - x.ID.StrikePrice)), key = lambda x: x.ID.Date, reverse=True) self.contract = contracts[0] self.AddOptionContract(self.contract, Resolution.Minute) self.Sell(self.contract, 1) def InitialFilter(self, underlyingsymbol, symbol_list, min_strike_rank, max_strike_rank, min_expiry, max_expiry): ''' This method is an initial filter of option contracts according to the range of strike price and the expiration date ''' if len(symbol_list) == 0 : return # fitler the contracts based on the expiry range contract_list = [i for i in symbol_list if min_expiry < (i.ID.Date.date() - self.Time.date()).days < max_expiry] # find the strike price of ATM option atm_strike = sorted(contract_list, key = lambda x: abs(x.ID.StrikePrice - self.Securities[underlyingsymbol].Price))[0].ID.StrikePrice strike_list = sorted(set([i.ID.StrikePrice for i in contract_list])) # find the index of ATM strike in the sorted strike list atm_strike_rank = strike_list.index(atm_strike) try: min_strike = strike_list[atm_strike_rank + min_strike_rank] max_strike = strike_list[atm_strike_rank + max_strike_rank] except: min_strike = strike_list[0] max_strike = strike_list[-1] filtered_contracts = [i for i in contract_list if i.ID.StrikePrice >= min_strike and i.ID.StrikePrice <= max_strike] return filtered_contracts
# Covered call options Algo. Trying to generate income by selling calls against existing portfolio positions. Some rules: # # - Sell calls against a defined size of the position. This is important for TSLA if you don't want to default to all shares. # - Set a defined expiration date rollover. 2 weeks seems good but when a rollover happens we should consider between 2 weeks - 3 months. # - Always rollover for a credit. This is a must. # - If 2 or more contracts expire on the same day and they are ITM consider selling 1 contract ATM for credit and release some shares. # - When doing a rollover and the contract is ITM always pick a strike above to get closer to the equity price. # - When doing a rollover and the contract is OTM always pick a strike below to get closer to the equity price. # - With a rollover down and out consider adding another contract in order to create the loop above. # - Roll up and out 2 ITM -> 1 ATM contracts | Roll down and out 1 OTM -> 2 ATM contracts !! <- not sure about this # - When rolling the contract do that based on a risk reward algo/function. https://www.thebalance.com/risk-to-reward-ratio-1031350 # NOTES: # - Rolling up and out works best when we are in a down movement (price is down).!!! # - Rolling down and out works best when we are in an up movement (price is up).!!! # - When we have earnings the contracts don't change value that much. Consider leaving them until after?! # - Open the first covered call on an up day. # - Roll if no EXT value # - 7 - 60 DTE range max # - Think of Risk Management. Position sizing. Making sure it does not get out of hand. # TODOs: # Test rolling when: # - 3 more days remaining # - price starts going over strike # - price is down # - there is low ext value # - always for a credit # - small debit to catch price # - 2 calls for 1 sold PUT?! # - ITM and credit 2 - 3 weeks out! roll there! -> 2 calls at one point same expiration for 1 PUT switch. # - a given profit % is hit then consider closing the call and roll at a later date! # IDEA!! I THINK I KNOW WHAT RANDY MEANT when he said switch from 2 to 1 contract. I think he ment 2 ITM calls to 1 SOLD PUT?!?! TEST! # EXT value # For example, if a call option has a strike price of $20, and the underlying stock # is trading at $22, that option has $2 of intrinsic value. The actual option may # trade at $2.50, so the extra $0.50 is extrinsic value. # sprice = $109 // stock price = $109 # oprice = $21.35 // option price = $21.35 # strike = $90 // option strike = $90 # oprice - abs(strike - sprice) // ext value = $2.35 # RESOURCES: # - https://www.quantconnect.com/tutorials/introduction-to-options/quantconnect-options-api from datetime import timedelta from optionsfinder import OptionsFinder from portfoliohandler import PortfolioHandler from benchmark import Benchmark from AlgorithmImports import * class CoveredCallOptionsAlgorithm(QCAlgorithm): # def Initialize(self): is run before back testing/live trading commences. In it we set important variables, modules, add data and warm up indicators and so forth. # We can also use Scheduled Events in Initialize() to trigger code to run at specific times of the day. def Initialize(self): self.SetStartDate(2021, 1, 1) self.SetEndDate(2021, 12, 1) self.SetCash(200000) # We also add data within the Initialize() method. # The general template to do so is very simple. # For equities we use self.AddEquity() which returns a security object that we can assign internally for future use, for example with the TSLA: # Resolution is the time period of a data bar, and the default options for Equities are: # Resolution.Tick # Resolution.Second # Resolution.Minute # Resolution.Hour # Resolution.Daily symbol = self.GetParameter("equity") self.rangeStart = int(self.GetParameter("rangeStart")) self.rangeStop = int(self.GetParameter("rangeStop")) self.minCredit = float(self.GetParameter("minCredit")) equity = self.AddEquity(symbol, Resolution.Daily) self.underlying = equity.Symbol self.sliceData = None # option = self.AddOption(self.underlying, Resolution.Minute) equity.SetDataNormalizationMode(DataNormalizationMode.Raw) # Not sure what this does yet # To fix the missing price history for option contracts https://www.quantconnect.com/forum/discussion/8779/security-doesn-039-t-have-a-bar-of-data-trade-error-options-trading/p1 self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x))) self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage) # not sure what this does # self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x))) # this was added to fix the warmup error. Not sure if needed. # We would also set up indicators that we wanted to use in the main algorithm in Initialize(), such as RSI or MACD. # For example for an RSI indicator, we would set key variables such as the look back period and overbought and oversold levels, # remembering to set them to self so that they are referenceable throughout different methods in the algorithm. # # self.RSI = self.RSI("TSLA", 14) self.ema = self.EMA(self.underlying, 50) self.emaSlow = self.EMA(self.underlying, 100) # Here is an example of a scheduled event that is set firstly to run every day, and then more specifically every 10 minutes within every day- # setting off the method/function self.ExampleFunc, which could be absolutely anything we want it to be: # # self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=10)), self.ExampleFunc) # You can also set a “warm up” period that allows components such as technical indictaors, historical data arrays and so forth to prime or # populate prior to the main algorithm going live. No trades will be executed during the warm up period. # # self.SetWarmUp(35) # Warm up 35 bars for all subscribed data. # Create an instance of OptionsFinder to handle the finding of option contracts self.optionHandler = OptionsFinder(self, self.underlying, self.rangeStart, self.rangeStop, -80, 80) self.benchmark = Benchmark(self, self.underlying, 100, [self.ema, self.emaSlow]) self.portfolioHandler = PortfolioHandler(self) # def OnData(self, data): is activated each time new data is passed to your algorithm, so this can be hourly, daily or weekly etc. # depending on the data resolution you request in Initialization(self). # You might fire trading logic in here, or update indicators or dataframes. # Remember you can also activate functions on regular time intervals/certain events not related to a data update with Scheduled Events. def OnData(self, slice): self.sliceData = slice # self.BuyUnderlying() self.RollForCredit() # self.CashSecuredPuts() # self.CoveredCalls() self.benchmark.PrintBenchmark() # self.portfolioHandler.PrintPortfolio() def OnOrderEvent(self, orderEvent): if orderEvent.IsAssignment: order = self.Transactions.GetOrderById(orderEvent.OrderId) if order.Type == OrderType.OptionExercise: pass # do stuff def CoveredCalls(self): # consider integrating the sold_puts into the strategy and see how it fares. Consider always keeping a ratio of 2x calls to 1x puts so margin is not affected. covered_calls = self.portfolioHandler.UnderlyingSoldOptions(self.underlying, OptionRight.Call) if len(covered_calls) == 0: self.__InitialOptionSell(OptionRight.Call) else: # Do we have existing covered calls? Then we should check and roll them if needed. self.__RollOption(covered_calls, OptionRight.Call) def CashSecuredPuts(self): sold_puts = self.portfolioHandler.UnderlyingSoldOptions(self.underlying, OptionRight.Put) if len(sold_puts) > 0: self.__RollOption(sold_puts, OptionRight.Put) #if len(sold_puts) == 0: # self.__InitialOptionSell(OptionRight.Put) #else: # Do we have existing covered calls? Then we should check and roll them if needed. # self.__RollOption(sold_puts, OptionRight.Put) def BuyUnderlying(self): if not self.Portfolio[self.underlying].Invested: self.MarketOrder(self.underlying, 100) # buy 100 shares of underlying stocks # Sell an option def __InitialOptionSell(self, option_type): # If we added the Option contract data and we have not Invested then we should short the option selected. # if self.Portfolio[self.underlying].Invested: option = self.optionHandler.AddContract(self.Securities[self.underlying].Price, option_type) # Add the option contract (subscribe the contract data) if self.Securities.ContainsKey(option) and not self.Portfolio[option].Invested: self.benchmark.PrintTrade(option, option_type) self.Sell(option, 1) # short the option def RollForCredit(self): covered_calls = self.portfolioHandler.UnderlyingSoldOptions(self.underlying, OptionRight.Call, 10) sold_puts = self.portfolioHandler.UnderlyingSoldOptions(self.underlying, OptionRight.Put, 10) if len(covered_calls) == 0 and len(sold_puts) == 0: if self.portfolioHandler.lastTradeBid > 0: optionContracts = self.optionHandler.ContractsWithCredit(self.portfolioHandler.lastTradeBid) self.__SellOptions(optionContracts) else: self.__InitialOptionSell(OptionRight.Call) else: roll = False soldPut = None soldCall = None # TODO: consider switching this to groupby(contracts, expiry..).. groupedByExpiry = dict() for option in covered_calls: groupedByExpiry.setdefault(int(option.Security.Expiry.timestamp()), []).append(option) for option in sold_puts: groupedByExpiry.setdefault(int(option.Security.Expiry.timestamp()), []).append(option) if len(groupedByExpiry) == 0: return firstExpiry = list(sorted(groupedByExpiry))[0] security = groupedByExpiry[firstExpiry][0].Security expiresIn = self.ExpiresIn(security) # if expiresIn <= 2: # roll = True soldCalls = [x for x in groupedByExpiry[firstExpiry] if x.Security.Right == OptionRight.Call] if len(soldCalls) > 0: soldCall = soldCalls[0] soldPuts = [x for x in groupedByExpiry[firstExpiry] if x.Security.Right == OptionRight.Put] if len(soldPuts) > 0: soldPut = soldPuts[0] optionsBid = sum([x.Security.BidPrice for x in groupedByExpiry[firstExpiry]]) self.portfolioHandler.lastTradeBid = optionsBid if soldCall is None and soldPut is not None: profit = soldPut.UnrealizedProfitPercent * 100 self.Buy(soldPut.Symbol, soldPut.Quantity) elif soldCall is not None and soldPut is None: profit = soldCall.UnrealizedProfitPercent * 100 self.Buy(soldCall.Symbol, soldCall.Quantity) elif soldCall is not None and soldPut is not None: profit = (soldCall.UnrealizedProfitPercent + soldPut.UnrealizedProfitPercent) / 2 * 100 self.Buy(soldCall.Symbol, soldCall.Quantity) self.Buy(soldPut.Symbol, soldPut.Quantity) if profit >= 75: self.__InitialOptionSell(OptionRight.Call) elif expiresIn <= 4: optionContracts = self.optionHandler.ContractsWithCredit(optionsBid) self.__SellOptions(optionContracts) def __SellOptions(self, contracts): # clean contracts and remove None and blanks. contracts = [i for i in contracts if i] if len(contracts) == 0: return for option in contracts: if self.Securities.ContainsKey(option) and not self.Portfolio[option].Invested: self.benchmark.PrintTrade(option, option.ID.OptionRight) self.Sell(option, 1) # sell the new contract # Take the option that is closest to expiration and roll it forward. def __RollOption(self, contracts, option_type = OptionRight.Call): for option in contracts: security = option.Security strike = security.Symbol.ID.StrikePrice stockPrice = self.Securities[option.Symbol.ID.Symbol].Price profit = option.UnrealizedProfitPercent * 100 expiresIn = self.ExpiresIn(security) roll = False # if profit < -100 or ((stockPrice * 1.2 > strike) and profit > -25) or ((stockPrice * 1.1 > strike) and profit > -50): # roll = True #if security.Right == OptionRight.Call: # if stockPrice > strike and profit > -25: # roll = True # # if stockPrice > strike and profit > -50: # roll = True #elif security.Right == OptionRight.Put: # if stockPrice < strike and profit > -25: # roll = True # # if stockPrice < strike and profit > -50: # roll = True if profit < -100: roll = True if profit > 80: roll = True if expiresIn <= 2: roll = True if roll: # TODO: # - 3 more days remaining # - price starts going over strike # - price is down # - there is low ext value # - always for a credit # - small debit to catch price # - 2 calls for 1 sold PUT?! # - ITM and credit 2 - 3 weeks out! roll there! -> 2 calls at one point same expiration for 1 PUT switch. # - a given profit % is hit then consider closing the call and roll at a later date! # optionStrike = self.RollUpPercent(strike, stockPrice, 1.25) if option_type == OptionRight.Call else self.RollDownPercent(strike, stockPrice, 1.25) # optionStrike = self.RollToEMA(stockPrice) optionBid = security.BidPrice # optionContract = self.optionHandler.AddContract(optionStrike, None, option_type) # Add the call option contract (subscribe the contract data) optionContract = self.optionHandler.AddContract(None, optionBid, option_type) # Add the call option contract (subscribe the contract data) if optionContract is None: return # if the above returns NULL then the self.Sell method will sell the option ATM (current strike price) self.Buy(option.Symbol, option.Quantity) if self.Securities.ContainsKey(optionContract) and not self.Portfolio[optionContract].Invested: self.benchmark.PrintTrade(optionContract, option_type) self.Sell(optionContract, 1) # short the new call option # Method that returns a boolean if the security expires in the given days # @param security [Security] the option contract def ExpiresIn(self, security): return (security.Expiry.date() - self.Time.date()).days def RollToEMA(self, stock_price): return self.ema.Current.Value * 1.07 # Roll to middle strike between old strike and current price. def RollUpMiddle(self, strike, stock_price): if strike < stock_price: new_strike = strike + ((stock_price - strike) / 2) else: new_strike = strike / 1.05 return new_strike # Roll to a strike a certain percentage in price up def RollUpPercent(self, strike, stock_price, percent): if strike < stock_price: new_strike = strike * percent else: new_strike = strike / 1.05 return new_strike def RollDownPercent(self, strike, stock_price, percent): if strike < stock_price: new_strike = strike / 1.05 else: new_strike = strike * percent return new_strike # class used to improve readability of the coarse selection function class SelectionData: def __init__(self, period): self.Ema = ExponentialMovingAverage(period) @property def EmaValue(self): return float(self.Ema.Current.Value) def Update(self, time, value): return self.Ema.Update(time, value)
from AlgorithmImports import * from itertools import groupby class OptionsFinder: def __init__(self, algo, underlying, rangeStart, rangeStop, minStrike, maxStrike): self.algo = algo self.underlying = underlying self.rangeStart = rangeStart self.rangeStop = rangeStop self.minStrike = minStrike self.maxStrike = maxStrike # Right now the below lines could be conditional as we don't need them if we are not using # the contractBid method where we look for credit and not really the strike. self.underlyingOption = self.algo.AddOption(self.underlying, Resolution.Daily) self.underlyingOption.SetFilter( lambda u: u.IncludeWeeklys() .Strikes(self.minStrike, self.maxStrike) .Expiration(timedelta(self.rangeStart), timedelta(self.rangeStop)) ) # Just adds the options data. def AddContract(self, strike = None, bid = None, optionType = OptionRight.Call): ''' We are adding the option contract data to the algo ''' # Replace this line below with different methods that filter the contracts in a more special way. if strike is not None: return self.__GetContractStrike(strike, optionType) if bid is not None: return self.__GetContractBid(bid, optionType) def ContractsWithCredit(self, bid): # Check if there is options data for this symbol. # Important that when we do GetValue we use the underlyingOption.Symbol because the # key returned by the equity is `TSLA` and the one from option is `?TSLA` chain = self.algo.sliceData.OptionChains.GetValue(self.underlyingOption.Symbol) if chain is None: return [] callContract = None putContract = None # Don't look at contracts that are closer than the defined rangeStart. contracts = [x for x in chain if self.__ExpiresIn(x) >= self.rangeStart and self.__ExpiresIn(x) <= self.rangeStop] # Limit the Strikes to be between -5% and +5% of the UnderlyingPrice. contracts = [x for x in contracts if x.UnderlyingLastPrice / 1.05 <= x.Strike <= x.UnderlyingLastPrice * 1.05 ] # Make sure we have the contracts sorted by Strike and Expiry contracts = sorted(contracts, key = lambda x: (x.Expiry, x.Strike)) # Iterate over the contracts that are grouped per day. for expiry, group in groupby(contracts, lambda x: x.Expiry): group = list(group) # sort group by type group = sorted(group, key = lambda x: x.Right) if callContract is not None and putContract is not None: return [callContract.Symbol, putContract.Symbol] # Reset the contracts after each expiration day. We want to force the options to Expire on the same date. callContract = None putContract = None for right, rightGroup in groupby(group, lambda x: x.Right): rightGroup = list(rightGroup) # sort the options by credit creditOptions = sorted(rightGroup, key = lambda x: x.BidPrice, reverse = True) if right == OptionRight.Call: # find any contract that has a BidPrice > bid creditCalls = [x for x in creditOptions if x.BidPrice > bid] creditCalls = sorted(creditCalls, key = lambda x: x.BidPrice) # if # we do have a call that can replace the one we are buying for a credit then return that. # else # sort the calls by BidPrice and pick the highest bid contract if len(creditCalls) > 0: return [creditCalls[0].Symbol] else: # TODO: here instead of picking the bigest credit contract (this would be the one with the smallest Strike) # we should consider picking one that is closer to strike as possible. callContract = creditOptions[0] # Only look for PUT contracts if we can't find a call contract with enough credit to replace the bid value. if right == OptionRight.Put and callContract is not None: # find any contract that has a BidPrice > bid - callContract.BidPrice creditPuts = [x for x in creditOptions if x.BidPrice > (bid - callContract.BidPrice)] creditPuts = sorted(creditPuts, key = lambda x: x.BidPrice) if len(creditPuts) > 0: putContract = creditPuts[0] return [] # Method that returns a boolean if the security expires in the given days # @param security [Security] the option contract def __ExpiresIn(self, security): return (security.Expiry.date() - self.algo.Time.date()).days def __GetContractBid(self, bid, optionType): # TODO: this method can be changed to return PUTs and CALLs if the strike selected is not # sensible enough. Like don't allow selection of contracts furthen than 15% of the stock price. # This should force the selection of puts to offset the calls or the other way around until we can get into # a situation where just calls are present. # Check if there is options data for this symbol. # Important that when we do GetValue we use the underlyingOption.Symbol because the # key returned by the equity is `TSLA` and the one from option is `?TSLA` chain = self.algo.sliceData.OptionChains.GetValue(self.underlyingOption.Symbol) if chain is None: return None # Don't look at contracts that are closer than the defined rangeStart. contracts = [x for x in chain if (x.Expiry.date() - self.algo.Time.date()).days > self.rangeStart] contracts = sorted( contracts, key = lambda x: abs(x.UnderlyingLastPrice - x.Strike) ) # Get the contracts that have more credit than the one we are buying. The bid here might be too small # so we should consider sorting by: # - strike > stock price # - closest expiration # - closest strike to stockPrice with credit! SORT THIS BELOW?!? # TODO: right now we have a problem as it keeps going down in strike when it's loosing. Maybe send in the profit and if it's over -100 -200% switch to PUT?! # TODO: consider using a trend indicator like EMA to determine a contract type switch CALL -> PUT etc. contracts = [x for x in contracts if x.BidPrice >= bid * 1.20] # If possible only trade the same contract type. This should have a limit of how low it should go before it switches to a put. # Maybe by doing the limit we can remove the trend indicator. if optionType == OptionRight.Call: typeContracts = [x for x in contracts if x.Right == optionType and (x.UnderlyingLastPrice / 1.05) < x.Strike] if len(typeContracts) > 0: contracts = typeContracts if optionType == OptionRight.Put: typeContracts = [x for x in contracts if x.Right == optionType and (x.UnderlyingLastPrice * 1.05) < x.Strike] if len(typeContracts) > 0: contracts = typeContracts # Grab us the contract nearest expiry contracts = sorted(contracts, key = lambda x: x.Expiry) if len(contracts) == 0: return None return contracts[0].Symbol def __GetContractStrike(self, strike, optionType): filtered_contracts = self.__DateOptionFilter() if len(filtered_contracts) == 0: return str() else: contracts = self.__FindOptionsStrike(filtered_contracts, strike, optionType) if len(contracts): self.algo.AddOptionContract(contracts[0], Resolution.Daily) return contracts[0] else: return str() def __FindOptionsBid(self, contracts, bid, optionType = OptionRight.Call): ''' We are filtering based on bid price. ''' # select only the specified options types contracts = [x for x in contracts if x.ID.OptionRight == optionType] # TODO!! THis does not work. Most probably we have to do self.algo.AddOptionContract on all contracts above and then #. use that to filter by BidPrice as the value does not seem to exist. :( # pick a contract that is above the bid price provided. contracts = [x for x in contracts if x.ID.BidPrice >= bid] contracts = sorted( contracts, key = lambda x: abs(bid - x.ID.BidPrice) ) # prefer shorter expirations contracts = sorted( contracts, key = lambda x: x.ID.Date, reverse=False ) # sorted the contracts according to their expiration dates and choose the ATM options return contracts # Returns options that are ATM. def __FindOptionsStrike(self, contracts, strike, optionType = OptionRight.Call): ''' We are filtering based on strike rank. ''' # select only the specified options types contracts = [x for x in contracts if x.ID.OptionRight == optionType] # never want to go beow strike contracts = [x for x in contracts if x.ID.StrikePrice >= strike] contracts = sorted( contracts, key = lambda x: abs(strike - x.ID.StrikePrice) ) # prefer shorter expirations contracts = sorted( contracts, key = lambda x: x.ID.Date, reverse=False ) # sorted the contracts according to their expiration dates and choose the ATM options return contracts # find the strike price of ATM option # atm_strike = sorted(contracts, # key = lambda x: abs(x.ID.StrikePrice - strike)) # atm_strike = atm_strike[0].ID.StrikePrice # strike_list = sorted(set([i.ID.StrikePrice for i in contracts])) # # find the index of ATM strike in the sorted strike list # atm_strike_rank = strike_list.index(atm_strike) # try: # strikes = strike_list[(atm_strike_rank + self.minStrike):(atm_strike_rank + self.maxStrike)] # except: # strikes = strike_list # filtered_contracts = [i for i in contracts if i.ID.StrikePrice in strikes] # # select only call options # call = [x for x in filtered_contracts if x.ID.OptionRight == optionType] # # sorted the contracts according to their expiration dates and choose the ATM options # return sorted(sorted(call, key = lambda x: abs(strike - x.ID.StrikePrice)), # key = lambda x: x.ID.Date, reverse=True) def __DateOptionFilter(self): ''' We are filtering the options based on the expiration date. It does return weekly contracts as well. ''' contracts = self.algo.OptionChainProvider.GetOptionContractList(self.underlying, self.algo.Time.date()) if len(contracts) == 0 : return [] # fitler the contracts based on the expiry range contract_list = [i for i in contracts if self.rangeStart < (i.ID.Date.date() - self.algo.Time.date()).days < self.rangeStop] return contract_list # Returns options that can be rolled for a credit and higher strike def __CreditUpOptions(self, contracts, existing_contract): # TODO: this! return []
from AlgorithmImports import * # Class that handles portfolio data. We have here any method that would search the portfolio for any of the contracts we need. class PortfolioHandler: def __init__(self, algo): self.algo = algo self.lastTradeBid = 0 # Returns all the covered calls of the specified underlying # @param underlying [String] # @param optionType [OptionRight.Call | OptionRight.Put] # @param maxDays [Integer] number of days in the future that the contracts are filtered by def UnderlyingSoldOptions(self, underlying, optionType, maxDays = 60): contracts = [] for option in self.algo.Portfolio.Values: security = option.Security if (option.Type == SecurityType.Option and str(security.Underlying) == underlying and security.Right == optionType and option.Quantity < 0 and (security.Expiry.date() - self.algo.Time.date()).days < maxDays): contracts.append(option) return contracts def PrintPortfolio(self): # self.Debug("Securities:") # self.Securities # contains Securities that you subscribe to but it does not mean that you are invested. # calling self.AddOptionContract will add the option to self.Securities for kvp in self.Securities: symbol = kvp.Key # key of the array security = kvp.Value # value of the array (these are not attributes) holdings = security.Holdings self.Debug(str(security.Symbol)) # self.Debug(str(security.Underlying)) # self.Debug(str(security.Holdings)) # self.Debug("Portfolio:") # self.Portfolio # contains the Security objects that you are invested in. for kvp in self.Portfolio: symbol = kvp.Key holding = kvp.Value holdings = holding.Quantity # self.Debug(str(holding.Holdings))
#region imports from AlgorithmImports import * #endregion class UnderlyingEma(object): def __init__(self, underlying): self.underlying = underlying # is not used self.tolerance = 1.01 # probably not needed self.fast = ExponentialMovingAverage(100) self.slow = ExponentialMovingAverage(300) self.is_uptrend = False self.scale = 0 def update(self, time, value): if self.fast.Update(time, value) and self.slow.Update(time, value): fast = self.fast.Current.Value slow = self.slow.Current.Value self.is_uptrend = fast > slow * self.tolerance if self.is_uptrend: self.scale = (fast - slow) / ((fast + slow) / 2.0)