| Overall Statistics |
|
Total Trades 18 Average Win 3.39% Average Loss -1.17% Compounding Annual Return 19.040% Drawdown 15.100% Expectancy -0.025 Net Profit 9.025% Sharpe Ratio 0.676 Probabilistic Sharpe Ratio 37.860% Loss Rate 75% Win Rate 25% Profit-Loss Ratio 2.90 Alpha -0.063 Beta 2.013 Annual Standard Deviation 0.239 Annual Variance 0.057 Information Ratio 0.281 Tracking Error 0.177 Treynor Ratio 0.08 Total Fees $62.75 Estimated Strategy Capacity $48000.00 Lowest Capacity Asset QQQ VRQOXO2FK7L2|QQQ RIWIV7K5Z9LX Portfolio Turnover 1.11% |
# region imports
from AlgorithmImports import *
# endregion
import re
class TestCallSell(QCAlgorithm):
def Initialize(self):
# set start/end date for backtest
self.SetStartDate(2013, 12, 1)
self.SetEndDate(2014, 5, 31)
# set starting balance for backtest
self.SetCash(100000)
# add the underlying asset
self.equity = self.AddEquity("QQQ", Resolution.Daily)
self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
self.symbol = self.equity.Symbol
self.callContract = str()
self.contractsAdded = set()
# parameters ------------------------------------------------------------
self.callDaysB4Exp = 2 # number of days before expiry to exit calls
self.callDTE = 25 # target days till expiration
self.callOTM = 1.00 # target percentage OTM of call
self.percentage = 0.9 # percentage of portfolio for underlying asset
self.options_alloc = 100 # 1 option for X num of shares (balanced would be 100)
# ------------------------------------------------------------------------
def OnData(self, data):
if(self.IsWarmingUp):
return
# buy underlying asset
if not self.Portfolio[self.symbol].Invested:
self.Debug(f"Buying {self.symbol} {self.Time}")
self.SetHoldings(self.symbol, self.percentage)
# buy calls
self.BuyCall(data)
# close call before it expires
if self.callContract:
db4 = self.callContract.ID.Date - self.Time
if self.Time > datetime(2014,3,1) and self.Time < datetime(2014,3,11):
self.Debug(f"Call exp {self.callContract.ID.Date} db4e {db4}")
if (self.callContract.ID.Date - self.Time) <= timedelta(self.callDaysB4Exp):
self.Liquidate(self.callContract)
self.Debug(f"Closed call: too close to expiration {db4} {self.Time.date()}")
self.callContract = str()
def BuyCall(self, data):
# get option data
if self.callContract == str():
self.callContract = self.CallOptionsFilter(data)
return
# if not invested and option data added successfully, buy option
elif not self.Portfolio[self.callContract].Invested and data.ContainsKey(self.callContract):
nc = round(self.Portfolio[self.symbol].Quantity / self.options_alloc)
q = self.Portfolio[self.symbol].Quantity
mr = self.Portfolio.MarginRemaining
mu = self.Portfolio.TotalMarginUsed
dt = self.callContract.ID.Date.date()
self.Log(f"BC Buying calls {self.Time.date()} nc {nc} q {q} mu {mu} X {dt}")
self.Buy(self.callContract, nc)
def CallOptionsFilter(self, data):
''' OptionChainProvider gets a list of option contracts for an underlying symbol at requested date.
Then you can manually filter the contract list returned by GetOptionContractList.
The manual filtering will be limited to the information included in the Symbol
(strike, expiration, type, style) and/or prices from a History call '''
contracts = self.OptionChainProvider.GetOptionContractList(self.symbol, data.Time)
self.underlyingPrice = self.Securities[self.symbol].Price
# filter the out-of-money call options from the contract list which expire close to self.callDTE num of days from now
otm_calls = [i for i in contracts if i.ID.OptionRight == OptionRight.Call and
i.ID.StrikePrice >= self.callOTM * self.underlyingPrice and
self.callDTE - 8 < (i.ID.Date - data.Time).days < self.callDTE + 8]
if len(otm_calls) > 0:
# sort options by closest to self.callDTE days from now and desired strike, and pick first
contract = sorted(sorted(otm_calls, key = lambda x: abs((x.ID.Date - self.Time).days - self.callDTE)),
key = lambda x: self.underlyingPrice - x.ID.StrikePrice,reverse=True)[0]
# self.Debug(f"call selected {contract.ID.Date.date()} str {contract.ID.StrikePrice}")
if contract not in self.contractsAdded:
# self.Debug(f"Buy call {contract.ID.StrikePrice} {self.symbol} {'%.2f'%self.underlyingPrice}")
self.contractsAdded.add(contract)
# use AddOptionContract() to subscribe the data for specified contract
self.AddOptionContract(contract, Resolution.Daily)
return contract
else:
return str()
def OnOrderEvent(self, orderEvent):
if self.Time > datetime(2014,1,1) and self.Time < datetime(2014,5,31):
self.Debug(f" Order {str(orderEvent)}")
if 0 and orderEvent.Status == OrderStatus.Filled:
self.Debug(f"Cash {self.Portfolio.Cash} Holdings {self.Portfolio.TotalHoldingsValue} Val {self.Portfolio.TotalPortfolioValue}")
self.Debug(str(orderEvent))
def OnAssignmentOrderEvent(self, assignmentEvent: OrderEvent) -> None:
self.Debug(f"Assignment event: {str(assignmentEvent)}")
# just set a flag to reset holdings in OnData
self.NeedReset = True
def OnEndOfAlgorithm(self):
self.Debug(f"Final Report")
# self.Liquidate()