Overall Statistics |
Total Orders 867 Average Win 0.12% Average Loss -0.10% Compounding Annual Return 9.701% Drawdown 11.600% Expectancy 0.102 Start Equity 100000 End Equity 108153.48 Net Profit 8.153% Sharpe Ratio 0.201 Sortino Ratio 0.216 Probabilistic Sharpe Ratio 46.166% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 1.23 Alpha -0.059 Beta 0.536 Annual Standard Deviation 0.073 Annual Variance 0.005 Information Ratio -1.77 Tracking Error 0.069 Treynor Ratio 0.028 Total Fees $963.94 Estimated Strategy Capacity $2200000.00 Lowest Capacity Asset VOX T2FCD04TATET Portfolio Turnover 22.10% |
#region imports from AlgorithmImports import * #endregion class DualMomentumAlphaModel(AlphaModel): def __init__(self, algorithm, fast_period = 10, slow_period = 20, moving_average_type = MovingAverageType.EXPONENTIAL, resolution = Resolution.DAILY): self.fast_period = fast_period self.slow_period = slow_period self.moving_average_type = moving_average_type self.resolution = resolution self.day = -1 self.symbol_data = {} self.ETF_data = {} self.etf_map = { algorithm.Securities["VGT"] : MorningstarSectorCode.TECHNOLOGY, algorithm.Securities["XLB"] : MorningstarSectorCode.BASIC_MATERIALS, algorithm.Securities["XLY"] : MorningstarSectorCode.CONSUMER_CYCLICAL, algorithm.Securities["XLF"] : MorningstarSectorCode.FINANCIAL_SERVICES, algorithm.Securities["VNQ"] : MorningstarSectorCode.REAL_ESTATE, algorithm.Securities["XLV"] : MorningstarSectorCode.HEALTHCARE, algorithm.Securities["XLP"] : MorningstarSectorCode.CONSUMER_DEFENSIVE, algorithm.Securities["XLU"] : MorningstarSectorCode.UTILITIES, algorithm.Securities["VOX"] : MorningstarSectorCode.COMMUNICATION_SERVICES, algorithm.Securities["XLE"] : MorningstarSectorCode.ENERGY, algorithm.Securities["XLI"] : MorningstarSectorCode.INDUSTRIALS } self.insight_collection = InsightCollection() def update(self, algorithm, data): insights = [] for symbol in set(data.splits.keys() + data.dividends.keys()): if symbol in self.symbol_data.keys(): self.symbol_data[symbol].ppo.reset() algorithm.subscription_manager.remove_consolidator(symbol, self.symbol_data[symbol].Consolidator) self._register_indicator(algorithm, self.symbol_data[symbol].security) history = algorithm.history[TradeBar](symbol, 20, Resolution.DAILY, data_normalization_mode=DataNormalizationMode.SCALED_RAW) for bar in history: self.symbol_data[symbol].Consolidator.update(bar) if symbol in self.ETF_data.keys(): self.ETF_data[symbol].ppo.reset() algorithm.subscription_manager.remove_consolidator(symbol, self.ETF_data[symbol].Consolidator) self._register_indicator(algorithm, self.ETF_data[symbol].security) history = algorithm.history[TradeBar](symbol, 20, Resolution.DAILY, data_normalization_mode=DataNormalizationMode.SCALED_RAW) for bar in history: self.ETF_data[symbol].Consolidator.update(bar) if data.quote_bars.count == 0: return [] if self.day == algorithm.time.day: return [] self.day = algorithm.time.day momentum_by_sector = {} security_momentum = {} #target_sectors = [self.etf_map[sector_etf.security] for sector_etf in self.etf_data.values() #if sector_etf.security.symbol in data.quote_bars and sector_etf.ppo.is_ready #and sector_etf.ppo.current.value > 0] target_sectors = [sector_etf.security for sector_etf in self.ETF_data.values() if sector_etf.security.symbol in data.quote_bars and sector_etf.ppo.is_ready and sector_etf.ppo.current.value > 0] #target_securities = [] #for sector in target_sectors: #for security in security_momentum[sector]: #if security_momentum[sector][security] > 0: #target_securities.append(security) #target_securities = sorted(target_securities, key = lambda x: algorithm.securities[x.symbol].Fundamentals.MarketCap, reverse=True)[:10] #for sector in target_securities: #insights.append(Insight.price(security.symbol, Expiry.END_OF_DAY, InsightDirection.UP)) for sector_security in target_sectors: insights.append(Insight.price(sector_security.symbol, Expiry.END_OF_DAY, InsightDirection.UP)) return insights def on_securities_changed(self, algorithm, changes): security_by_symbol = {} ETF_by_symbol = {} for added in changes.added_securities: if added in self.etf_map.keys(): self.ETF_data[added.symbol] = ETF(algorithm, added, self.fast_period, self.slow_period, self.moving_average_type, self.resolution) else: self.symbol_data[added.symbol] = SymbolData(algorithm, added, self.fast_period, self.slow_period, self.moving_average_type, self.resolution) if security_by_symbol: history = algorithm.history[TradeBar](list(security_by_symbol.keys()), 20, Resolution.DAILY, data_normalization_mode=DataNormalizationMode.SCALED_RAW) for trade_bars in history: for bar in trade_bars.values(): symbol_data[bar.symbol].consolidator.update(bar) if ETF_by_symbol: history = algorithm.history[TradeBar](list(ETF_by_symbol.keys()), 20, Resolution.DAILY, data_normalization_mode=DataNormalizationMode.SCALED_RAW) for trade_bars in history: for bar in trade_bars.values(): ETF_data[bar.symbol].consolidator.update(bar) for removed in changes.removed_securities: symbol = removed.Symbol if removed in self.etf_map.keys(): data = self.ETF_data.pop(symbol, None) if data is not None: algorithm.SubscriptionManager.RemoveConsolidator(symbol, data.Consolidator) else: data = self.symbol_data.pop(symbol, None) if data is not None: algorithm.SubscriptionManager.RemoveConsolidator(symbol, data.Consolidator) def _register_indicator(self, algorithm, security): if security.symbol in self.symbol_data.keys(): self.symbol_data[security.symbol].Consolidator = TradeBarConsolidator(timedelta(days = 1)) algorithm.subscription_manager.add_consolidator(security.symbol, self.symbol_data[security.symbol].Consolidator) algorithm.RegisterIndicator(security.symbol, self.symbol_data[security.symbol].ppo, self.symbol_data[security.symbol].Consolidator) if security.symbol in self.ETF_data.keys(): self.ETF_data[security.symbol].Consolidator = TradeBarConsolidator(timedelta(days=1)) algorithm.subscription_manager.add_consolidator(security.symbol, self.ETF_data[security.symbol].Consolidator) algorithm.RegisterIndicator(security.symbol, self.ETF_data[security.symbol].ppo, self.ETF_data[security.symbol].Consolidator) class SymbolData: def __init__(self, algorithm, security, fast_period, slow_period, moving_average_type, resolution): self.security = security self.sector = security.Fundamentals.AssetClassification.MorningstarSectorCode self.ppo = PercentagePriceOscillator(security.symbol, fast_period, slow_period, moving_average_type) self.Consolidator = algorithm.ResolveConsolidator(security.symbol, resolution) algorithm.RegisterIndicator(security.symbol, self.ppo, self.Consolidator) algorithm.WarmUpIndicator(security.symbol, self.ppo, resolution) class ETF: def __init__(self, algorithm, security, fast_period, slow_period, moving_average_type, resolution): etf_map = { algorithm.Securities["VGT"] : MorningstarSectorCode.TECHNOLOGY, algorithm.Securities["XLB"] : MorningstarSectorCode.BASIC_MATERIALS, algorithm.Securities["XLY"] : MorningstarSectorCode.CONSUMER_CYCLICAL, algorithm.Securities["XLF"] : MorningstarSectorCode.FINANCIAL_SERVICES, algorithm.Securities["VNQ"] : MorningstarSectorCode.REAL_ESTATE, algorithm.Securities["XLV"] : MorningstarSectorCode.HEALTHCARE, algorithm.Securities["XLP"] : MorningstarSectorCode.CONSUMER_DEFENSIVE, algorithm.Securities["XLU"] : MorningstarSectorCode.UTILITIES, algorithm.Securities["VOX"] : MorningstarSectorCode.COMMUNICATION_SERVICES, algorithm.Securities["XLE"] : MorningstarSectorCode.ENERGY, algorithm.Securities["XLI"] : MorningstarSectorCode.INDUSTRIALS } self.security = security self.sector = etf_map[security] self.ppo = PercentagePriceOscillator(security.symbol, fast_period, slow_period, moving_average_type) self.Consolidator = algorithm.ResolveConsolidator(security.symbol, resolution) algorithm.RegisterIndicator(security.symbol, self.ppo, self.Consolidator) algorithm.WarmUpIndicator(security.symbol, self.ppo, resolution)
# region imports from AlgorithmImports import * from DualMomentumAlphaModel import * # endregion class SectorDualMomentumStrategy(QCAlgorithm): undesired_symbols_from_previous_deployment = [] checked_symbols_from_previous_deployment = False def initialize(self): self.set_start_date(2023, 6, 5) self.set_end_date(2024, 6, 5) self.set_cash(100000) #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.universe_settings.asynchronous = True self.add_universe(self.universe.etf("SPY", self.universe_settings, self._etf_constituents_filter)) self.add_equity("VGT") self.add_equity("XLB") self.add_equity("XLY") self.add_equity("XLF") self.add_equity("VNQ") self.add_equity("XLP") self.add_equity("XLV") self.add_equity("XLU") self.add_equity("VOX") self.add_equity("XLE") self.add_equity("XLI") self.add_alpha(DualMomentumAlphaModel(self)) self.settings.rebalance_portfolio_on_security_changes = False self.settings.rebalance_portfolio_on_insight_changes = False self.day = -1 self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel(self._rebalance_func)) self.add_risk_management(TrailingStopRiskManagementModel()) self.set_execution(ImmediateExecutionModel()) self.set_warm_up(timedelta(7)) def _etf_constituents_filter(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]: selected = sorted([c for c in constituents if c.weight], key=lambda c: c.weight, reverse=True)[:200] return [c.symbol for c in selected] def _rebalance_func(self, time): if self.day != self.time.day and not self.is_warming_up and self.current_slice.quote_bars.count > 0: self.day = self.time.day return time return None def on_data(self, data): 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="Not backed up by current insights") self.undesired_symbols_from_previous_deployment.remove(symbol)