| Overall Statistics |
|
Total Trades 28 Average Win 1.03% Average Loss -0.41% Compounding Annual Return 16.673% Drawdown 2.300% Expectancy 0.764 Net Profit 3.859% Sharpe Ratio 2.692 Probabilistic Sharpe Ratio 79.941% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 2.53 Alpha 0.121 Beta 0.227 Annual Standard Deviation 0.06 Annual Variance 0.004 Information Ratio -0.157 Tracking Error 0.14 Treynor Ratio 0.719 Total Fees $136.00 Estimated Strategy Capacity $30000000.00 Lowest Capacity Asset SPY WU82X7CDEKDI|SPY R735QTJ8XC9X |
# class TradeManagement:
# '''Manager for trades for each security'''
# def __init__(self, algorithm, symbol):
# '''
# stoploss = None
# takeProfit = None'''
# def EnterTrade(self, quantity, takeprofit, stoploss):
# '''''
# '''''
# # do this minutely/hourly
# def UpdatePosition(self):
# #current_price > takeprofit
# #liquidate
# # close out
# def timelimits(self):
#https://www.quantconnect.com/forum/discussion/8399/optionstrategies-limit-orders
import numpy as np
class QuantumParticleAtmosphericScrubbers(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2018, 2, 8) # Set Start Date #2019, 12,8
self.SetEndDate(2018,5,8)
self.SetCash(100000) # Set Strategy Cash
self.spy_symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
self.Securities[self.spy_symbol].SetDataNormalizationMode(DataNormalizationMode.Raw)
self.SetSecurityInitializer(self.CustomSecurityInitializer) #TO WARM SHIT UP WHEN ADDED!!
self.straddleTickets = {}
self.max_util = .5
self.lmts = False
self.last_option_exp = None
# credit/max_risk
def OnData(self, data):
if not data.Bars.ContainsKey(self.spy_symbol):
return
## To handle any early assignment (Could loop through univ tickers here.)
#if self.Portfolio[self.spy_symbol].Invested:
# self.Liquidate(self.spy_symbol)
''' Original (Straddle)
if not self.Portfolio.Invested:
price = data.Bars[self.spy_symbol].Close;
expiry = self.Time + timedelta(days = 14)
call_symbol, put_symbol = self.GetStraddleContracts(self.spy_symbol, price, expiry)
#Why made into a call / put here (vs call / put symbol?) ***************
call = self.AddOptionContract(call_symbol, Resolution.Minute)
put = self.AddOptionContract(put_symbol, Resolution.Minute)
call_tickets, put_tickets = self.TradeStraddle(call, put, 2) #(Tgt, Stop) format
self.straddleTickets[self.spy_symbol] = [call_tickets, put_tickets] #[(Tgt, Stp), (Put Tgt, Put Stp)]
#Addit
self.last_option_exp = call_symbol.ID.Date
'''
if not self.Portfolio.Invested:
#HERE is where you would loop (thorugh securities)
price = data.Bars[self.spy_symbol].Close
expiry = self.Time + timedelta(days = 30)
#Just using 10% strikes for IC here -- NEXT UP: greeks
icp = price * 1.10
ocp = price * 1.20
ipp = price * .9
opp = price * .8
#Lookup Option Contracts
o_call, call, put, o_put = self.IronCondor(self.spy_symbol, expiry, icp, ocp, ipp, opp)
# #Add Respective Symbols (To be Traded) -- DO THIS inside Execute
# oc_sym = self.AddOptionContract(o_call, Resolution.Minute)
# c_sym = self.AddOptionContract(call, Resolution.Minute)
if o_call and call and put and o_put:
to_buy = [o_call, o_put]
to_sell = [call, put]
self.ExecuteSpread(to_buy, to_sell, use_limits = self.lmts)
if self.Portfolio.Invested:
self.CloseExpiring(data, False)
'''
SAMPLE (Getting Options data from Securities or Portfolio -- once added/held):
## https://www.quantconnect.com/forum/discussion/3549/getting-option-contract-info-from-portfolio-holding-objects-in-python/p1
## So the OPTION uses the KEY aspect, really KEY.ID !! (CHECK if Option first.)
for kvp in self.Securities:
symbol = kvp.Key
id = symbol.ID
security_type = id.SecurityType
if security_type = SecurityType.Option
expiry = id.Date
strike_price = id.StrikePrice
#ZO Initial Attempt (Cleaned up Below in CloseExpiring)
if self.last_option_exp and self.Portfolio.Invested:
days_to_exp = (self.last_option_exp - self.Time).days
self.Debug(f'DTE -- {days_to_exp}')
if days_to_exp < 1:
self.Debug(f'Closing Prior to Exit.') #Should we check for ITM?
option_posits = [symbol for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested and \
symbol.SecurityType == SecurityType.Option]
for symbol in option_posits:
self.Liquidate(symbol)
'''
def GetICMargin(self, buys, sells):
#calls = [i for i in buys + sells if i.Right == OptionRight.Call]
#puts = [i for i in buys + sells if i.Right == OptionRight.Put]
#self.GetVerticalMargin() #Weird to now sort, etc. Faster to just use abs diff
#MUST BE ADDED (in Securities) PRIOR TO CALLING THIS!
credits = np.sum([self.Securities[symbol].BidPrice for symbol in sells])
debits = np.sum([self.Securities[symbol].AskPrice for symbol in buys]) #[symbol.ID.AskPrice
credit = credits - debits
call_stks = [i.ID.StrikePrice for i in buys + sells if i.ID.OptionRight == OptionRight.Call]
put_stks = [i.ID.StrikePrice for i in buys + sells if i.ID.OptionRight == OptionRight.Put]
self.Debug(f'Calls: {[i for i in call_stks]}')
self.Debug(f'Puts: {[i for i in put_stks]}')
call_dist = abs(max(call_stks) - min(call_stks))
put_dist = abs(max(put_stks) - min(put_stks))
#call_dist = abs(np.diff([i.ID.StrikePrice for i in buys + sells if i.ID.OptionRight == OptionRight.Call]))
#put_dist = abs(np.diff([i.ID.StrikePrice for i in buys + sells if i.ID.OptionRight == OptionRight.Put]))
distance = max(call_dist, put_dist)
self.Debug(f'credit {credit} -- dist {distance}')
return (distance - credit) * 100
#Not going to be used here... Easier to track buys / sells ^^
def GetVerticalMargin(self, buy, sell):
credit = sell.BidPrice - buy.AskPrice #Ex: $1.50
distance = abs(buy.Strike - sell.Strike) #Ex: 5
return (distance - credit) * 100 #100 shares per contract
def ExecuteSpread(self, buys, sells, use_limits = False):
added = [self.AddOptionContract(c) for c in buys + sells]
if buys[0] not in self.Securities: return
#if not set(buys+sells).issubset(self.Securities): return #More accurate, confusing.
#usd_per_ct = self.GetICMargin(buys, sells)
#self.Debug(f'USD per ct -- {usd_per_ct}')
#num_cts = int(self.Portfolio.MarginRemaining / usd_per_ct * self.max_util)
pf = (self.Portfolio.MarginRemaining * self.max_util)
num_cts = 2 #max(num_cts,5)
## ---------------- ADDIT: APPROX of Margin
call_dist = abs(buys[0].ID.StrikePrice - sells[0].ID.StrikePrice)
put_dist = abs(buys[1].ID.StrikePrice - sells[1].ID.StrikePrice)
distance = max(call_dist, put_dist)
credit = abs(self.Securities[sells[0]].BidPrice - self.Securities[buys[0]].AskPrice) + \
abs(self.Securities[sells[1]].BidPrice - self.Securities[buys[1]].AskPrice)
self.Debug(f'Distance - credit * 100 --- {distance} - {credit} * 100 -- > {abs(distance - credit) * 100} ')
if max(call_dist, put_dist) == 0:
#Approximate
ul = self.Securities[sells[0]].Underlying.Price * .2
k = sells[0].ID.StrikePrice
m1 = .2 * ul - abs(k - ul)
usd_per_cts = (m1 - credit) * 100
self.Debug(f'Strangle Calc ---- per_ct: {m1}')
else:
usd_per_ct = (distance - credit) * 100
num_cts = int(pf / usd_per_ct)
## ---------------------- End Addit
self.Debug(f'Num Contracts -- {num_cts}')
for buy in buys:
if use_limits:
ask = self.Securities[buy].AskPrice #Does this need to be buy.Symbol ?
self.LimitOrder(buy, num_cts, ask)
else:
self.MarketOrder(buy, num_cts)
for sell in sells:
if use_limits:
bid = self.Securities[sell].BidPrice
self.LimitOrder(sell, -num_cts, bid)
else:
self.MarketOrder(sell, -num_cts)
def IronCondor(self, symbol, expiry, call, otm_call, put, otm_put):
#Returns oc, ic, ip, op
inner_call, outer_call = self.GetVertical(symbol, OptionRight.Call, call, otm_call, expiry)
inner_put, outer_put = self.GetVertical(symbol, OptionRight.Put, put, otm_put, expiry)
return outer_call, inner_call, inner_put, outer_put
''' Original -- Cleaned this up
if inner_call and outer_call and inner_put and outer_put:
## For Limits ***
#Selling inners (need bid)
# call_price = self.Securities[inner_call].BidPrice
# put_price = self.Securities[inner_put].BidPrice
# #Buying Outers (need ask)
# otm_call_p = self.Securities[outer_call].AskPrice
# otm_put_p = self.Securities[outer_put].AskPrice
self.MarketOrder(outer_call, 1)
self.MarketOrder(inner_call, -1)
self.MarketOrder(inner_put, -1)
self.MarketOrder(outer_put, 1)
'''
def GetVertical(self, symbol, right, inner, outer, expiry):
#Get Contracts (OCP, bc no greeks, iv)
contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
#Get Expiry
expiry_sorted = sorted(contracts, key=lambda k: abs(k.ID.Date - expiry), reverse=False) #Asc
closest = expiry_sorted[0].ID.Date
correct_exp = [symbol for symbol in contracts if symbol.ID.Date == closest]
#Select Side (Call / Put)
rights = [symbol for symbol in correct_exp if symbol.ID.OptionRight == right]
if len(rights) == 0: return None, None
#self.Debug(f'Inner: {inner} ----- Outer: {outer}') #THIS is correct...
#Select Strike
inner_ct = sorted(rights, key=lambda k: abs(k.ID.StrikePrice - inner), reverse=False)[0]
outer_ct = sorted(rights,key=lambda k: abs(k.ID.StrikePrice - outer), reverse=False)[0]
#Not sure if I need to split this? (Maybe Just SORT by strike, and take FIRST, then NEXT?)
#self.Debug(f' (EOVertical) -- Inner: {inner_ct.ID.StrikePrice} ---- Outer: {outer_ct.ID.StrikePrice}')
return inner_ct, outer_ct
def CloseExpiring(self, data, only_itm=False):
## QUESTION -- how to get to OCP type contract from PORTFOLIO, or SECURITIES
#open_pos_exps = [symbol.ID.Date for symbol, val in self.Portfolio.items() \
# if val.Invested and symbol.SecurityType == SecurityType.Option ][0]
#dte = (open_pos_exps - self.Time).days
#ul_price = data.Bars[self.spy_symbol].Close
to_close = []
for kvp in self.Portfolio:
symbol = kvp.Key #OPTION symbol I assume? -- HOW to get Underlying?
value = kvp.Value
if symbol.ID.SecurityType == SecurityType.Option:
#Check if Expiring (If not, Ignore)
expiry = symbol.ID.Date
dte = (expiry - self.Time).days
if dte > 1: continue
#Close ALL expiring
if not only_itm:
self.Liquidate(symbol)
continue
#Close expiring IF ITM
elif only_itm:
strike = symbol.ID.StrikePrice #Can also check ITM here!!
##How to get Underlying Price from OPTION?
ul = symbol.Underlying
ul_price = data.Bars[ul].Close
if symbol.ID.OptionRight == OptionRight.Call:
if strike > ul_price:
self.Liquidate(symbol)
else:
if strike < ul_price:
self.Liquidate(symbol)
def GetStraddleContracts(self, symbol, strike, expiry):
contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
expiry_sorted = sorted(contracts, key=lambda k: abs(k.ID.Date - expiry), reverse=False)
closest_expiry = expiry_sorted[0].ID.Date
contracts_with_desired_expiry = [symbol for symbol in contracts if symbol.ID.Date == closest_expiry]
calls = [symbol for symbol in contracts_with_desired_expiry if symbol.ID.OptionRight == OptionRight.Call]
puts = [symbol for symbol in contracts_with_desired_expiry if symbol not in calls]
sorted_calls = sorted(calls, key=lambda k: abs(k.ID.StrikePrice - strike), reverse=False)
sorted_puts = sorted(puts, key=lambda k: abs(k.ID.StrikePrice - strike), reverse=False)
return sorted_calls[0], sorted_puts[0]
## Interesting use of Stops / Targets (With OnOrder to make them OCO!!) VERY clever...
def TradeStraddle(self, call, put, quantity):
call_entry= call.AskPrice
call_entry_ticket = self.MarketOrder(call.Symbol, quantity)
call_take_profit = call_entry * 1.50
call_stop_loss = call_entry * 0.90
call_profit_ticket = self.LimitOrder(call.Symbol, -quantity, call_take_profit)
call_loss_ticket = self.StopMarketOrder(call.Symbol, -quantity, call_stop_loss)
put_entry = put.AskPrice
put_entry_ticket = self.MarketOrder(put.Symbol, quantity)
put_take_profit = put_entry * 1.50
put_stop_loss = put_entry * 0.90
put_profit_ticket = self.LimitOrder(put.Symbol, -quantity, put_take_profit)
put_loss_ticket = self.StopMarketOrder(put.Symbol, -quantity, put_stop_loss)
return (call_profit_ticket, call_loss_ticket), (put_profit_ticket, put_loss_ticket)
def CustomSecurityInitializer(self, security):
bar = self.GetLastKnownPrice(security)
security.SetMarketPrice(bar)
# OCO Logic (For Exits) --- CAN we get FILL price in here? I suppose it's in PF object anyway, but still.
#Make the Stop / Tgt OCO's basically.
def OnOrderEvent(self, orderevent):
if orderevent.Status != OrderStatus.Filled:
return
#FILLED orderID
orderId = orderevent.OrderId
for underlying, order_pairs in self.straddleTickets.items():
for order_pair in order_pairs:
order_one = order_pair[0]
order_two = order_pair[1]
#IF tgt filled, cancel Stop (Vice Versa)
if orderId == order_one.OrderId:
order_two.Cancel()
elif orderId == order_two.OrderId:
order_one.Cancel()