Created with Highcharts 12.1.2Equity20082009201020112012201320142015201620172018201920202021202220232024202520260200k400k-20-10000.050.1012050M100M010M22242628
Overall Statistics
Total Orders
688
Average Win
1.16%
Average Loss
-1.13%
Compounding Annual Return
5.956%
Drawdown
18.800%
Expectancy
0.255
Start Equity
100000
End Equity
270241.37
Net Profit
170.241%
Sharpe Ratio
0.338
Sortino Ratio
0.408
Probabilistic Sharpe Ratio
1.004%
Loss Rate
38%
Win Rate
62%
Profit-Loss Ratio
1.03
Alpha
0
Beta
0
Annual Standard Deviation
0.079
Annual Variance
0.006
Information Ratio
0.555
Tracking Error
0.079
Treynor Ratio
0
Total Fees
$4136.75
Estimated Strategy Capacity
$8400000.00
Lowest Capacity Asset
UUP TQBX2PUC67OL
Portfolio Turnover
4.45%
# https://quantpedia.com/strategies/seasonality-patterns-in-the-crisis-hedge-portfolios/
#
# The investment universe for this strategy consists of six ETFs that are considered to be crisis hedges. These include Invesco CurrencyShares Swiss Franc Trust (FXF), 
# Invesco CurrencyShares Japanese Yen Trust (FXY), Invesco DB US Dollar Index Bullish Fund (UUP), SPDR Gold Trust (GLD), iShares 7-10 Year Treasury Bond ETF (IEF), 
# and iShares 20+ Year Treasury Bond ETF (TLT). (As highlighted in the research paper, these instruments are selected based on their historical performance as effective
# hedges during market downturns. The selection is rooted in the Black Swan Hedging Model and the Antifragile Asset Allocation strategy, emphasizing resilience in 
# volatile markets.) (Data can be obtained from Yahoo Finance.)
# Variant Selection: This approach is based on the research paper’s findings that front-running seasonal signals outperform static strategies. The strategy utilizes 
# a front-running approach to seasonality, focusing on the performance of ETFs in the previous T-11 month as a predictor for the upcoming month.
# Advanced Instructions: The methodology involves calculating the returns for each ETF and ranking them based on their T-11 performance. Perform an inter-asset comparison
# within the portfolio, identifying the top and bottom performers within groups. Trading Rules: The buy rule is to go long on the top 2 ETFs with the highest returns 
# from the considered front-running period. Rebalancing & Weighting: The strategy involves monthly rebalancing to adjust positions based on the updated rankings of ETF 
# performance. The portfolio holds long positions in the top 2 ETFs, with equal capital allocated to each.

# region imports
from AlgorithmImports import * 
import pandas as pd
from dateutil.relativedelta import relativedelta
from pandas.core.frame import DataFrame
from typing import List
# endregion

class SeasonalityPatternsintheCrisisHedgePortfolios(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2008, 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] = ['FXF', 'FXY', 'UUP', 'GLD', 'IEF', 'TLT']
        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

        long: List[str] = []
        short: List[str] = []

        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