Overall Statistics |
Total Orders 236 Average Win 2.89% Average Loss -1.78% Compounding Annual Return 5.698% Drawdown 33.600% Expectancy 0.721 Start Equity 100000 End Equity 403785.13 Net Profit 303.785% Sharpe Ratio 0.216 Sortino Ratio 0.139 Probabilistic Sharpe Ratio 0.031% Loss Rate 34% Win Rate 66% Profit-Loss Ratio 1.63 Alpha 0.005 Beta 0.407 Annual Standard Deviation 0.101 Annual Variance 0.01 Information Ratio -0.167 Tracking Error 0.122 Treynor Ratio 0.054 Total Fees $1897.71 Estimated Strategy Capacity $1400000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X Portfolio Turnover 2.29% |
# https://quantpedia.com/strategies/time-series-reversal-in-sp-500-using-eom-signal/ # # This strategy’s investment universe focuses on major U.S. stock indices, specifically the S&P 500. Individual instruments are selected based on their inclusion in # these indices, which consist of highly capitalized and liquid American companies. (The approach can also include the Dow Jones Industrial Average, as the research # paper suggests that the reversal pattern is evident in these indices. The strategy does not apply to smaller indices like the Russell 2000, as the reversal pattern # does not appear to exist.) # (You can replicate via SPY and VOO ETFs or CFDs.) # (Data for closing prices is from the Global Financial Data (GFD).) # Strategy Rationale Recapitulation: The trading rules are based on the negative correlation between end-of-the-month returns and the one-month-ahead returns. The primary # tool calculates the end-of-the-month return, defined as the return from the fourth Friday to the month’s last trading day. The methodology generates a buy signal if the # end-of-the-month return is negative, indicating a potential upward reversal in the following month. Conversely, a sell signal is generated if the positive end-of-the-month # return suggests a potential downward reversal. The strategy does not rely on complex indicators but on this straightforward rule derived from the observed pattern. # Our Variant Selection: Long-only. # Trading rule (pg. 10): Buying units of the S&P 500 if the end-of-the-month return is negative. (Liquidate position at next EOM.) # Else, stay in cash (hold risk-free asset). # Rebalancing & Weighting: Performed every month. Always invest the entire amount allocated to the strategy only to one asset at a time. # # QC Implementation changes: # - End-of-the-month returns are calculated as the return from the day t-3 of the month to the month’s last trading day. # region imports from AlgorithmImports import * from pandas.core.frame import DataFrame # endregion class TimeSeriesReversalInSP500UsingEOMSignal(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2000, 1, 1) self.set_cash(100_000) self._history_period: int = 10 self._observed_day: int = 4 leverage: int = 4 # Calculate returns from last friday in month. self._last_friday_flag: bool = False if self._last_friday_flag: self._observed_day = 4 self._spy: Symbol = self.add_equity("SPY", Resolution.DAILY, leverage=leverage).symbol self._bil: Symbol = self.add_equity("BIL", Resolution.DAILY, leverage=leverage).symbol self._rebalance_flag: bool = False self.schedule.on( self.date_rules.month_end(self._spy), self.time_rules.before_market_close(self._spy), self._rebalance ) def on_data(self, slice: Slice) -> None: if not self._rebalance_flag: return self._rebalance_flag = False history: DataFrame = self.history(self._spy, timedelta(days=self._history_period)).unstack(level=0) if history.empty: return if self._last_friday_flag: observed_day: int = self._observed_day observed_day_price_df: DataFrame = history.loc[history.index.weekday == self._observed_day, 'close'] # When friday is not trading day. while observed_day_price_df.empty: observed_day -= 1 observed_day_price_df = history.loc[history.index.weekday == observed_day, 'close'] observed_day_price: float = observed_day_price_df.values[0][0] else: observed_day_price = history.close.iloc[-self._observed_day].values[0] traded_asset: Symbol = self._spy if (history.close.iloc[-1].values[0] / observed_day_price - 1) < 0 else self._bil self.set_holdings(traded_asset, 1, True) def _rebalance(self) -> None: if all(self.securities[symbol].get_last_data() for symbol in [self._spy, self._bil]): self._rebalance_flag = True