| Overall Statistics |
|
Total Orders 8 Average Win 0% Average Loss -2.59% Compounding Annual Return 3.212% Drawdown 18.900% Expectancy -0.75 Start Equity 10000000 End Equity 10321158.6 Net Profit 3.212% Sharpe Ratio 0.143 Sortino Ratio 0.193 Probabilistic Sharpe Ratio 17.324% Loss Rate 75% Win Rate 25% Profit-Loss Ratio 0 Alpha 0.103 Beta 1.33 Annual Standard Deviation 0.291 Annual Variance 0.084 Information Ratio 0.388 Tracking Error 0.226 Treynor Ratio 0.031 Total Fees $1089.40 Estimated Strategy Capacity $0 Lowest Capacity Asset TSLA UNU3P8Y3WFAD Portfolio Turnover 0.32% |
from AlgorithmImports import *
from datetime import timedelta
class MultiAssetStraddle(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2018, 1, 1)
self.SetEndDate(2018, 12, 31)
self.SetCash(10_000_000)
self.tickers = ["AAPL", "MSFT", "GOOG", "AMZN", "META", "TSLA", "NVDA"]
self.atr = {}
self.macd = {}
self.rsi = {}
self.option_symbol = {}
self.entryCost = {}
self.entryExpiry = {}
self.entrySpot = {}
self.lastEntryTime = {}
self.atrMultiplier = 1.5
self.takeProfit = 0.4
self.stopLoss = -0.05
self.cooldown_days = 30
self.max_trade_value = 500_000
self.SetWarmup(200)
for ticker in self.tickers:
equity = self.AddEquity(ticker, Resolution.Daily).Symbol
option = self.AddOption(ticker, Resolution.Daily)
option.SetFilter(-1, 1, timedelta(30), timedelta(45))
self.option_symbol[ticker] = option.Symbol
self.atr[ticker] = self.ATR(equity, 14, Resolution.Daily)
self.macd[ticker] = self.MACD(equity, 12, 26, 9, MovingAverageType.Wilders, Resolution.Daily, Field.Close)
self.rsi[ticker] = self.RSI(equity, 14, MovingAverageType.Wilders, Resolution.Daily)
def OnData(self, slice: Slice):
if self.IsWarmingUp:
return
for ticker in self.tickers:
if self.HasOpenStraddle(ticker):
self._exit_checks(ticker)
continue
if self.lastEntryTime.get(ticker) and (self.Time - self.lastEntryTime[ticker]).days < self.cooldown_days:
continue
if self._atr_breakout(ticker) and self._macd_cross(ticker) and self._rsi_neutral(ticker):
self._enter_straddle(ticker, slice)
def _atr_breakout(self, ticker):
if not self.atr[ticker].IsReady:
return False
price = self.Securities[ticker].Close
low = self.Securities[ticker].Low
atr_val = self.atr[ticker].Current.Value
return (price - low) > self.atrMultiplier * atr_val
def _macd_cross(self, ticker):
return self.macd[ticker].IsReady and self.macd[ticker].Current.Value > self.macd[ticker].Signal.Current.Value
def _rsi_neutral(self, ticker):
return self.rsi[ticker].IsReady and 35 < self.rsi[ticker].Current.Value < 65
def _enter_straddle(self, ticker, slice):
chain = slice.OptionChains.get(self.option_symbol[ticker])
if not chain:
return
contracts = [c for c in chain if c.Expiry > self.Time]
if not contracts:
return
expiry = sorted({c.Expiry for c in contracts})[0]
spot = self.Securities[ticker].Price
atm_strike = min(contracts, key=lambda c: abs(c.Strike - spot)).Strike
calls = [c for c in contracts if c.Strike == atm_strike and c.Right == OptionRight.Call and c.AskPrice > 0 and c.Volume > 0 and c.Expiry == expiry]
puts = [c for c in contracts if c.Strike == atm_strike and c.Right == OptionRight.Put and c.AskPrice > 0 and c.Volume > 0 and c.Expiry == expiry]
if not calls or not puts:
return
call = sorted(calls, key=lambda c: (-c.Volume, c.AskPrice))[0]
put = sorted(puts, key=lambda c: (-c.Volume, c.AskPrice))[0]
mult = self.Securities[call.Symbol].SymbolProperties.ContractMultiplier
price = (call.AskPrice + put.AskPrice) * mult
if price == 0:
return
qty = int(self.max_trade_value / price)
totalCost = qty * price
if qty < 1 or totalCost > self.Portfolio.Cash * 0.9:
return
self.MarketOrder(call.Symbol, qty)
self.MarketOrder(put.Symbol, qty)
self.entryCost[ticker] = totalCost
self.entryExpiry[ticker] = expiry
self.entrySpot[ticker] = spot
self.lastEntryTime[ticker] = self.Time
self.Debug(f"Entered straddle: {ticker} | Spot: {spot:.2f}, Strike: {atm_strike}, Qty: {qty}")
def _exit_checks(self, ticker):
positions = [h for h in self.Portfolio.Values if h.Invested and h.Symbol.SecurityType == SecurityType.Option and h.Symbol.Underlying.Value == ticker]
if not positions:
return
unreal = sum(h.UnrealizedProfit for h in positions)
cost = self.entryCost.get(ticker, 1)
pl_pct = unreal / cost
days_to_expiry = (self.entryExpiry[ticker].date() - self.Time.date()).days
if pl_pct >= self.takeProfit or pl_pct <= self.stopLoss or days_to_expiry <= 1:
self.Debug(f"Exiting {ticker} | PnL: {pl_pct:.2%}, DTE: {days_to_expiry}")
self.Liquidate(ticker)
def HasOpenStraddle(self, ticker):
return any(
h.Invested and
h.Symbol.SecurityType == SecurityType.Option and
h.Symbol.Underlying.Value == ticker
for h in self.Portfolio.Values
)