Overall Statistics |
Total Orders 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Start Equity 100000 End Equity 100000 Net Profit 0% Sharpe Ratio 0 Sortino Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -0.773 Tracking Error 0.094 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset Portfolio Turnover 0% |
from AlgorithmImports import * class EnergeticRedAnt(QCAlgorithm): def Initialize(self): # Setup self.SetStartDate(2024, 1, 1) self.SetEndDate(2024, 1, 15) self.SetCash(100_000) # Data self.symbol = self.AddIndex("SPX", Resolution.Hour).Symbol option = self.AddIndexOption(self.symbol, "SPXW", resolution=Resolution.Hour) self.symbol_option = option.Symbol option.SetFilter(minStrike=-10, maxStrike=10, minExpiry=timedelta(days=0), maxExpiry=timedelta(days=1)) # Init self.expected_moves = RollingWindow[float](5) self.actual_moves = RollingWindow[float](5) self.long_straddle = None self.short_straddle = None def OnData(self, slice: Slice) -> None: # Check data if not slice.ContainsKey(self.symbol): return if slice.OptionChains: self.Debug(f"time 2 {self.Time}") for chain in slice.OptionChains.Values: atmCall, atmPut = self.FindATMOption(chain) if atmCall is not None and atmPut is not None: # Get straddle price straddle_open_price = slice[atmPut.Symbol].Open + slice[atmCall.Symbol].Open # Calculate straddle-implied expected move and store expected_move = 0.85 * straddle_open_price self.expected_moves.Add(expected_move) self.Log(f"expected_move: {expected_move}") else: return # Calculate actual move from SPX prices and store actual_move = abs(slice[self.symbol].Close - slice[self.symbol].Open) self.actual_moves.Add(actual_move) self.Log(f"actual_move {actual_move}") # Calculate recent average expected and actual moves self.Log(f"expected_moves.IsReady {self.expected_moves.IsReady} actual_moves {self.actual_moves.IsReady}") if self.expected_moves.IsReady and self.actual_moves.IsReady: ave_expected_move = sum(list(self.expected_moves)) / len(list(self.expected_moves)) # self.expected_moves.Count maybe faster ave_actual_move = sum(list(self.actual_moves)) / len(list(self.actual_moves)) self.Log(f"Average expected move, average actual move: ave_expected_move {ave_expected_move} ave_actual_move {ave_actual_move}") # Check if we already have an invested position invested = any([self.Portfolio[option.Symbol].Invested for option in [atmCall, atmPut]]) self.Debug(f"Invested {invested}") # Trading logic if not invested: self.Debug(f"ave_actual_move {ave_actual_move} ave_expected_move {ave_expected_move}") if ave_actual_move > ave_expected_move: short_straddle = OptionStrategies.ShortStraddle(self.symbol_option, atmCall.Strike, atmCall.Expiry) self.Buy(short_straddle, 1) else: long_straddle = OptionStrategies.Straddle(self.symbol_option, atmPut.Strike, atmPut.Expiry) self.Buy(long_straddle, 1) return def FindATMOption(self, optionChain): underlyingPrice = self.Securities[self.symbol].Price minCallDistance = float('inf') minPutDistance = float('inf') atmCallOption = None atmPutOption = None # Iterate through the option chain to find the closest call and put options for optionContract in optionChain: distance = abs(optionContract.Strike - underlyingPrice) # Update the closest call option if optionContract.Right == OptionRight.Call and distance < minCallDistance: atmCallOption = optionContract minCallDistance = distance # Update the closest put option elif optionContract.Right == OptionRight.Put and distance < minPutDistance: atmPutOption = optionContract minPutDistance = distance return atmCallOption, atmPutOption