Created with Highcharts 12.1.2Equity2000200220042006200820102012201420162018202020222024202601M2M-50-25000.050.10120100M200M025M50M202530
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