Overall Statistics |
Total Orders 1076 Average Win 2.08% Average Loss -2.01% Compounding Annual Return 10.743% Drawdown 50.800% Expectancy 0.258 Start Equity 100000 End Equity 1307023.39 Net Profit 1207.023% Sharpe Ratio 0.38 Sortino Ratio 0.413 Probabilistic Sharpe Ratio 0.096% Loss Rate 38% Win Rate 62% Profit-Loss Ratio 1.03 Alpha 0 Beta 0 Annual Standard Deviation 0.178 Annual Variance 0.032 Information Ratio 0.507 Tracking Error 0.178 Treynor Ratio 0 Total Fees $22088.67 Estimated Strategy Capacity $110000000.00 Lowest Capacity Asset XLY RGRPZX100F39 Portfolio Turnover 5.15% |
# https://quantpedia.com/strategies/front-running-sector-etf-seasonality-strategy/ # # The investment universe for this strategy consists of the 9 sector ETFs of the S&P 500 index: XLB (materials), XLE (energy), XLF (financials), XLI (industrials), # XLK (technology), XLP (consumer staples), XLU (utilities), XLV (health care), and XLY (consumer discretionary). These ETFs are selected because they represent # distinct sectors of the US stock market, providing a diversified exposure to different economic segments. The individual instruments are selected based on their # historical performance during specific months, as outlined in the research paper. The strategy focuses on identifying the top-performing ETFs from the same month # # in the previous year to anticipate future trends. # The strategy does not rely on traditional technical indicators but instead uses a seasonality-based approach. The methodology involves evaluating the performance # of each ETF in the T-11 month of the previous year. At the end of each month (for example January), the ETFs are ranked based on their past performance for that # specific month (T-11 month, for example, previous year March). The top-performing ETFs are selected for long positions in the upcoming month (for example, February). # The buy rule is to enter long positions in the selected ETFs at the beginning of the month. The sell rule is to close these positions at the end of the month, # allowing for a monthly re-evaluation and selection process. # The strategy involves holding long positions in the top 2 ranked ETFs each month. Equal capital is allocated to each selected ETF to maintain a balanced portfolio. # 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 FrontRunningSectorETFSeasonalityStrategy(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2000, 1, 1) self.set_cash(100_000) self._period: int = 12 self._offset_months: int = 10 self._top_count: int = 2 self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) tickers: List[str] = ['XLB', 'XLE', 'XLF', 'XLI', 'XLK', 'XLP', 'XLU', 'XLV', 'XLY'] 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.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 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().dropna() if len(monthly_returns) >= self._period - 1 and len(prices.columns) == len(self._traded_assets): # Historical performance. observed_performance: DataFrame = monthly_returns[monthly_returns.index.month == (self.time - pd.DateOffset(months=self._offset_months)).month].iloc[0] top_sorted_performance: DataFrame = observed_performance.sort_values(ascending=False).iloc[:self._top_count] # Order execution. targets: List[PortfolioTarget] = [] for symbol in top_sorted_performance.index: if slice.contains_key(str(symbol)) and slice[str(symbol)]: targets.append(PortfolioTarget(str(symbol), 1 / self._top_count)) self.set_holdings(targets, True) def _selection(self) -> None: self._selection_flag = True