Overall Statistics |
Total Orders 14 Average Win 0.43% Average Loss -1.22% Compounding Annual Return -4.977% Drawdown 8.100% Expectancy -0.614 Start Equity 1000000 End Equity 920312.56 Net Profit -7.969% Sharpe Ratio -2.808 Sortino Ratio -3.245 Probabilistic Sharpe Ratio 0.072% Loss Rate 71% Win Rate 29% Profit-Loss Ratio 0.35 Alpha -0.056 Beta -0.247 Annual Standard Deviation 0.032 Annual Variance 0.001 Information Ratio -1.74 Tracking Error 0.127 Treynor Ratio 0.358 Total Fees $9.94 Estimated Strategy Capacity $1300000.00 Lowest Capacity Asset NQ 32NKVT3A87TLW|NQ YOGVNNAOI1OH Portfolio Turnover 0.16% |
# Jingxiang Zou # Quantitative Research Analyst # region imports from datetime import timedelta from numpy import array, exp, log, sqrt, arange, var from AlgorithmImports import * from math import floor import numpy as np from pandas import DataFrame from datetime import datetime import pandas as pd # endregion class HedgedFutureStrategy(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2023, 4, 1) self.set_cash(1000000) self.set_security_initializer(CustomSecurityInitializer(self)) self.last_roll_date_1 = None # Variable to track the last roll date self.last_roll_date_2 = None # leverage and protection level # self.leverage = 5.0 self.protection_level = 1.0 self.margin_pct = 0.25 # the index self.nasdaq100 = self.AddIndex("NDX", Resolution.MINUTE).Symbol self.cumulative_return = 1 # start with 1 for multiplicative return calculation self.previous_price = None # our strategy self.previous_port_value = None self.normalized_port_value = 1 # add the futures to the universe self.universe_settings.asynchronous = True self.future = self.add_future(Futures.Indices.NASDAQ_100_E_MINI, extended_market_hours=False, data_mapping_mode=DataMappingMode.LAST_TRADING_DAY, # time to roll : on the last trading day data_normalization_mode=DataNormalizationMode.RAW, # adjust for jumps, backward in time contract_depth_offset=0, # getting the front contract resolution=Resolution.MINUTE ) self.future.set_filter(0, 180) # add the option to the universe self.add_future_option(self.future.symbol, self._contract_selector) self.lev = None self.rets = [] def _contract_selector(self, universe): return universe.Expiration(0, 365) def on_end_of_day(self, symbol): self.plot(f'Security Type {symbol.id.security_type}', 'EOD', self.securities[symbol].price) def closest_number(self, target, sorted_list): # initialize variables to keep track of the closest number and the smallest difference closest = sorted_list[0] min_difference = abs(target - closest) # iterate through the list to find the number with the smallest difference for number in sorted_list: difference = abs(target - number) if difference < min_difference: closest = number min_difference = difference return closest def on_data(self, data: Slice) -> None: if self.Time.date() != self.last_roll_date_2 and self.Time.date() > datetime(2021, 11, 12).date(): # Check if the date has changed self.last_roll_date_2 = self.Time.date() # Update the last roll date if self.future.Symbol in data.FutureChains: future_chain = data.FutureChains[self.future.Symbol] valid_contracts = [contract for contract in future_chain if contract.Expiry > self.Time] if valid_contracts: self.front_contract = sorted(valid_contracts, key=lambda x: x.Expiry)[0] self.debug(f"what's in your portfolio ??? ") for symbol in self.Portfolio.Keys: holding = self.Portfolio[symbol] if holding.Invested: self.Debug(f"{symbol}: {holding.Quantity}") if not self.portfolio.invested: watcher = 0 if self.future.Symbol in data.FutureChains: future_chain = data.FutureChains[self.future.Symbol] valid_contracts = [contract for contract in future_chain if contract.Expiry > self.Time] if valid_contracts: front_contract = sorted(valid_contracts, key=lambda x: x.Expiry)[0] kks = [option_chain for _, option_chain in data.option_chains.items()] kss = sorted(kks, key= lambda x : list(x.contracts)[0].value.expiry, reverse=False) sss1 = [x for x in kss if list(x.contracts)[0].value.expiry > front_contract.expiry - timedelta(30)] for option_chain in sss1: watcher += 1 put_options = [contract for contract in option_chain if contract.Right == OptionRight.PUT] if put_options: cleveland = sorted([int(x.strike) / 10 for x in put_options]) fp = self.Securities[self.front_contract.Symbol].Price self.debug(f"this is the current front NQ price = {fp}") self.debug(f"these are the striks") self.debug(cleveland[100:105]) gs = self.closest_number(fp * self.protection_level, cleveland) self.debug(f"here is the nearest strike = {gs}") opt_contract = [x for x in put_options if int(x.strike) == gs * 10][0] self.market_order(opt_contract, 1) if watcher == 1: break def on_end_of_algorithm(self): for symbol, security in self.securities.items(): if security.is_tradable: self.log(f'{self.time} :: {symbol} :: {security.price}') # Outside of the algorithm class class CustomSecurityInitializer(BrokerageModelSecurityInitializer): def __init__(self, algorithm: QCAlgorithm) -> None: security_seeder = SecuritySeeder.NULL super().__init__(algorithm.brokerage_model, security_seeder) def initialize(self, security: Security) -> None: # First, call the superclass definition. # This method sets the reality models of each security using the default reality models of the brokerage model. super().initialize(security) # Next, overwrite the assignment model if security.Type == SecurityType.FUTURE_OPTION: # Option type security.set_option_exercise_model(MyOptionExerciseModel()) class MyOptionExerciseModel(DefaultExerciseModel): def option_exercise(self, option: Option, order: OptionExerciseOrder) -> List[OrderEvent]: underlying = option.underlying utc_time = Extensions.convert_to_utc(option.local_time, option.exchange.time_zone) underlying_price = underlying.price strike = option.scaled_strike_price / 10 # NQ hack intrinsic_value = max(0, underlying_price - strike if option.right == OptionRight.Call else strike - underlying_price) in_the_money = intrinsic_value >= 0.01 is_assignment = in_the_money and option.holdings.is_short order_event = OrderEvent(order.id, option.symbol, utc_time, OrderStatus.FILLED, Extensions.get_order_direction(order.quantity), 0, order.quantity, OrderFee.ZERO, Messages.DefaultExerciseModel.ContractHoldingsAdjustmentFillTag(in_the_money, is_assignment, option) ) order_event.is_assignment = is_assignment order_event.is_in_the_money = in_the_money order_events = [order_event] if in_the_money and option.exercise_settlement == SettlementType.PHYSICAL_DELIVERY: exercise_quantity = option.get_exercise_quantity(order.quantity) delivery_order_event = OrderEvent(order.id, underlying.symbol, utc_time, OrderStatus.FILLED, Extensions.get_order_direction(exercise_quantity), strike, exercise_quantity, OrderFee.ZERO, Messages.DefaultExerciseModel.OptionAssignment if is_assignment else Messages.DefaultExerciseModel.OptionExercise ) delivery_order_event.is_in_the_money = True order_events.append(delivery_order_event) return order_events