Overall Statistics
Total Trades
60
Average Win
0.35%
Average Loss
-0.65%
Compounding Annual Return
0.774%
Drawdown
1.400%
Expectancy
0.019
Net Profit
0.325%
Sharpe Ratio
-2.927
Sortino Ratio
-1.852
Probabilistic Sharpe Ratio
29.947%
Loss Rate
33%
Win Rate
67%
Profit-Loss Ratio
0.53
Alpha
-0.05
Beta
0.038
Annual Standard Deviation
0.017
Annual Variance
0
Information Ratio
-0.675
Tracking Error
0.097
Treynor Ratio
-1.303
Total Fees
$142.60
Estimated Strategy Capacity
$1300000.00
Lowest Capacity Asset
SPY 32CO0FSIZGKCM|SPY R735QTJ8XC9X
Portfolio Turnover
0.20%
# region imports
from AlgorithmImports import *
# endregion

class VariancePremium(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2023, 7, 1)
        self.SetEndDate(2023, 12, 1)
        self.SetCash(100000)
        self.spy = self.AddEquity("SPY", Resolution.Minute)
        self.option = self.AddOption("SPY", Resolution.Minute)
        self.option_symbol = self.option.Symbol
        self.option.SetFilter(-2, 2, 0, 1)
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.At(10, 0), self.SellStraddle)
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.Every(TimeSpan.FromMinutes(30)), self.DeltaHedge)
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.At(15, 55), self.LiquidatePositions)

    # Determine straddle order quantity
    def OrderSizing(self):
        spy_price = self.Securities["SPY"].Price
        size = int(self.Portfolio.Cash / (spy_price * 50))
        return size

    # Place short straddle order
    def SellStraddle(self):
        if not self.Portfolio.Invested:
            if self.CurrentSlice.OptionChains.ContainsKey(self.option_symbol):
                chain = self.CurrentSlice.OptionChains[self.option_symbol]
                contracts_expiring_today = [i for i in chain if i.Expiry.date() == self.Time.date()]
                if len(contracts_expiring_today) == 0:
                    contracts_expiring_today = [i for i in chain if i.Expiry.date() == self.Time.date() + timedelta(days=1)]
                if len(contracts_expiring_today) == 0:
                    return
                spy_price = self.Securities["SPY"].Price
                contract = min(contracts_expiring_today, key=lambda x: abs(x.Strike - spy_price))
                straddle = OptionStrategies.Straddle(self.option_symbol, contract.Strike, contract.Expiry)
                order_size = self.OrderSizing()
                self.Sell(straddle, order_size)
                self.Debug(f"Sold straddle: strike = {contract.Strike}, expiry = {contract.Expiry}, quantity = {order_size}")

    # Dynamically hedge straddle by adjusting position in the underlying
    def DeltaHedge(self):
        if not self.Portfolio.Invested:
            return
        total_delta = 0
        for holding in self.Portfolio.Values:
            if holding.Symbol.SecurityType == SecurityType.Option:
                if self.CurrentSlice.OptionChains.ContainsKey(holding.Symbol.Underlying):
                    option_contract = self.CurrentSlice.OptionChains[holding.Symbol.Underlying].Contracts[holding.Symbol]
                    total_delta += holding.Quantity * option_contract.Greeks.Delta
        spy_holding = self.Portfolio["SPY"]
        spy_quantity = spy_holding.Quantity
        target_spy_quantity = -total_delta
        if spy_quantity != target_spy_quantity:
            self.MarketOrder("SPY", target_spy_quantity - spy_quantity)
            self.Debug("Adjusted Underlying Hedge Position")

    # Liquidate all positions
    def LiquidatePositions(self):
        self.Liquidate()
        self.Debug("Portfolio Liquidated")