| 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)