Overall Statistics |
Total Orders 114 Average Win 3.48% Average Loss -2.49% Compounding Annual Return 8.846% Drawdown 11.300% Expectancy 0.808 Start Equity 200000 End Equity 600769.88 Net Profit 200.385% Sharpe Ratio 0.577 Sortino Ratio 0.18 Probabilistic Sharpe Ratio 17.461% Loss Rate 25% Win Rate 75% Profit-Loss Ratio 1.40 Alpha 0 Beta 0 Annual Standard Deviation 0.078 Annual Variance 0.006 Information Ratio 0.816 Tracking Error 0.078 Treynor Ratio 0 Total Fees $341.91 Estimated Strategy Capacity $420000.00 Lowest Capacity Asset VIXY UT076X30D0MD Portfolio Turnover 1.24% |
from AlgorithmImports import * from pandas.tseries.offsets import BDay from pandas.tseries.offsets import BMonthEnd from object_store_helper import ObjectStoreHelper from typing import Any, List, Dict from traded_strategy import TradedStrategy class MetatronShortVolatilityStrategy(QCAlgorithm): _notional_value: float = 200_000 _trade_exec_minute_offset: int = 15 _sma_period: int = 20 def initialize(self) -> None: self.set_start_date(2012, 1, 1) self.set_cash(self._notional_value) self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) self.settings.minimum_order_margin_portfolio_percentage = 0 self.settings.daily_precise_end_time = True leverage: int = 4 security: Security = self.add_equity('VIXY', Resolution.MINUTE, leverage=leverage) self._traded_asset: Symbol = security.symbol # holding days setting self._before_expiration_delta_days: int = -4 self._after_expiration_delta_days: int = 0 assert self._before_expiration_delta_days < self._after_expiration_delta_days, 'delta days are not aligned properly' security.sma: SimpleMovingAverage = self.sma(self._traded_asset, self._sma_period, Resolution.DAILY) security.sma.updated += self.sma_updated self.last_market_price: float|None = None # load expiration days file: str = self.download('data.quantpedia.com/backtesting_data/calendar/vix_futures_expiration.csv') self.vix_expiration_dates: List[datetime.date] = list( map(lambda x: datetime.strptime(x, '%Y-%m-%d').date(), file.split('\r\n')[1:]) ) self.vix_symbols: List[Symbol] = [ self.add_data(CBOE, 'VIX', Resolution.DAILY).symbol, self.add_data(CBOE, 'VIX3M', Resolution.DAILY).symbol ] self.position_opened_this_month: bool = False self.recent_month: int = -1 self.market_close_flag: bool = False self.schedule.on( self.date_rules.every_day(self._traded_asset), self.time_rules.before_market_close(self._traded_asset, self._trade_exec_minute_offset), self.market_close ) self.set_warmup(self._sma_period, Resolution.DAILY) def sma_updated(self, sender: SimpleMovingAverage, datapoint: IndicatorDataPoint) -> None: self.last_market_price = self.securities[self._traded_asset].price def close_volatility_position(self) -> None: if self.portfolio[self._traded_asset].invested: self.market_order(self._traded_asset, -self.portfolio[self._traded_asset].quantity) def market_close(self) -> None: self.market_close_flag = True def on_data(self, slice: Slice) -> None: if not (slice.ContainsKey(self._traded_asset) and slice[self._traded_asset] is not None) or \ not all(self.securities.contains_key(symbol) and self.securities[symbol].get_last_data() for symbol in self.vix_symbols) or \ not self.securities[self._traded_asset].sma.is_ready or self.last_market_price is None or \ self.is_warming_up: return if not self.market_close_flag: return self.market_close_flag = False # get 1-day lagged VIX signal vix_in_contango: bool = self.securities[self.vix_symbols[1]].price / self.securities[self.vix_symbols[0]].price > 1. # check VIX ratio every day if self.portfolio.invested and not vix_in_contango: self.log(f'Liquidation signal triggered. VIX in contango: {vix_in_contango}. Terminating position') self.close_volatility_position() # new month -> reset flag if self.recent_month != self.time.month: self.position_opened_this_month: bool = False self.recent_month = self.time.month open_day_range: List[int] = list(range(self._before_expiration_delta_days, self._after_expiration_delta_days)) # open position if any((self.time.date() + BDay((-1 * x) + 1)).date() in self.vix_expiration_dates for x in open_day_range): self.log('Trying to open position') if vix_in_contango: self.log(f'VIX in contango: {vix_in_contango}') if not self.portfolio[self._traded_asset].invested: if not self.position_opened_this_month: above_sma: bool = self.last_market_price > self.securities[self._traded_asset].sma.current.value if above_sma: self.log(f'Last Market Price above SMA: {above_sma}') self.log(f'Opening position') quantity: int = self._notional_value // slice[self._traded_asset].price self.market_order(self._traded_asset, -quantity) # NOTE handle sudden changes of term structure # highly improbable, yet possible self.position_opened_this_month = True # close day elif (self.time + BDay(-1 * self._after_expiration_delta_days)).date() in self.vix_expiration_dates: self.log('Upcoming expiration date. Terminating position') self.close_volatility_position()
# region imports from AlgorithmImports import * import json from traded_strategy import TradedStrategy # endregion class ObjectStoreHelper: def __init__( self, algorithm: QCAlgorithm, path: str ) -> None: """ Initializes ObjectStoreHelper with reference to the algorithm instance. """ self._algorithm: QCAlgorithm = algorithm self._path: str = path def save_state(self, state: Dict) -> None: """ Saves a dictionary `state` to the Object Store as JSON. """ if not self._algorithm.live_mode: return json_data = json.dumps(state) self._algorithm.object_store.save(self._path, json_data) self._algorithm.log(f"Saved state to Object Store: {json_data}") def load_state(self) -> Dict: """ Loads a JSON string from the Object Store and returns it as a dictionary. """ if self._algorithm.object_store.contains_key(self._path) and self._algorithm.live_mode: json_data = self._algorithm.object_store.read(self._path) if json_data: self._algorithm.log(f"Loaded state from Object Store: {json_data}") result: Dict = json.loads(json_data) result['trade_signal'] = {TradedStrategy._member_map_[key]: value for key, value in result['trade_signal'].items() if key in TradedStrategy._member_map_} return result else: return { 'trade_signal': { TradedStrategy.CALENDAR: False, TradedStrategy.REVERSAL_MODEL: False }, 'reversal_model_days_held': 0 } return {}
# region imports from AlgorithmImports import * from enum import Enum # endregion class TradedStrategy(Enum): CALENDAR = 1 REVERSAL_MODEL = 2