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