Overall Statistics
Total Orders
91
Average Win
0.09%
Average Loss
-0.02%
Compounding Annual Return
216.910%
Drawdown
2.900%
Expectancy
3.930
Start Equity
2000000
End Equity
2110382.76
Net Profit
5.519%
Sharpe Ratio
3.039
Sortino Ratio
4.445
Probabilistic Sharpe Ratio
63.844%
Loss Rate
24%
Win Rate
76%
Profit-Loss Ratio
5.53
Alpha
0.941
Beta
-0.887
Annual Standard Deviation
0.171
Annual Variance
0.029
Information Ratio
0.131
Tracking Error
0.318
Treynor Ratio
-0.585
Total Fees
$195.91
Estimated Strategy Capacity
$14000.00
Lowest Capacity Asset
QQQ XUERCY466YXY|QQQ RIWIV7K5Z9LX
Portfolio Turnover
26.76%
from AlgorithmImports import *
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

class DeltaHedgingStrategy(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2021, 12, 1)
        self.SetEndDate(2021, 12, 17)
        self.SetCash(2000000)
        
        self.qqq = self.AddEquity("QQQ", Resolution.Hour)
        self.qqq_option = self.AddOption("QQQ", Resolution.Hour)
        self.qqq_option.SetFilter(self.OptionFilterFunc)
        
        self.InitializeStrategyVariables()
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9, 30), self.UpdateHistoricalVolatility)
        self.CalculateInitialHistoricalVolatility()
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16, 0), self.LogDailySummary)
    
    def InitializeStrategyVariables(self):
        ## option position tracking
        self.option_symbol = None
        self.option_strike = 0
        self.is_position_open = False
        self.position_type = None
        self.option_position_quantity = 0
        
        ## hedging parameters
        self.last_delta_hedge_time = datetime.min
        self.hedge_frequency = timedelta(hours=1)
        self.option_expiry = datetime(2021, 12, 17)
        self.start_trading_time = datetime(2021, 12, 1, 10, 0, 0)
        
        ##risk management parameters
        self.max_capital_usage = 0.05
        self.max_margin_usage = 0.70
        self.margin_buffer = 0.30
        
        ## volatility tracking
        self.historical_volatility = 0
        
        # PnL tracking
        self.daily_pnl_start = 0
        self.today_date = None
    
    def OptionFilterFunc(self, universe):
        return (universe
                .Expiration(timedelta(0), self.option_expiry - self.Time)
                .Strikes(-10, 10)
                .CallsOnly())
    
    def CalculateInitialHistoricalVolatility(self):
        hist_start = self.Time - timedelta(days=40)
        hist_end = self.Time - timedelta(days=1)
        history = self.History(self.qqq.Symbol, hist_start, hist_end, Resolution.Daily)
        
        if not history.empty and len(history) >= 25:
            closes = history['close'].values[-25:]
            log_returns = np.diff(np.log(closes))
            self.historical_volatility = np.std(log_returns) * np.sqrt(252)
    
    def UpdateHistoricalVolatility(self):
        end_date = self.Time.date() - timedelta(days=1)
        start_date = end_date - timedelta(days=40)
        
        history = self.History(self.qqq.Symbol, start_date, end_date, Resolution.Daily)
        
        if len(history) >= 25:
            closes = history['close'].values[-25:]
            log_returns = np.diff(np.log(closes))
            self.historical_volatility = np.std(log_returns) * np.sqrt(252)
            
            self.daily_pnl_start = self.Portfolio.TotalPortfolioValue
            self.today_date = self.Time.date()
    
    def SelectATMOption(self, chain):
        underlying_price = chain.Underlying.Price
        
        call_options = [contract for contract in chain if 
                        contract.Right == OptionRight.Call and 
                        contract.Strike >= underlying_price and
                        contract.Expiry.date() == self.option_expiry.date()]
        
        if not call_options:
            return None
            
        call_options.sort(key=lambda x: x.Strike)
        return call_options[0]
    
    def CalculateSafePositionSize(self, option_price):
        underlying_price = self.Securities[self.qqq.Symbol].Price
        estimated_margin_per_contract = (underlying_price * 0.2 + option_price) * 100
        
        available_capital = self.Portfolio.Cash * self.max_capital_usage
        max_contracts_by_capital = int(available_capital / option_price / 100)
        
        available_margin = self.Portfolio.Cash * self.max_margin_usage
        max_contracts_by_margin = int(available_margin / estimated_margin_per_contract)
        
        return max(1, min(max_contracts_by_capital, max_contracts_by_margin) // 2)
    
    def OpenOptionPosition(self, atm_call):
        option_price = (atm_call.BidPrice + atm_call.AskPrice) / 2
        
        self.option_position_quantity = self.CalculateSafePositionSize(option_price)
        self.option_symbol = atm_call.Symbol
        self.option_strike = atm_call.Strike
        
        implied_vol = atm_call.ImpliedVolatility
        
        if implied_vol > self.historical_volatility:
            self.position_type = "short"
            self.Sell(self.option_symbol, self.option_position_quantity)
        else:
            self.position_type = "long"
            self.Buy(self.option_symbol, self.option_position_quantity)
            
        self.is_position_open = True
    
    def CalculateSafeDeltaHedgeSize(self, delta, target_shares):
        available_cash = self.Portfolio.Cash
        underlying_price = self.Securities[self.qqq.Symbol].Price
        hedge_cost = abs(target_shares) * underlying_price
        
        if hedge_cost > available_cash * self.margin_buffer:
            safe_shares = int((available_cash * self.margin_buffer) / underlying_price)
            return safe_shares if target_shares > 0 else -safe_shares
        
        return target_shares
    
    def PerformDeltaHedge(self, option_contract):
        try:
            delta = option_contract.Greeks.Delta
            
            if self.position_type == "long":
                target_shares = -delta * self.option_position_quantity * 100
            else:
                target_shares = -delta * self.option_position_quantity * 100
            
            current_shares = self.Portfolio[self.qqq.Symbol].Quantity
            shares_to_trade = int(target_shares - current_shares)
            
            safe_shares_to_trade = self.CalculateSafeDeltaHedgeSize(delta, shares_to_trade)
            
            if abs(safe_shares_to_trade) > 0:
                if safe_shares_to_trade > 0:
                    self.Buy(self.qqq.Symbol, abs(safe_shares_to_trade))
                else:
                    self.Sell(self.qqq.Symbol, abs(safe_shares_to_trade))
            
            self.last_delta_hedge_time = self.Time
            
        except Exception:
            pass
    
    def CheckMarginStatus(self):
        margin_used = self.Portfolio.TotalMarginUsed
        margin_remaining = self.Portfolio.MarginRemaining
        margin_total = margin_used + margin_remaining if margin_used + margin_remaining > 0 else self.Portfolio.Cash
        
        if margin_remaining < self.margin_buffer * margin_total:
            reduction_factor = 3
            
            if self.position_type == "long":
                qty_to_reduce = self.option_position_quantity // reduction_factor
                if qty_to_reduce > 0:
                    self.Sell(self.option_symbol, qty_to_reduce)
                    self.option_position_quantity -= qty_to_reduce
            else:
                qty_to_reduce = self.option_position_quantity // reduction_factor
                if qty_to_reduce > 0:
                    self.Buy(self.option_symbol, qty_to_reduce)
                    self.option_position_quantity -= qty_to_reduce
    
    def LogDailySummary(self):
        if self.today_date is None:
            return
            
        daily_pnl = self.Portfolio.TotalPortfolioValue - self.daily_pnl_start
        total_pnl = self.Portfolio.TotalProfit
        
        self.Debug(f"Date: {self.today_date} | Daily PnL: ${daily_pnl:,.2f} | Total PnL: ${total_pnl:,.2f}")
        
        self.daily_pnl_start = self.Portfolio.TotalPortfolioValue
    
    def OnData(self, slice):
        if self.Time < self.start_trading_time:
            return
        
        if slice.OptionChains.Count == 0:
            return
        
        chain = list(slice.OptionChains.Values)[0]
        if not chain:
            return
        
        if not self.is_position_open:
            atm_call = self.SelectATMOption(chain)
            if atm_call:
                self.OpenOptionPosition(atm_call)
            return
        
        if (self.Time - self.last_delta_hedge_time) >= self.hedge_frequency:
            option_contract = next((contract for contract in chain 
                              if contract.Symbol == self.option_symbol), None)
            
            if option_contract is None:
                return
                
            self.CheckMarginStatus()
            self.PerformDeltaHedge(option_contract)
    
    def OnEndOfAlgorithm(self):
        self.Debug(f"Final PnL: ${self.Portfolio.TotalProfit:,.2f}")