Overall Statistics
Total Orders
4060
Average Win
0.08%
Average Loss
-0.09%
Compounding Annual Return
0.215%
Drawdown
5.900%
Expectancy
0.013
Start Equity
100000
End Equity
102113.24
Net Profit
2.113%
Sharpe Ratio
-1.32
Sortino Ratio
-0.833
Probabilistic Sharpe Ratio
0.036%
Loss Rate
49%
Win Rate
51%
Profit-Loss Ratio
0.97
Alpha
-0.025
Beta
0.038
Annual Standard Deviation
0.017
Annual Variance
0
Information Ratio
-0.673
Tracking Error
0.143
Treynor Ratio
-0.575
Total Fees
$0.00
Estimated Strategy Capacity
$50000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
38.27%
# region imports
from AlgorithmImports import *

import random
from statsmodels.tsa.regime_switching.markov_regression import MarkovRegression
# endregion


class FearAndGreedExampleAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2015, 7, 12)
        self.set_end_date(2025, 4, 1)
        self.set_cash(100_000)
        # Set the random seed.
        random.seed(42)
        # Set the flag of whether or not to use the regime filter.
        self._filter = True # False 
        self._probability_of_trade = 0.01  # per minute
        # Add the Fear and Greed Index.
        self._index = self.add_data(FearGreedIndex, 'FG')
        self._index.history = self.history(self._index.symbol, datetime(2014, 7, 1), self.time).loc[self._index.symbol].qcindex
        self._index.regime = None
        # Add the SPY to trade.
        self._equity = self.add_equity('SPY')
        self._equity.set_fee_model(ConstantFeeModel(0)) # Remove fees.
        # Liquidate positions at market close.
        self.schedule.on(self.date_rules.every_day(self._equity.symbol), self.time_rules.before_market_close(self._equity.symbol, 1), self.liquidate)
    
    def on_data(self, data: Slice) -> None:
        if self._equity.symbol in data and self._equity.exchange.hours.is_open(self.time + timedelta(minutes=1), False):
            # Check if we should trade.
            trade = random.random() < self._probability_of_trade
            if not trade:
                return
            if self._filter and self._index.regime == 1: # If filter is on, only trade during the fear regime.
                return
            self.market_order(self._equity.symbol, 100 if not self.portfolio.invested else -100)
        # Wait until the Fear and Greed dataset has new data.
        if self._index.symbol not in data:
            return
        self._index.history.loc[self.time] = self._index.close
        # Fit the HMM and detect the current regime.
        regimes = pd.Series(
            MarkovRegression(self._index.history, k_regimes=2).fit().smoothed_marginal_probabilities.values.argmax(axis=1), 
            index=self._index.history.index
        )
        # Sanity check: Ensure that regime 0=fear and 1=greed
        self.plot('Regimes Mean', 'Fear', self._index.history[regimes[regimes == 0].index].mean())
        self.plot('Regimes Mean', 'Greed', self._index.history[regimes[regimes == 1].index].mean())
        # Save the current regime.
        self._index.regime = regimes.iloc[-1]
        self.plot('Regime', 'Current', self._index.regime)