| Overall Statistics |
|
Total Orders 12 Average Win 2.65% Average Loss -0.87% Compounding Annual Return 0.396% Drawdown 4.700% Expectancy 1.521 Start Equity 100000 End Equity 110531.61 Net Profit 10.532% Sharpe Ratio -0.922 Sortino Ratio -0.121 Probabilistic Sharpe Ratio 0.000% Loss Rate 38% Win Rate 62% Profit-Loss Ratio 3.03 Alpha -0.02 Beta 0.014 Annual Standard Deviation 0.022 Annual Variance 0 Information Ratio -0.38 Tracking Error 0.159 Treynor Ratio -1.438 Total Fees $95.78 Estimated Strategy Capacity $990000.00 Lowest Capacity Asset IEF SGNKIKYGE9NP Portfolio Turnover 0.17% |
# https://quantpedia.com/strategies/front-running-rebalancing-signals/
#
# The investment universe for this strategy primarily consists of equity and bond markets, specifically focusing on the S&P 500 Index and the 10-year Treasury note.
# (The selection of these instruments is based on their representation of a typical balanced portfolio, such as the 60/40 equity/bond allocation commonly used by
# institutional investors. The strategy also considers futures contracts for these indices, as they provide liquidity and the ability to execute trades efficiently.
# The selection of individual instruments is guided by the predictable rebalancing activities of significant funds, which are known to impact these markets.)
# (Data Sources: Daily prices for the E-mini S&P 500 Index and the 10-year Treasury note from Bloomberg, also obtain daily index data for the S&P 500 Total Return
# Index, the Bloomberg U.S. Aggregate Bond Total Return Index, and international equities well as implied volatility measures from there too. Commodity Futures
# Trading Commission (CFTC) data, Federal Reserve’s Financial Accounts database, the National Center for Education Statistics.)
# Broad Overview: The strategy employs signals derived from rebalancing activities, specifically Threshold and Calendar signals. The Threshold signal is generated
# by monitoring deviations from target equity and bond allocations. For a 60/40 portfolio, deviations are calculated based on excess returns, and signals are
# triggered when these deviations exceed a predefined threshold, such as ±2.5%. The Calendar signal focuses on end-of-month effects, capturing patterns of
# rebalancing activities during the last week of each month. Buy and sell rules are straightforward: buy equities or bonds when they are underweight relative to
# target allocations and sell when they are overweight. The strategy anticipates price reversals by adjusting positions based on Calendar signals, particularly at
# month-end.
# Individual Signals: Subsection 2.2 describes the signal precisely, detailing how threshold and calendar signals are composed and combined.
# For the Threshold signal, consider regression eq. (1) where the dependent variable R_et_t+1 is the difference between S&P 500 and 10-year Treasury note futures
# returns and the independent variable Threshold Signal^δ_t.
# For the Calendar signal, consider regression eq. (3), which has the same dependent variable but a different independent variable Calendar Signal_t.
# Combined Strategy Variant: Perform rebalancing-based strategy R^Strategy_t constructed as described in Section 5:
# Build a simple, implementable, real-time trading strategy by combining Threshold and Calendar signals. This strategy simulates an investor’s actions who, based
# on rebalancing signals, enters the equity and bond markets as a front-runner. On average, this investor buys equities and sells bonds after bonds have relatively
# outperformed and buys bonds while selling equities after equities have outperformed.
# The trading strategy takes a position in an S&P 500 futures contract and an opposite position in a 10-year Treasury note futures contract as follows formula on
# pg. 32 (section 5 Front-Running Rebalancers), where the portfolio weight w-Strategy_t is defined as the average of modified versions of the Threshold and Calendar
# signals. The Threshold signal is rescaled to −(Threshold Signal_t / 1.5%) to ensure that both rebalancing signals contribute approximately equal risk to the
# trading strategy. Take the negative of the signal since a positive Threshold signal indicates that the S&P 500 is overweight relative to the 10-year Treasury note.
# While the Threshold strategy can take a position on any day of the month, the Calendar strategy focuses on the end-of-month effect. Therefore, the Calendar signal
# is modified to sign (−Calendar Signalt) if t falls within the last week of a month to capture the “week4” effect. Furthermore, on the first business day of a new
# month, the modified Calendar signal is set to sign Calendar Signal−4 to capture potential reversal effects. On any other day, the modified version of the signal
# is set to zero.
# Rebalancing & Weighting: Position sizes are determined as a fixed percentage of the portfolio’s total value, ensuring consistent exposure across trades. Rebalanced monthly.
#
# Implementation changes:
# - Excess returns predefined threshold is set to 2.5%
# - Portfolio allocation change is calculated 4 days before month end.
# region imports
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from pandas.core.frame import DataFrame
# endregion
class FrontRunningRebalancingSignals(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2000, 1, 1)
self.set_cash(100_000)
leverage: int = 4
days_offset: int = 4
self._threshold: float = .03
self._allocations: List[float] = [.6, .4]
self._spy: Symbol = self.add_equity("SPY", Resolution.MINUTE, leverage=leverage).symbol
self._ief: Symbol = self.add_equity("IEF", Resolution.MINUTE, leverage=leverage).symbol
self._rebalance_flag: bool = False
self._reverse_flag: bool = False
self.schedule.on( # Open position n days before month end.
self.date_rules.month_end(self._spy, days_offset),
self.time_rules.after_market_open(self._spy),
self._rebalance
)
self.schedule.on( # Reverse positions.
self.date_rules.month_start(self._spy),
self.time_rules.after_market_open(self._spy),
self._reverse
)
self.schedule.on( # Liquidate positions.
self.date_rules.month_start(self._spy, 1),
self.time_rules.after_market_open(self._spy),
lambda: self.liquidate()
)
def on_data(self, slice: Slice) -> None:
# Reverse position.
if self._reverse_flag:
self._reverse_flag = False
targets: List[PortfolioTarget] = []
for i, symbol in enumerate([self._spy, self._ief]):
if slice.contains_key(symbol) and slice[symbol]:
targets.append(PortfolioTarget(symbol, -1 * np.sign(self.portfolio[symbol].quantity)))
self.set_holdings(targets, True)
if not all(self.securities[symbol].get_last_data() for symbol in [self._spy, self._ief]):
self._rebalance_flag = False
self.log('insufficient data for signal determination.')
return
if not self._rebalance_flag:
return
self._rebalance_flag = False
# Last month performance.
history: DataFrame = self.history([self._spy, self._ief], start=self.time - relativedelta(months=1), end=self.time).unstack(level=0).resample('D').last()
history = history[history.index.month == self.time.month]
last_month_performance: DataFrame = history.close.iloc[-1] / history.close.iloc[0] - 1
if len(last_month_performance.dropna()) < 2:
return
spy_allocation, ief_allocation = self._allocations[0], self._allocations[1]
spy_returns: float = spy_allocation * (1 + last_month_performance[self._spy])
ief_returns: float = ief_allocation * (1 + last_month_performance[self._ief])
# Calculate new allocations.
spy_new_allocation: float = spy_returns / (spy_returns + ief_returns)
allocation_diff: float = spy_new_allocation - spy_allocation
# Threshold and trade execution.
if abs(allocation_diff) > self._threshold:
traded_directions: List[int] = [-1 * np.sign(allocation_diff), np.sign(allocation_diff)]
targets: List[PortfolioTarget] = []
for i, symbol in enumerate([self._spy, self._ief]):
if slice.contains_key(symbol) and slice[symbol]:
targets.append(PortfolioTarget(symbol, traded_directions[i]))
self.set_holdings(targets, True)
def _rebalance(self) -> None:
self._rebalance_flag = True
def _reverse(self) -> None:
if self.portfolio.invested:
self._reverse_flag = True