| Overall Statistics |
|
Total Trades 44 Average Win 0.20% Average Loss -0.09% Compounding Annual Return -1.514% Drawdown 1.900% Expectancy 0.546 Net Profit -0.122% Sharpe Ratio -0.152 Probabilistic Sharpe Ratio 36.064% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 2.09 Alpha 0.419 Beta -0.62 Annual Standard Deviation 0.067 Annual Variance 0.004 Information Ratio -6.453 Tracking Error 0.109 Treynor Ratio 0.016 Total Fees $44.00 Estimated Strategy Capacity $24000000.00 Lowest Capacity Asset FB 30I1FG1ACLNRA|FB V6OIPNZEM8V9 |
from scipy.stats import norm
from datetime import timedelta
class IronCondorAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2017, 2, 1)
self.SetEndDate(2017, 3, 1)
self.SetCash(100000)
self.SetSecurityInitializer(self.CustomSecurityInitializer)
# Add equities
self.AddUniverse(self.Universe.DollarVolume.Top(5))
[self.AddEquity(symbol, Resolution.Minute) for symbol in ["GOOG", "SPY", "VCR", "SLY", "EEM", "XLP", "ARKK", "XLY", "AAPL"]]
self.activeSymbols = []
# use the underlying equity GOOG as the benchmark
self.SetBenchmark("SPY")
self.SetWarmUp(1)
# Set trading intervals
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("SPY", 1), self.TradeOptions)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(12, 0), self.TradeOptions)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose("SPY", 10), self.TradeOptions)
def CustomSecurityInitializer(self, security):
security.SetDataNormalizationMode(DataNormalizationMode.Raw)
security.SetMarketPrice(self.GetLastKnownPrice(security))
def OnSecuritiesChanged(self, changes):
for change in changes.AddedSecurities:
if change.Type == SecurityType.Equity:
change.SetLeverage(1)
self.activeSymbols.append(change.Symbol)
for change in changes.RemovedSecurities:
if change.Symbol in self.activeSymbols:
self.activeSymbols.remove(change.Symbol)
def OnOrderEvent(self, orderEvent):
order = self.Transactions.GetOrderById(orderEvent.OrderId)
if order.Type == OrderType.OptionExercise:
self.Liquidate(orderEvent.Symbol.Underlying)
def TradeOptions(self):
if self.Portfolio.Invested:
self.AdjustOptions()
if not self.Portfolio.Invested and self.Time.hour != 0 and self.Time.minute != 0:
self.AddToPortfolio()
def CloseCondor(self, x):
#CCC = Close Condor Called
self.Debug("CCC")
for symbol in x:
self.Liquidate(symbol.Symbol)
def AdjustOptions(self):
if self.Portfolio.Invested:
for symbol, x in self.condor_list.items():
days_to_expiry = abs(x[0].Expiry - self.Time).days
# if this condor expires in 25+ days, leave it condor
if days_to_expiry > 25:
continue
elif days_to_expiry < 3.75:
self.CloseCondor(x)
# OTM Check
otm = True
for c in x:
if c.Right == 1 and self.Securities[symbol].Price - c.StrikePrice < 0:
otm = False
elif c.Right == 0 and self.Securities[symbol].Price - c.StrikePrice > 0:
otm = False
# If Condor is in the head region, close
if days_to_expiry <= 25 and otm:
self.CloseCondor(x)
def AddToPortfolio(self):
self.condor_list = {}
for symbol in self.activeSymbols:
contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
# option universe filter
contracts = self.InitialFilter(symbol, contracts, -15, 15, 35, 50)
if not contracts: continue
# sorted the optionchain by expiration date and choose the furthest date
expiry = sorted(contracts, key = lambda x: x.ID.Date, reverse=True)[0].ID.Date
# get puts
puts = [contract for contract in contracts if contract.ID.Date == expiry \
and contract.ID.OptionRight == OptionRight.Put \
and contract.ID.StrikePrice < self.Securities[symbol].Price]
if len(puts) <= 2: continue
put_contracts = sorted(puts, key = lambda x: x.ID.StrikePrice)
# get calls
calls = [contract for contract in contracts if contract.ID.Date == expiry \
and contract.ID.OptionRight == OptionRight.Call \
and contract.ID.StrikePrice > self.Securities[symbol].Price]
if len(calls) <= 2: continue
call_contracts = sorted(calls, key = lambda x: x.ID.StrikePrice)
# get and subscribe to the option contracts
otm_put_lower = self.AddOptionContract(put_contracts[0], Resolution.Minute)
otm_put = self.AddOptionContract(put_contracts[-1], Resolution.Minute)
otm_call = self.AddOptionContract(call_contracts[0], Resolution.Minute)
otm_call_higher = self.AddOptionContract(call_contracts[-1], Resolution.Minute)
self.condor_list[symbol] = [otm_call, otm_call_higher, otm_put, otm_put_lower]
# get the margin requirement
totalPrice = (otm_call_higher.AskPrice + otm_put_lower.AskPrice + otm_put.BidPrice + otm_call.BidPrice) * 100
margin = self.Portfolio.GetMarginRemaining(symbol, OrderDirection.Buy)
if margin > totalPrice * 4:
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)
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
based on the range of strike price and the expiration date
https://www.quantconnect.com/tutorials/applied-options/iron-condor'''
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 + 1]
max_strike = strike_list[atm_strike_rank + max_strike_rank - 1]
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