| Overall Statistics |
|
Total Orders 118 Average Win 0.77% Average Loss -0.62% Compounding Annual Return 10.083% Drawdown 8.500% Expectancy 0.408 Start Equity 100000 End Equity 110112.42 Net Profit 10.112% Sharpe Ratio 0.19 Sortino Ratio 0.213 Probabilistic Sharpe Ratio 36.174% Loss Rate 38% Win Rate 62% Profit-Loss Ratio 1.25 Alpha 0 Beta 0 Annual Standard Deviation 0.102 Annual Variance 0.01 Information Ratio 0.729 Tracking Error 0.102 Treynor Ratio 0 Total Fees $229.99 Estimated Strategy Capacity $770000.00 Lowest Capacity Asset INDY UHSHWL3SZOTH Portfolio Turnover 4.13% Drawdown Recovery 54 |
# https://quantpedia.com/strategies/seasonal-front-running-in-country-etfs
#
# The investment universe for this strategy consists of 23 country ETFs, as outlined in the research paper.
# These ETFs include SPY, EWU, EWG, EWQ, EWI, EWD, EWN, EWP, EWK, EWL, EWC, EWJ, EWW, EWM, EWA, EWS, EWY, EWT, EWZ, EWH, EZA, FXI, and INDY. The selection of these ETFs is based on their availability and historical data,
# with most having data available from the year 2000. The strategy focuses on these ETFs to exploit seasonal patterns in their returns. The strategy employs a cross-sectional approach to seasonality. At the end of each month,
# the returns of all included ETFs are ranked based on their performance in the same month of the previous year. This ranking is used to identify the top-performing ETFs. The methodology involves selecting a subset of these
# top-ranked ETFs for investment in the following month. The buy rule is to invest in the top 3 to 8 ETFs based on their seasonal ranking, while the sell rule involves exiting positions at the end of the month to reassess
# and rebalance based on new rankings. The strategy involves rebalancing the portfolio at the end of each month. The number of positions selected ranges from 3 to 8 ETFs, based on their seasonal ranking. Capital is allocated
# equally among the selected ETFs to ensure diversification and manage risk.
# region imports
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from pandas.core.frame import DataFrame
from typing import List
import pandas as pd
# endregion
class SeasonalFrontRunninginCountryETFs(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 1, 1)
self.set_end_date(2025, 1, 1)
self.set_cash(100_000)
self._period: int = 12
self._offset_months: int = 10
self._slice = slice(3, 9)
self._selected_assets: List[Symbol] = []
self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
tickers: List[str] = [
'SPY', # SPDR S&P 500 ETF Trust
'EWU', # iShares MSCI United Kingdom ETF
'EWG', # iShares MSCI Germany ETF
'EWQ', # iShares MSCI France ETF
'EWI', # iShares MSCI Italy ETF
'EWD', # iShares MSCI Sweden ETF
'EWN', # iShares MSCI Netherlands ETF
'EWP', # iShares MSCI Spain ETF
'EWK', # iShares MSCI Belgium ETF
'EWL', # iShares MSCI Switzerland ETF
'EWC', # iShares MSCI Canada ETF
'EWJ', # iShares MSCI Japan ETF
'EWW', # iShares MSCI Mexico ETF
'EWM', # iShares MSCI Malaysia ETF
'EWA', # iShares MSCI Australia ETF
'EWS', # iShares MSCI Singapore ETF
'EWY', # iShares MSCI South Korea ETF
'EWT', # iShares MSCI Taiwan ETF
'EWZ', # iShares MSCI Brazil ETF
'EWH', # iShares MSCI Hong Kong ETF
'EZA', # iShares MSCI South Africa ETF
'FXI', # iShares China Large-Cap ETF
'INDY' # iShares India 50 ETF
]
self._traded_assets: List[Symbol] = [
self.add_equity(ticker, Resolution.DAILY).symbol for ticker in tickers
]
self._selection_flag: bool = False
self.settings.minimum_order_margin_portfolio_percentage = 0.
self.settings.daily_precise_end_time = False
self.schedule.on(
self.date_rules.month_end(self._traded_assets[0]),
self.time_rules.before_market_close(self._traded_assets[0]),
self._selection
)
def on_data(self, slice: Slice) -> None:
# Monthly rebalance.
if not self._selection_flag:
return
self._selection_flag = False
# Order execution.
targets: List[PortfolioTarget] = []
for symbol in self._selected_assets:
if slice.contains_key(str(symbol)) and slice[str(symbol)]:
targets.append(PortfolioTarget(str(symbol), 1 / len(self._selected_assets)))
self.set_holdings(targets, True)
self._selected_assets.clear()
def _selection(self) -> None:
history: DataFrame = self.history(
self._traded_assets, start=self.time - relativedelta(months=self._period), end=self.time
)
prices: DataFrame = history.close.unstack(level=0)
monthly_returns: DataFrame = prices.groupby(pd.Grouper(freq='M')).last().pct_change()[1:].dropna(axis=1)
if len(monthly_returns) >= self._period - 1: # The paper suggests that a complete set of assets is not required prior to evaluation.
self._selection_flag = True
# Historical performance.
observed_performance: DataFrame = monthly_returns[monthly_returns.index.month == (self.time - pd.DateOffset(months=self._offset_months)).month].iloc[0]
self._selected_assets = list(observed_performance.sort_values(ascending=False).iloc[self._slice].index)