Overall Statistics |
Total Orders 589 Average Win 0.55% Average Loss -0.23% Compounding Annual Return 51.139% Drawdown 27.600% Expectancy 1.020 Start Equity 100000000 End Equity 184228405.11 Net Profit 84.228% Sharpe Ratio 1.273 Sortino Ratio 1.518 Probabilistic Sharpe Ratio 64.035% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 2.38 Alpha 0 Beta 0 Annual Standard Deviation 0.261 Annual Variance 0.068 Information Ratio 1.479 Tracking Error 0.261 Treynor Ratio 0 Total Fees $66185.09 Estimated Strategy Capacity $430000000.00 Lowest Capacity Asset NQ YLZ9Z50BJE2P Portfolio Turnover 12.88% |
# region imports from AlgorithmImports import * from universe import FrontMonthFutureUniverseSelectionModel from portfolio import InverseVolatilityPortfolioConstructionModel # endregion class InverseVolatilityRankAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2023, 3, 1) # Set Start Date self.set_cash(100000000) # For a large future universe, the fund needed would be large self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) self.universe_settings.extended_market_hours = True self.universe_settings.resolution = Resolution.MINUTE # Seed initial price data self.set_security_initializer(BrokerageModelSecurityInitializer( self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))) # We only want front month contract self.add_universe_selection(FrontMonthFutureUniverseSelectionModel(self.select_future_chain_symbols)) # Since we're using all assets for portfolio optimization, we emit constant alpha for every security self.add_alpha(ConstantAlphaModel(InsightType.PRICE, InsightDirection.UP, timedelta(7))) # A custom PCM to size by inverse volatility self.set_portfolio_construction(InverseVolatilityPortfolioConstructionModel()) self.set_warmup(31, Resolution.DAILY) def select_future_chain_symbols(self, utcTime): return [ Symbol.create(Futures.Indices.VIX, SecurityType.FUTURE, Market.CFE), Symbol.create(Futures.Indices.SP_500_E_MINI, SecurityType.FUTURE, Market.CME), Symbol.create(Futures.Indices.NASDAQ_100_E_MINI, SecurityType.FUTURE, Market.CME), Symbol.create(Futures.Indices.DOW_30_E_MINI, SecurityType.FUTURE, Market.CME), Symbol.create(Futures.Energies.BRENT_CRUDE, SecurityType.FUTURE, Market.NYMEX), Symbol.create(Futures.Energies.GASOLINE, SecurityType.FUTURE, Market.NYMEX), Symbol.create(Futures.Energies.HEATING_OIL, SecurityType.FUTURE, Market.NYMEX), Symbol.create(Futures.Energies.NATURAL_GAS, SecurityType.FUTURE, Market.NYMEX), Symbol.create(Futures.Grains.CORN, SecurityType.FUTURE, Market.CBOT), Symbol.create(Futures.Grains.OATS, SecurityType.FUTURE, Market.CBOT), Symbol.create(Futures.Grains.SOYBEANS, SecurityType.FUTURE, Market.CBOT), Symbol.create(Futures.Grains.WHEAT, SecurityType.FUTURE, Market.CBOT), ]
#region imports from AlgorithmImports import * from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel #endregion class InverseVolatilityPortfolioConstructionModel(EqualWeightingPortfolioConstructionModel): def __init__(self, rebalance = Expiry.END_OF_WEEK, lookback = 30): '''Initialize a new instance of EqualWeightingPortfolioConstructionModel Args: rebalance: Rebalancing parameter. If it is a timedelta, date rules or Resolution, it will be converted into a function. If None will be ignored. The function returns the next expected rebalance time for a given algorithm UTC DateTime. The function returns null if unknown, in which case the function will be called again in the next loop. Returning current time will trigger rebalance. lookback: The lookback period of historical return to calculate the volatility''' super().__init__(rebalance, PortfolioBias.LONG) self.symbol_data = {} self.lookback = lookback def determine_target_percent(self, activeInsights): '''Will determine the target percent for each insight Args: activeInsights: The active insights to generate a target for''' result = {} active_symbols = [insight.symbol for insight in activeInsights] active_data = {symbol: data for symbol, data in self.symbol_data.items() if data.is_ready and symbol in active_symbols} # make sure data in used are ready # Sum the inverse STD of ROC for normalization later std_sum = sum([1 / data.value for data in active_data.values()]) if std_sum == 0: return {insight: 0 for insight in activeInsights} for insight in activeInsights: if insight.symbol in active_data: data = active_data[insight.symbol] # Sizing by inverse volatility, then divide by contract multiplier to avoid unwanted leveraging result[insight] = 10 / data.value / std_sum / data.multiplier else: result[insight] = 0 return result def on_securities_changed(self, algorithm, changes): super().on_securities_changed(algorithm, changes) for removed in changes.removed_securities: data = self.symbol_data.pop(removed.symbol, None) # Free up resources if data: data.dispose() for added in changes.added_securities: symbol = added.symbol if symbol not in self.symbol_data: self.symbol_data[symbol] = SymbolData(algorithm, added, self.lookback) class SymbolData: '''An object to hold the daily return and volatility data for each security''' def __init__(self, algorithm, security, period): self.algorithm = algorithm self.symbol = security.symbol self.multiplier = security.symbol_properties.contract_multiplier self.ROC = RateOfChange(1) self.volatility = IndicatorExtensions.of(StandardDeviation(period), self.ROC) self.consolidator = TradeBarConsolidator(timedelta(1)) self.consolidator.data_consolidated += self.on_data_update algorithm.subscription_manager.add_consolidator(self.symbol, self.consolidator) # Warm up with historical data history = algorithm.history[TradeBar](self.symbol, period+1, Resolution.DAILY) for bar in history: self.ROC.update(bar.end_time, bar.close) def on_data_update(self, sender, bar): self.ROC.update(bar.end_time, bar.close) def dispose(self): '''Free up memory and speed up update cycle''' self.consolidator.data_consolidated -= self.on_data_update self.algorithm.subscription_manager.remove_consolidator(self.symbol, self.consolidator) self.ROC.reset() self.volatility.reset() @property def is_ready(self): return self.volatility.is_ready and self.value != 0 @property def value(self): return self.volatility.current.value
#region imports from AlgorithmImports import * from Selection.FutureUniverseSelectionModel import FutureUniverseSelectionModel #endregion class FrontMonthFutureUniverseSelectionModel(FutureUniverseSelectionModel): '''Creates futures chain universes that select the front month contract and runs a user defined futureChainSymbolSelector every day to enable choosing different futures chains''' def __init__(self, select_future_chain_symbols, rebalancePeriod = 7): super().__init__(timedelta(rebalancePeriod), select_future_chain_symbols) def filter(self, filter): '''Defines the futures chain universe filter''' return (filter.front_month() .only_apply_filter_at_market_open())