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