| Overall Statistics |
|
Total Trades 6 Average Win 22.55% Average Loss -7.71% Compounding Annual Return 235.347% Drawdown 8.100% Expectancy 0.962 Net Profit 26.800% Sharpe Ratio 7.496 Probabilistic Sharpe Ratio 96.319% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 2.92 Alpha 0.801 Beta 1.867 Annual Standard Deviation 0.232 Annual Variance 0.054 Information Ratio 7.403 Tracking Error 0.167 Treynor Ratio 0.93 Total Fees $150.00 Estimated Strategy Capacity $600000.00 Lowest Capacity Asset AAPL 30IZW3B3L6QH2|AAPL R735QTJ8XC9X |
''' Original
def TradeOptions(self,slice):
# If there is undelying assets in portfolio at expiration, liquidate the stocks in order to roll into new contracts
if self.Portfolio["GOOG"].Quantity != 0:
self.Liquidate()
if not self.Portfolio.Invested and self.Time.hour != 0 and self.Time.minute != 0:
for i in slice.OptionChains:
chain = i.Value
contract_list = [x for x in chain]
# if there is no optionchain or no contracts in this optionchain, pass the instance
if (slice.OptionChains.Count == 0) or (len(contract_list) == 0):
return
# sorted the optionchain by expiration date and choose the furthest date
expiry = sorted(chain,key = lambda x: x.Expiry)[-1].Expiry
# filter the call and put options from the contracts
call = [i for i in chain if i.Expiry == expiry and i.Right == 0]
put = [i for i in chain if i.Expiry == expiry and i.Right == 1]
# sorted the contracts according to their strike prices
call_contracts = sorted(call,key = lambda x: x.Strike)
put_contracts = sorted(put,key = lambda x: x.Strike)
if len(call_contracts) == 0 or len(put_contracts) == 0 : continue
otm_put_lower = put_contracts[0]
otm_put = put_contracts[10]
otm_call = call_contracts[-10]
otm_call_higher = call_contracts[-1]
self.trade_contracts = [otm_call.Symbol,otm_call_higher.Symbol,otm_put.Symbol,otm_put_lower.Symbol]
# if there is no securities in portfolio, trade the options
self.Buy(otm_put_lower.Symbol ,1)
self.Sell(otm_put.Symbol ,1)
self.Sell(otm_call.Symbol ,1)
self.Buy(otm_call_higher.Symbol ,1)
''' from datetime import timedelta
import QuantConnect as qc
'''
Questions
Portfolio vs Securities usage of Chain / Contract?
(Heirarchy + Return)
Delta (not seemingly accurate?) -- Pricing Model needed?
Margin Issue?
Extras:
Managing Limits Incrementally with OnOrder event?
'''
class IronCondorAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2017, 2, 1)
self.SetEndDate(2017, 4, 15)
self.SetCash(100000) #CHANGE THIS BACK
self.tickers = ['AAPL']
for i in self.tickers:
equity = self.AddEquity(i, Resolution.Minute)
option = self.AddOption(i, Resolution.Minute)
# some of the OptionPriceModels are unreliable
option.PriceModel = qc.Securities.Option.OptionPriceModels.CrankNicolsonFD()
option.PriceModel.EnableGreekApproximation = True
#THESE only work because it's ONE symbol
self.symbol = option.Symbol # identifier for the OptionChain for AAPL not any specific contract
self.ul = equity.Symbol
option.SetFilter(self.UniverseFunc)
self.SetBenchmark(equity.Symbol)
self.stk_dist = 5 #CHANGED FROM 10
self.max_delta = .5 #Not used bc not accurate ...? (Need pricingmodel maybe?)
self.tgt_dte = 40
self.use_limits = False
self.pf_util = .5
self.cts = 100 ## IF DEFINED: Override ^^ Calc w Margin
# symbols = self.OptionChainProvider.GetOptionContractList(underlying_symbol, self.Time)
# symbols[0].Underlying
def OnData(self,slice):
'''
#How to check for equity holdings / Clear them
for kvp in self.Securities: #-- ORIGINALLY this was SECURITIES
symbol = kvp.Key
id = symbol.ID
security_type = id.SecurityType
if security_type != SecurityType.Option:
if self.Portfolio[symbol].Invested:
self.Liquidate()
'''
#Simple version of clearing any Assigned.
if self.Portfolio[self.ul].Quantity != 0:
self.Liquidate()
#If holding options
if self.Portfolio.Invested:
self.ExitExpiring() #Check for Expiring options.
return
# no positions, every minute
short_put, long_put = self.GetBullPut(slice, self.max_delta, self.stk_dist, self.tgt_dte)
if not short_put or not long_put: return
usd_per_spread = self.GetMarginReq(short_put, long_put)
if usd_per_spread == 0:
usd_per_spread = self.stk_distance
num_cts = self.Portfolio.MarginRemaining * self.pf_util / usd_per_spread
self.Debug(f'Number Contracts -- {num_cts}')
self.Debug(f'PF$ {self.Portfolio.MarginRemaining} vs {num_cts * usd_per_spread}')
if self.cts:
num_cts = self.cts
self.ExecuteOptions(short_put, long_put, num_cts, use_lmts = self.use_limits)
#Close Expiring? (Drag in from my example in other script)
def GetMarginReq(self, sell, buy):
## Accepts FULL OptionChains (from SetFilter)
#for i in zip(sell, buy): ## For IC?
credit = sell.BidPrice - buy.AskPrice
distance = sell.Strike - buy.Strike
self.Debug(f'Credit: {credit}, Distance: {distance}')
return (distance - credit) * 100
''' Alternate Lookup -- if easier to look up one opt at a time
def GetOption(self, slice, right, dte, stk=None, delta=None):
if isinstance(dte, int):
tgt_expiry = self.Time + timedelta(dte)
else:
tgt_expiry = dte
for i in slice.OptionChains:
symbol = i.Key
chain = i.Value
if len(x for x in chain) == 0: return
rights = [i for i in chain if i.Right == right]
#Expiry
sort_by_exp = sorted(rights, key=lambda x: abs(x.Expiry - tgt_expiry), reverse=False) #ASC
#if not sort_by_exp[0]: return
exp = sort_by_exp[0].Expiry
exp_rts = [i for i in rights if i.Expiry == exp]
if len(exp_rts) == 0: return
if stk:
return sorted(exp_rts, key=lambda x: abs(x.Strike - stk), reverse=False)[0]
if delta:
return sorted(exp_rts, key=lambda x: abs(x.Greeks.Delta - delta), reverse=False)[0]
'''
## Easier to write a 'findOption' function, and find EACH component of the spread?
def GetBullPut(self, slice, max_delta, stk_dist, dte):
inner, outer = None, None
#
# Slice.OptionChains most recent option chains data, most recent minute
#
aapl_option_chain = slice.OptionChains[self.symbol] # pick out aapl option
# if you know the symbol of a contract, you can use this
# self.AddOptionContract
for i in slice.OptionChains:
symbol = i.Key
chain = i.Value
if len([x for x in chain]) == 0:
return
#Expiry
tgt_expiry = self.Time + timedelta(dte)
dist_to_expiry = sorted(chain, key=lambda x: abs(tgt_expiry - x.Expiry), reverse=False) #ASCENDING
if len(dist_to_expiry) == 0: return
expiry = dist_to_expiry[0].Expiry
exp_puts = [i for i in chain if i.Right == OptionRight.Put and i.Expiry == expiry]
#clusters = [i for i in exp_puts if i.Strike % 5 == 0] # HOW TO GET EVEN NUMS
## Ignore Greeks Initially -- being wierd?
#Greeks
filter_delta = [i for i in exp_puts if abs(i.Greeks.Delta) < max_delta]
if len(filter_delta) < 2:
self.Debug(f'Not enough options meet delta filter.')
return
#Get HIGHEST abs delta here
sort_by_delta = sorted(exp_puts, key=lambda x: abs(x.Greeks.Delta), reverse=True) #DESC
inner = sort_by_delta[0]
self.Debug(f'Inner -- {inner.Strike} d: {inner.Greeks.Delta}')
#USING SOLELY STK BASED CALC
ul = chain.Underlying.Price
sort_by_ul_dist = sorted(exp_puts, key=lambda x: abs(x.Strike - ul), reverse=False) #ASC
#How to get 5 multiples... filter by them early (if x.Strike % 5 == 0) ?
inner = sort_by_ul_dist[0]
tgt_outer_stk = inner.Strike - stk_dist #HARD CODED TEMPORARILY
self.Debug(f'Tgt Lower Stk {tgt_outer_stk}')
outer = sorted(exp_puts, key=lambda x: abs(tgt_outer_stk - x.Strike), reverse = False)[0] #ASC
self.Debug(f'Lower -- {outer.Strike}')
return inner, outer
#def GetRiskRewardBullPut(self, slice, stk_dist, )
#How to even search this one? Use Margin + Credit to FIND them
def ExecuteOptions(self, sell, buy, num_cts=1, use_lmts = False):
if use_lmts:
ask = buy.AskPrice
bid = sell.BidPrice
## THINK this needs symbol BC this is the FULL Security (vs the Symbol object)
#Do I really need Symbol here? ----------------------------------- TRY W + W/OUT
self.LimitOrder(buy.Symbol, num_cts, ask)
self.LimitOrder(sell.Symbol, -num_cts, bid)
#Could manage these limits with OnOrderEvent ...(Decrease price slowly until filled... if pending)
else:
self.MarketOrder(buy.Symbol, num_cts)
self.MarketOrder(sell.Symbol, -num_cts)
self.Debug(f'Order Submitted: -{sell.Strike} / +{buy.Strike}')
#This is always showing DTE of 1...?
def ExitExpiring(self, only_itm=False):
#invested = [kvp for kvp in self.Portfolio if self.Portfolio[kvp.Key].Invested] #?
# expiries = [x.Key.ID.Date for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
# self.Debug(f'expiries -- {expiries}')
# self.Debug(f'Diffs -- {[(i - self.Time) for i in expiries]}')
# self.Debug(f'DTEs -- {[(i-self.Time).days for i in expiries]}')
# return
# collection of dictionaries, 1 dictionary for slice.TradeBars (symbol, tradebars), 1 dictionary for
# OptionChains (symbol, OptionChain)
# for i in myDict.items()
# for kvp in self.CurrentSlice.OptionChains:
# chain_symbol = kvp.Key
# chain_contracts = kvp.Value
# # AAPL chain, QQQ Chain,
# # loop through specific contracts of that chain
# for contract in chain_contracts:
# list_of_symbol_objects = self.OptionChainProvider.GetOptionContractList()
# Symbol.cs
# Symbol.ID - SecurityIdentifier.cs
# SecurityIdentifier.cs contains basic information about security
#Shold this be self.Securities?
# iterating through every security, including otions, futures, equities
# Portfolio <keys=Symbol.cs, value=PortfolioHolding.cs>, element for every security invested and not invested,
# that has a data subscription
for kvp in self.Portfolio:
symbol = kvp.Key
holding = kvp.Value
id = symbol.ID
if id.SecurityType == SecurityType.Option and self.Portfolio[symbol].Invested: #kvp.Value.Invested
expiry = symbol.ID.Date #WHY is this always one?
dte = (expiry - self.Time).days
self.Debug(f'DTE -- {dte}')
if dte > 1: continue
stk = symbol.ID.StrikePrice
if only_itm:
ul = symbol.Underlying
ul_price = self.Portfolio[ul].Price
if symbol.ID.OptionRight == OptionRight.Call:
if ul_price > stk:
self.Debug(f'Exitting ITM Call')
self.Liquidate(symbol)
else:
if ul_price < stk:
self.Debug(f'Exitting ITM Put')
self.Liquidate(symbol)
else:
self.Liquidate(symbol)
self.Debug(f'Exitting Expiring Symbol')
def OnOrderEvent(self, orderEvent):
self.Log(str(orderEvent))
def UniverseFunc(self, universe):
return universe.IncludeWeeklys().Strikes(-99, 99).Expiration(timedelta(0), timedelta(self.tgt_dte + 10))