Overall Statistics |
Total Orders 155 Average Win 0.34% Average Loss -0.21% Compounding Annual Return 0.315% Drawdown 3.500% Expectancy 0.105 Start Equity 1000000 End Equity 1003168.30 Net Profit 0.317% Sharpe Ratio -1.251 Sortino Ratio -1.525 Probabilistic Sharpe Ratio 13.391% Loss Rate 58% Win Rate 42% Profit-Loss Ratio 1.61 Alpha 0 Beta 0 Annual Standard Deviation 0.04 Annual Variance 0.002 Information Ratio 0.079 Tracking Error 0.04 Treynor Ratio 0 Total Fees $1345.66 Estimated Strategy Capacity $260000.00 Lowest Capacity Asset EWO R735QTJ8XC9X Portfolio Turnover 2.54% |
#region imports from AlgorithmImports import * #endregion class MeanReversionAlphaModel(AlphaModel): _securities = [] _month = -1 def __init__(self, roc_period, num_positions_per_side): self._roc_period = roc_period self._num_positions_per_side = num_positions_per_side def update(self, algorithm: QCAlgorithm, data: Slice) -> List[Insight]: # Reset indicators when corporate actions occur for symbol in set(data.splits.keys() + data.dividends.keys()): security = algorithm.securities[symbol] if security in self._securities: algorithm.unregister_indicator(security.indicator) self._initialize_indicator(algorithm, security) # Only emit insights when there is quote data, not when a corporate action occurs (at midnight) if data.quote_bars.count == 0: return [] # Only emit insights once per month if self._month == algorithm.time.month: return [] # Check if enough indicators are ready ready_securities = [security for security in self._securities if security.indicator.is_ready and security.symbol in data.quote_bars] if len(ready_securities) < 2 * self._num_positions_per_side: return [] self._month = algorithm.time.month # Short securities that have the highest trailing ROC sorted_by_roc = sorted(ready_securities, key=lambda security: security.indicator.current.value) insights = [Insight.price(security.symbol, Expiry.END_OF_MONTH, InsightDirection.DOWN) for security in sorted_by_roc[-self._num_positions_per_side:]] # Long securities that have the lowest trailing ROC insights += [Insight.price(security.symbol, Expiry.END_OF_MONTH, InsightDirection.UP) for security in sorted_by_roc[:self._num_positions_per_side]] return insights def _initialize_indicator(self, algorithm, security): security.indicator = algorithm.ROC(security.symbol, self._roc_period, Resolution.DAILY) algorithm.warm_up_indicator(security.symbol, security.indicator) def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None: for security in changes.added_securities: self._initialize_indicator(algorithm, security) self._securities.append(security) for security in changes.removed_securities: if security in self._securities: algorithm.unregister_indicator(security.indicator) self._securities.remove(security)
#region imports from AlgorithmImports import * from universe import CountryEquityIndexUniverseSelectionModel from alpha import MeanReversionAlphaModel #endregion class CountryEquityIndexesMeanReversionAlgorithm(QCAlgorithm): _undesired_symbols_from_previous_deployment = [] _checked_symbols_from_previous_deployment = False _previous_expiry_time = None def initialize(self): self.set_start_date(2023, 3, 1) # Set Start Date 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(CountryEquityIndexUniverseSelectionModel()) self.add_alpha(MeanReversionAlphaModel( self.get_parameter("roc_period_months", 6) * 21, self.get_parameter("num_positions_per_side", 5) )) self.settings.rebalance_portfolio_on_security_changes = False self.settings.rebalance_portfolio_on_insight_changes = False self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel(self._rebalance_func)) self.add_risk_management(NullRiskManagementModel()) self.set_execution(ImmediateExecutionModel()) self.set_warm_up(timedelta(31)) def _rebalance_func(self, time): # Rebalance when all of the following are true: # - There are new insights or old insights have been cancelled since the last rebalance # - The algorithm isn't warming up # - There is QuoteBar data in the current slice latest_expiry_time = sorted([insight.close_time_utc for insight in self.insights], reverse=True)[0] if self.insights.count else None if self._previous_expiry_time != latest_expiry_time and not self.is_warming_up and self.current_slice.quote_bars.count > 0: self._previous_expiry_time = latest_expiry_time 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)
#region imports from AlgorithmImports import * #endregion class CountryEquityIndexUniverseSelectionModel(ManualUniverseSelectionModel): def __init__(self): tickers = [ "EWJ", # iShares MSCI Japan Index ETF "EFNL", # iShares MSCI Finland Capped Investable Market Index ETF "EWW", # iShares MSCI Mexico Inv. Mt. Idx "ERUS", # iShares MSCI Russia ETF "IVV", # iShares S&P 500 Index "AUD", # Australia Bond Index Fund "EWQ", # iShares MSCI France Index ETF "EWH", # iShares MSCI Hong Kong Index ETF "EWI", # iShares MSCI Italy Index ETF "EWY", # iShares MSCI South Korea Index ETF "EWP", # iShares MSCI Spain Index ETF "EWD", # iShares MSCI Sweden Index ETF "EWL", # iShares MSCI Switzerland Index ETF "EWC", # iShares MSCI Canada Index ETF "EWZ", # iShares MSCI Brazil Index ETF "EWO", # iShares MSCI Austria Investable Mkt Index ETF "EWK", # iShares MSCI Belgium Investable Market Index ETF "BRAQ", # Global X Brazil Consumer ETF "ECH" # iShares MSCI Chile Investable Market Index ETF ] symbols = [Symbol.create(ticker, SecurityType.EQUITY, Market.USA) for ticker in tickers] super().__init__(symbols)