| 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)