Overall Statistics |
Total Orders 51 Average Win 1.98% Average Loss -0.29% Compounding Annual Return -19.713% Drawdown 25.600% Expectancy -0.271 Start Equity 1000000 End Equity 801905.08 Net Profit -19.809% Sharpe Ratio -1.731 Sortino Ratio -2.256 Probabilistic Sharpe Ratio 0.253% Loss Rate 91% Win Rate 9% Profit-Loss Ratio 6.78 Alpha 0 Beta 0 Annual Standard Deviation 0.108 Annual Variance 0.012 Information Ratio -1.237 Tracking Error 0.108 Treynor Ratio 0 Total Fees $136.59 Estimated Strategy Capacity $570000.00 Lowest Capacity Asset OEF RZ8CR0XXNOF9 Portfolio Turnover 1.18% |
# region imports from AlgorithmImports import * # endregion class VIXAlphaModel(AlphaModel): _symbols = [] def __init__(self, algorithm, lookback_days, long_percentile, short_percentile): self._vix = algorithm.add_data(CBOE, "VIX", Resolution.DAILY).symbol self._window = RollingWindow[float](lookback_days) self._long_percentile = long_percentile self._short_percentile = short_percentile def update(self, algorithm: QCAlgorithm, data: Slice) -> List[Insight]: if data.contains_key(self._vix): # Get the current VIX value price = data[self._vix].price # Get the trailing VIX values self._window.add(price) if not self._window.is_ready: return [] history_close = [i for i in self._window] # Check if the current VIX value is in an area of extreme relative to trailing values if price > np.percentile(history_close, self._long_percentile): direction = InsightDirection.UP elif price < np.percentile(history_close, self._short_percentile): direction = InsightDirection.DOWN else: return [] # Emit insights if the current VIX value is extreme return [Insight.price(symbol, timedelta(365), direction) for symbol in self._symbols] return [] def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None: for security in changes.added_securities: self._symbols.append(security.symbol)
# region imports from AlgorithmImports import * from alpha import VIXAlphaModel # endregion class VIXPredictsStockIndexReturns(QCAlgorithm): _undesired_symbols_from_previous_deployment = [] _checked_symbols_from_previous_deployment = False _previous_direction_by_symbol = {} _week = -1 def initialize(self): self.set_start_date(2023, 3, 1) self.set_end_date(2024, 3, 1) self.set_cash(1_000_000) self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) self.settings.minimum_order_margin_portfolio_percentage = 0 self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW self.add_universe_selection(ManualUniverseSelectionModel([Symbol.create("OEF", SecurityType.EQUITY, Market.USA)])) lookback_days = self.get_parameter("lookback_days", 504) self.add_alpha(VIXAlphaModel( self, lookback_days, self.get_parameter("long_percentile", 90), self.get_parameter("short_percentile", 10) )) self.settings.rebalance_portfolio_on_insight_changes = False self.settings.rebalance_portfolio_on_security_changes = False self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel(self._rebalance_func)) self.add_risk_management(NullRiskManagementModel()) self.set_execution(ImmediateExecutionModel()) self.set_warm_up(timedelta(lookback_days*2)) def _rebalance_func(self, time): # Rebalance when all of the following are true: # - Not warming up # - There is QuoteBar data in the current slice # - It's a new week or the insight direction has changed direction_by_symbol = {insight.symbol: insight.direction for insight in self.insights} if (not self.is_warming_up and self.current_slice.quote_bars.count > 0 and (direction_by_symbol != self._previous_direction_by_symbol or self.time.date().isocalendar()[1] != self._week)): self._week = self.time.date().isocalendar()[1] self._previous_direction_by_symbol = direction_by_symbol return time return None def on_data(self, data): # Exit positions that aren't backed by existing insights. # If you don't want this behavior, delete this method definition. if not self.is_warming_up and not self._checked_symbols_from_previous_deployment: for security_holding in self.portfolio.values(): if not security_holding.invested: continue symbol = security_holding.symbol if not self.insights.has_active_insights(symbol, self.utc_time): self._undesired_symbols_from_previous_deployment.append(symbol) self._checked_symbols_from_previous_deployment = True for symbol in self._undesired_symbols_from_previous_deployment: if self.is_market_open(symbol): self.liquidate(symbol, tag="Holding from previous deployment that's no longer desired") self._undesired_symbols_from_previous_deployment.remove(symbol)