| Overall Statistics |
|
Total Trades 540 Average Win 0.69% Average Loss -0.80% Compounding Annual Return 15.679% Drawdown 6.700% Expectancy 0.135 Net Profit 32.207% Sharpe Ratio 0.937 Probabilistic Sharpe Ratio 68.815% Loss Rate 39% Win Rate 61% Profit-Loss Ratio 0.87 Alpha 0.08 Beta 0.212 Annual Standard Deviation 0.08 Annual Variance 0.006 Information Ratio 0.664 Tracking Error 0.148 Treynor Ratio 0.353 Total Fees $2550.80 Estimated Strategy Capacity $850000.00 Lowest Capacity Asset SPY 32C89ACDSYUZQ|SPY R735QTJ8XC9X Portfolio Turnover 0.65% |
#region imports
from AlgorithmImports import *
#endregion
class VirtualYellowGiraffe(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2022, 1, 1)
self.SetEndDate(2023, 12, 1)
self.SetCash(100000)
self.equity = self.AddEquity("SPY", Resolution.Minute)
self.symbol = self.equity.Symbol
self.SetBenchmark(self.equity.Symbol)
self.last_time_invested = self.Time
self.InitOptionsAndGreeks(self.equity)
self.order_list = {}
self.rsi = self.RSI(self.symbol, 2)
self.volatility_target = self.GetParameter("volatility", 0.15)
self.delta_target = self.GetParameter("delta", 0.5)
## Initialize Options settings, chain filters, pricing models, etc
## ====================================================================
def InitOptionsAndGreeks(self, theEquity ):
## 1. Specify the data normalization mode (must be 'Raw' for options)
theEquity.SetDataNormalizationMode(DataNormalizationMode.Raw)
## 2. Set Warmup period of at least 80 days
self.SetWarmup(10, Resolution.Daily)
## 3. Set the security initializer to call SetMarketPrice
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
## 4. Subscribe to the option feed for the symbol
theOptionSubscription = self.AddOption(theEquity.Symbol)
## 5. set the pricing model, to calculate Greeks and volatility
theOptionSubscription.PriceModel = OptionPriceModels.CrankNicolsonFD() # both European & American, automatically
## 6. Set the function to filter out strikes and expiry dates from the option chain
theOptionSubscription.SetFilter(self.OptionsFilterFunction)
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 5), self.Liq)
def OnData(self, data):
## If we're done warming up, and not invested, Sell a put.
if (not self.IsWarmingUp) and (self.Time > self.last_time_invested + timedelta(days = 1)
and self.Time.minute > 44 and self.Time.hour < 12 and self.Portfolio.MarginRemaining > 0.5 * self.Portfolio.TotalPortfolioValue):
if data.Bars.ContainsKey(self.symbol) and self.rsi.IsReady:# and self.rsi.Current.Value < 20:
ticket = self.SellAnOTMPut()
if ticket is not None:
self.last_time_invested = self.Time
self.order_list[ticket.OrderId] = {"id" : ticket.OrderId,"fillPrice" : ticket.AverageFillPrice, "qty": ticket.Quantity,"stopLoss" : 2 * ticket.AverageFillPrice, "takeProfit" : 0.00 * ticket.AverageFillPrice, "symbol" : ticket.Symbol}
else:
return
#self.Log(self.order_list[ticket.OrderId])
for key, value in self.order_list.items():
try:
current_symbol = value['symbol']
current_quantity = value['qty']
current_stop_loss = value['stopLoss']
current_take_profit = value['takeProfit']
current_put_price = data[current_symbol].Close
#self.Debug(f'the stop loss is {current_stop_loss} while put price is {current_put_price}')
#self.Debug(f'the take profit is {current_take_profit} while put price is {current_put_price}')
if current_put_price > current_stop_loss:
orderMessageSL = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \
f"Buy Stop Loss {current_symbol.Value}, price {current_put_price}"
stop_loss_ticket = self.Order(current_symbol.Value, -current_quantity, False, orderMessageSL)
self.Log(f"{self.Time} {orderMessageSL}")
del self.order_list[key]
break
if current_put_price < current_take_profit:
orderMessageTP = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \
f"Buy Take Profit {current_symbol.Value}, price {current_put_price}"
take_profit_ticket = self.Order(current_symbol.Value, -current_quantity, False, orderMessageTP)
self.Log(f"{self.Time} {orderMessageTP}")
del self.order_list[key]
break
except:
continue
# option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
# if option_invested:
# self.Portfolio[option_invested[0]]
# pass
# if self.Time + timedelta(28) > option_invested[0].ID.Date:
# self.Liquidate(option_invested[0], "Too close to expiration")
# return
if self.Portfolio[self.symbol].HoldStock:
self.MarketOrder(self.symbol, -self.Portfolio[self.symbol].Quantity)
if self.Portfolio.MarginRemaining < 0:
pass
## Sell an OTM Put Option.
## Use Delta to select a put contract to sell
## ==================================================================
def SellAnOTMPut(self):
## Sell a 15 delta put expiring in 90 days
putContract = self.SelectContractByDelta(self.equity.Symbol, self.delta_target, 0, OptionRight.Put) #30 delta
if putContract.BidPrice != 0 and putContract.BidPrice > 0.60 and putContract.ImpliedVolatility > self.volatility_target:
self.quantity = max(8,round((self.Portfolio.TotalPortfolioValue * 0.005) / (putContract.BidPrice * 100),0))
## construct an order message -- good for debugging and order records
orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \
f"Sell {putContract.Symbol} "+ \
f"({round(putContract.Greeks.Delta,2)} Delta)" + \
f"({round(putContract.BidPrice,2)} Ask)" + \
f"(Quantity: {self.quantity})"
self.Log(f"{self.Time} {orderMessage}")
ticket_p = self.Order(putContract.Symbol, -self.quantity, False, orderMessage)
return ticket_p
else:
return
## Get an options contract that matches the specified criteria:
## Underlying symbol, delta, days till expiration, Option right (put or call)
## ============================================================================
def SelectContractByDelta(self, symbolArg, strikeDeltaArg, expiryDTE, optionRightArg= OptionRight.Call):
canonicalSymbol = self.AddOption(symbolArg)
theOptionChain = self.CurrentSlice.OptionChains[canonicalSymbol.Symbol]
theExpiryDate = self.Time + timedelta(days=expiryDTE)
## Filter the Call/Put options contracts
filteredContracts = [x for x in theOptionChain if x.Right == optionRightArg]
## Sort the contracts according to their closeness to our desired expiry
contractsSortedByExpiration = sorted(filteredContracts, key=lambda p: abs(p.Expiry - theExpiryDate), reverse=False)
closestExpirationDate = contractsSortedByExpiration[0].Expiry
## Get all contracts for selected expiration
contractsMatchingExpiryDTE = [contract for contract in contractsSortedByExpiration if contract.Expiry == closestExpirationDate]
## Get the contract with the contract with the closest delta
closestContract = min(contractsMatchingExpiryDTE, key=lambda x: abs(abs(x.Greeks.Delta)-strikeDeltaArg))
return closestContract
## The options filter function.
## Filter the options chain so we only have relevant strikes & expiration dates.
## =============================================================================
def OptionsFilterFunction(self, optionsContractsChain):
strikeCount = 35 # no of strikes around underyling price => for universe selection #35
minExpiryDTE = 0 # min num of days to expiration => for uni selection
maxExpiryDTE = 5 # max num of days to expiration => for uni selection
return optionsContractsChain.IncludeWeeklys()\
.Strikes(-strikeCount, -0)\
.Expiration(timedelta(minExpiryDTE), timedelta(maxExpiryDTE))
def Liq(self):
self.Liquidate()from AlgorithmImports import *
from datetime import timedelta
class PutCalendarSpreadStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2022, 12, 29)
self.SetEndDate(2023, 2, 20)
self.SetCash(500000)
self.underlying = self.AddEquity("SPY", Resolution.Minute)
self.options = self.AddOption(self.underlying.Symbol, Resolution.Minute)
self.options.SetFilter(lambda u: u.WeeklysOnly().Strikes(-2,0).Expiration(0,1))
self.symbol = self.options.Symbol
self.action_taken = False
def OnData(self, data):
# avoid extra orders
if self.Portfolio.Invested:
self.holdings = [x.Symbol for x in self.Portfolio.Values if x.Invested]
self.positions_debug = [x for x in self.Portfolio.Values if x.Invested]
for item in self.holdings:
if item.HasUnderlying == False:
self.MarketOrder(self.underlying.Symbol, 100)
self.Debug(f'ho comprato 100 SPY al {self.Time}')
if item.HasUnderlying:
chain_invested_opt = data.OptionChains.get(item.Canonical, None)
if chain_invested_opt:
self.contract = chain_invested_opt.Contracts.get(item)
if self.contract:
delta = self.contract.Greeks.Delta
if delta <= -0.8 and self.action_taken and self.Time.date() == self.last_action_date:
return
self.MarketOrder(self.underlying.Symbol, -100)
self.Debug(f'Delta contratto era {delta} e quindi ho venduto 100 SPY al {self.Time}')
self.action_taken = True
self.last_action_date = self.Time.date()
return
# Get the OptionChain of the self.symbol
chain = data.OptionChains.get(self.options.Symbol, None)
if not chain: return
# get at-the-money strike
atm_strike = sorted(chain, key=lambda x: abs(x.Strike - chain.Underlying.Price))[0].Strike
# filter the put options from the contracts which is ATM in the option chain.
puts = [i for i in chain if i.Strike == atm_strike and i.Right == OptionRight.Put]
if len(puts) == 0: return
# sorted the optionchain by expiration date
expiries = sorted([x.Expiry for x in puts], key = lambda x: x)
# select the farest expiry as far-leg expiry, and the nearest expiry as near-leg expiry
near_expiry = expiries[0]
#far_expiry = expiries[-1]
naked_put = OptionStrategies.NakedPut(self.symbol, atm_strike, near_expiry)
#option_strategy = OptionStrategies.PutCalendarSpread(self.symbol, atm_strike, near_expiry, far_expiry)
# We open a position with 1 unit of the option strategy
self.Buy(naked_put, 1)
# self.Sell(option_strategy, 1) if short put calendar spreadSecurities[holdings[0].Value].Price
def OnEndOfAlgorithm(self):
for symbol, sec in self.Securities.items():
self.Log(f"{symbol} :: {sec.Price}")
def OnEndOfDay(self):
self.action_taken = False