Created with Highcharts 12.1.2EquityJan 2Jan 9Jan 16Jan 23Jan 30Feb 6Feb 13Feb 20Feb 27Mar 6Mar 13Mar 20Mar 27Apr 31,000,000
Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
1000000
End Equity
1000000
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
-1.692
Tracking Error
0.141
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
from AlgorithmImports import *

class OilFuturesDeltaNeutralStrategy(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2023, 1, 1)
        self.SetEndDate(2023, 4, 1)
        self.SetCash(1000000)

        # Add Crude Oil Futures
        self.future = self.AddFuture(Futures.Energies.CrudeOilWTI, Resolution.Minute)
        self.future.SetFilter(timedelta(80), timedelta(100))  # Slightly relaxed expiry filter

        # Add Future Options
        self.AddFutureOption(self.future.Symbol, self.OptionFilter)

        # Track price history for delta estimation
        self.prev_futures_prices = []
        self.prev_option_prices = []

    def OptionFilter(self, universe):
        return universe.Strikes(-10, 10) \
                       .Expiration(timedelta(80), timedelta(100)) \
                       .OnlyApplyFilterAtMarketOpen()

    def OnData(self, slice):
        if not slice.FutureChains:
            self.Log("No future chain data available.")
            return
        
        if not slice.OptionChains:
            self.Log("No option chain data available.")
            return

        # Step 1: Select 90-day Futures Contract
        future_chain = slice.FutureChains.get(self.future.Symbol)
        if not future_chain:
            self.Log("No valid futures contracts found.")
            return

        futures_contracts = sorted(
            [contract for contract in future_chain if 85 <= (contract.Expiry - self.Time).days <= 95],
            key=lambda x: abs((x.Expiry - self.Time).days - 90)
        )

        if not futures_contracts:
            self.Log("No futures contract close to 90-day expiry found.")
            return

        selected_futures_contract = futures_contracts[0]
        futures_price = selected_futures_contract.LastPrice
        self.prev_futures_prices.append(futures_price)

        self.Log(f"Selected Futures Contract: {selected_futures_contract.Symbol} | Price: {futures_price}")

        # Step 2: Select ATM Option
        option_chain = slice.OptionChains.get(selected_futures_contract.Symbol)
        if not option_chain or len(option_chain) == 0:
            self.Log(f"No options found for futures contract {selected_futures_contract.Symbol}.")
            return

        atm_option_candidates = sorted(
            [contract for contract in option_chain if contract.OpenInterest > 10],
            key=lambda x: abs(x.Strike - futures_price)
        )

        if not atm_option_candidates:
            self.Log("No ATM option found with sufficient liquidity.")
            return

        atm_option = atm_option_candidates[0]  # Closest strike to futures price
        option_price = (atm_option.BidPrice + atm_option.AskPrice) / 2
        self.prev_option_prices.append(option_price)

        self.Log(f"Selected ATM Option: {atm_option.Symbol} | Strike: {atm_option.Strike} | Price: {option_price}")

        # Ensure we have enough data for delta estimation
        if len(self.prev_futures_prices) > 5 and len(self.prev_option_prices) > 5:
            deltas = [
                (self.prev_option_prices[i] - self.prev_option_prices[i - 1]) /
                (self.prev_futures_prices[i] - self.prev_futures_prices[i - 1])
                for i in range(1, 5) if self.prev_futures_prices[i] != self.prev_futures_prices[i - 1]
            ]

            if deltas:
                estimated_delta = sum(deltas) / len(deltas)

                # Step 3: Compute Hedge Ratio
                if estimated_delta != 0:
                    contracts_needed = round(-1 / estimated_delta)
                    position_type = "Long" if estimated_delta < 0 else "Short"

                    self.Log("\n====== Delta Hedge Calculation ======")
                    self.Log(f"Date: {self.Time}")
                    self.Log(f"Futures Price: {futures_price:.2f}")
                    self.Log(f"Option Price: {option_price:.2f}")
                    self.Log(f"Estimated Delta: {estimated_delta:.4f}")
                    self.Log(f"Required Contracts: {contracts_needed}")
                    self.Log(f"Suggested Position: {position_type} {abs(contracts_needed)} option contracts")

        # Keep only last 5 records for smoothing
        if len(self.prev_futures_prices) > 5:
            self.prev_futures_prices.pop(0)
            self.prev_option_prices.pop(0)