# Markowitz Approach to Managing a Dynamic Basket of MBSAs ## Introduction This note outlines a Markowitz optimization approach tailored for managing a dynamic basket of Moving-Band Statistical Arbitrages (MBSAs). The key aspects covered include: * **Multiple MBSAs:** Handling a collection of MBSAs that evolves over time. * **Portfolio Construction:** Defining and managing both asset-level and MBSA-level holdings. * **Costs:** Incorporating trading costs and holding costs. * **Markowitz Optimization:** Formulating the optimization problem to maximize returns while managing risk and costs. * **Implementation:** Briefly discuss code implementation highlights. * **Results:** Showcase the performance and analysis of the strategy. ## Representing Multiple MBSAs We work with a collection of active Moving-Band Statistical Arbitrages (MBSAs) that dynamically changes over time. New MBSAs are discovered and added to the basket, while existing ones might be removed as market conditions evolve. * **Number of MBSAs:** $K_t$ represents the number of active MBSAs at a given time period $t$. * **MBSA Definition:** Each MBSA, indexed by $k$ ($1 \leq k \leq K_t$), is defined by: * **Holdings:** A vector $s^{(k)} \in \mathbb{R}^n$ representing the asset holdings within the MBSA. * **Price:** $p^{(k)}_t = s^{(k)} P_t$, calculated based on the holdings and current asset prices $P_t$. * **Alpha:** $\alpha^{(k)}_t = \mu^{(k)}_t - p^{(k)}_t$, the difference between the MBSA's moving midpoint $\mu^{(k)}_t$ and its price. To facilitate analysis, we organize the holdings of all active MBSAs into a matrix: * **Holdings Matrix $S_t = [s^{(1)} \cdots s^{(K_t)}]$:** * Dimensions: $n \times K_t$ * Each column represents the holdings of a single MBSA. We also introduce convenient vectors to track key MBSA attributes: * **Price Vector $p_t = (p_t^{(1)}, \dots, p_t^{(K_t)})$:** Contains the prices of all active MBSAs. * **Moving Midpoint Vector $\mu_t = (\mu_t^{(1)}, \dots, \mu_t^{(K_t)})$, where $\mu^{(k)}_t$:** Contains the moving midpoints of all active MBSAs. * **Alpha Vector $\alpha_t = (\alpha_t^{(1)}, \dots, \alpha_t^{(K_t)})$:** Contains the alphas of all active MBSAs. This notation allows us to seamlessly extend the concepts from a single MBSA to a dynamic basket of multiple MBSAs. ## Portfolio Management ### Portfolio Holdings * **Arb-level Holdings $q_t$:** A vector in $\mathbb{R}^{K_t}$ representing the portfolio's holdings in each active MBSA at time $t$. The units are considered "shares" of each MBSA. * **Asset-level Holdings $h_t$:** A vector in $\mathbb{R}^n$ derived from the MBSA holdings $q_t$ and the transformation matrix $S_t$: $$h_t = S_t q_t$$ This represents the actual number of shares held in each individual asset. * In USD terms, the asset-level holdings are valued as: $P_t \circ (S_t q_t)$, where $\circ$ is the element-wise (Hadamard) product and $P_t$ is the vector of asset prices. * **Cash Account $c_t$:** The amount of cash held in USD. We assume $c_t > 0$ to ensure we have collateral for any short positions and avoid borrowing cash. * **Total Portfolio Value:** The total value of the portfolio at time $t$, calculated as $p_t^T q_t + c_t$ in USD. #### Transaction Costs * **Trades Vector $z_t$:** Represents the change in asset-level holdings between time $t-1$ and $t$: $$z_t = h_t - h_{t-1}$$ * **Trading Cost:** The cost incurred due to trading activities, calculated as: $$(\kappa^{trade}_t)^T |z_t|$$ where: * $\kappa^{trade}_t \in \mathbb{R}^n_+$ is a vector containing half the bid-ask spread for each asset at time $t$ (in USD per share). * $|z_t|$ is the element-wise absolute value of the trades vector. #### Holding Costs * **Holding Cost:** The cost associated with maintaining short positions, calculated as: $$(\kappa^{short}_t)^T (-h)_+$$ where: * $\kappa^{short}_t \in \mathbb{R}^n$ is a vector of shorting rates for each asset over period t (in USD per share per period). * $(u)_+ = \max\{u,0\}$ is the element-wise non-negative part of a vector, ensuring we only consider costs for short positions (negative holdings). ## Markowitz Optimization Model We employ a Markowitz optimization framework to manage our dynamic basket of MBSAs. This approach aims to maximize the portfolio's expected return, adjusted for transaction and holding costs, while adhering to risk and portfolio construction constraints. ### Assumptions * **Known Information at Time $t$:** * Current asset prices $P_t$ * Arb-to-asset transformation matrix $S_t$ * Previous portfolio holdings $h_{t-1}$, $q_{t-1}$ and cash value $c_{t-1}$ ### Goal * Determine the optimal new portfolio holdings: * Asset-level holdings $h_t$ * $q$: Candidate MBSA-level holdings. * $c$: Candidate cash allocation. ### Objective Function The objective function seeks to maximize the portfolio's exposure to alpha (expected excess return) while accounting for the costs associated with trading and shorting: \begin{split} \text{maximize}\quad& \alpha^T_tq-\gamma^{trade}(\kappa^{trade}_t)^T(h-h_{t-1})-\gamma^{short}(\kappa^{short}_t)^T(-h)_+\\ \text{subject to}\quad& h=S_tq, \quad c\geq(\eta-1)P^T_th_-,\quad c=c_{t-1}+P^T_tq_{t-1}\\ &P^T_tq=0,\quad |q|_k(p_t)_k\leq \xi^{(k)}_tc, \quad k=1,\cdots,K,\\ &\|\Sigma^{\frac{1}{2}}_tq\|_2\leq \sigma_{tar} c, \end{split} where: * $\alpha_t$: Vector of alphas for the active MBSAs at time $t$. * $\gamma^{trade}$, $\gamma^{short}$: Positive parameters that control the trade-off between alpha exposure and transaction/holding costs. * $\kappa^{trade}_t$: Vector of one-half the bid-ask spreads for each asset at time $t$. * $\kappa^{short}_t$: Vector of shorting rates for each asset over period $t$. * $h - h_{t-1}$: Vector representing the changes in asset holdings (trades). * $(-h)_+$: Element-wise non-negative part of $-h$, capturing short positions. **Note:** The true values of $\kappa^{trade}_t$ and $\kappa^{short}_t$ are unknown at time t and are estimated from historical data. ### Constraints The optimization is subject to several constraints to ensure the portfolio aligns with our investment goals and risk tolerance. #### Cash Neutrality The portfolio is constrained to be cash-neutral: $$ p^T_t q = 0 $$ This implies that the total market value of the MBSA holdings is zero, and the total portfolio value is equal to the cash account value. This constraint is common in long-short strategies. #### Collateral Constraint We enforce a collateral constraint to ensure sufficient cash to cover potential losses from short positions: $$ c \geq (\eta-1) P^T_t(h)_- $$ where: * $\eta ≥ 1$: Collateral parameter, typically set to 2.02 to maintain a 102% collateralization ratio. * $(h)_-$: Element-wise non-positive part of h, representing short positions. This constraint guarantees that the cash account holds at least $\eta-1$ times the value of the short positions. #### MBSA Size Limit Constraints To avoid excessive concentration and facilitate the gradual decommissioning of MBSAs, we impose size limits: $$ |q|_k (p_t)_k \leq \xi_t^{(k)}c, \quad k=1,\dots,K_t $$ where: * $|q|_k$: Absolute value of the holding in the k-th MBSA * $(p_t)_k$: Price of the k-th MBSA * $\xi_t^{(k)}$: A time-varying parameter that controls the maximum allowed size of the k-th MBSA as a fraction of the total portfolio value $c$. #### Risk Constraint We employ a custom risk measure that focuses on the fluctuation of the portfolio value around its expected midpoint: $$ ||\Sigma_t^{1/2} q||_2 \leq \sigma_{tar} c, $$ where: * $\Sigma_t$: Covariance matrix representing the short-term fluctuations of MBSA prices. * $\sigma_{tar} > 0$: Target risk parameter (unitless), controlling the acceptable level of risk as a fraction of the portfolio value. This constraint is a second-order cone (SOC) constraint, ensuring the portfolio's risk remains within the desired bounds. ## Numerical Experiments ### Experimental Setup #### Data The dataset used for MBSAs remains consistent with previous experiments. Please refer to relevant sections or accompanying documentation for further information about this dataset. #### Dynamic MBSA Management * **Portfolio Rebalancing:** The Markowitz optimization problem is solved daily to adjust the portfolio allocation in response to evolving market conditions. * **MBSA Decommissioning:** The MBSA size limit parameter $\xi$ is linearly reduced to zero over a 21 trading day period, allowing for the gradual removal of less profitable or relevant MBSAs. #### Risk Modeling * **Covariance Matrix Decomposition:** The covariance matrix $\Sigma_t$ is decomposed into: * $S_t$: The arb-to-asset transformation matrix, representing the relationship between MBSAs and underlying assets * **$\Sigma^P_t$:** The short-term covariance matrix of asset prices * **IEWMA for Covariance Estimation:** The $\Sigma^P_t$ matrix is estimated using the Iterated Exponentially Weighted Moving Average (IEWMA) method. * **Centered Prices:** Prices are centered by subtracting their 21-day rolling mean to focus on short-term fluctuations. * **IEWMA Parameters:** * Volatility Estimation: 125-day half-life * Correlation Estimation: 250-day half-life * **Covariance Smoothing:** An additional 250-day half-life EWMA smoothing is applied to the estimated covariances to reduce unnecessary trading triggered by noise in the risk model. ```python= def compute_covariance(prices, volatility_halflife=125, correlation_halflife=250, smoothing_halflife=250): # Center prices prices_centered = prices - prices.rolling(21).mean() # Compute IEWMA def iewma(x, halflife): return x.ewm(halflife=halflife, adjust=False).mean() vol = iewma(prices_centered ** 2, volatility_halflife) ** 0.5 corr = iewma(prices_centered.div(vol, axis=1), correlation_halflife) # Compute covariance cov = corr.mul(vol, axis=0).mul(vol, axis=1) # Apply additional smoothing cov_smooth = iewma(cov, smoothing_halflife) return cov_smooth # Compute covariance for each time step covariances = {t: compute_covariance(prices_full.loc[:t]) for t in prices_full.index} ``` #### Parameter Settings * $\gamma^{trade} = 1$ * $\eta = 1$ * $\xi = 1$ * $\sigma_{tar} = 10%$ * Shorting cost = 0.5% / 252 (annualized) ### Implementation: Core Functions and Backtesting Workflow The provided Python code implements a dynamic MBSA (Moving-Band Statistical Arbitrage) strategy for portfolio management. Let's break down the key classes, functions, and the backtesting process involved. #### Key Classes and Functions ##### `StatArbManager` * **Purpose:** The central engine for managing the basket of MBSAs (Moving-Band Statistical Arbitrages) over time. * **Key Functionality:** * `update(times, stat_arbs_new)`: Updates the active MBSAs by appending new ones to the existing collection, ensuring continuity in the basket. * `size_stat_arbs(...)`: Calculates optimal holdings for each MBSA in the portfolio, considering risk, cost, and size constraints using Markowitz optimization. * `build()`: Constructs the final portfolio by aggregating asset holdings across all time periods. * `reset()`: Clears all internal data for a new backtest or analysis. ```python= class StatArbManager: def __init__(self): self.sized_stat_arbs = {} self.active_stat_arbs = {} self.positions = {} self.filtered_stat_arbs = {} def update(self, times, stat_arbs_new): # Update the collection of active MBSAs def size_stat_arbs(self, time, h_prev, prices, covariance, risk_limit, active_stat_arbs, tradeable_assets, eta, xi, kappa_spread, portfolio_value): # Determine optimal holdings for each MBSA def build(self): # Construct the final portfolio def reset(self): # Clear all internal data structures ``` ##### `run_portfolio_backtest` * **Purpose:** The main function that orchestrates the entire backtesting process. * **Key Functionality:** * **Initializes:** Sets up the `StatArbManager` and `Builder` to handle MBSA management and portfolio tracking. * **Main Loop:** * **Periodically Identifies New Stat Arbs:** Every `update_freq` days (default 21), new MBSAs are identified and added to the `StatArbManager`. * `Daily Portfolio Update:` Rebalances the portfolio using Markowitz optimization, considering risk, costs, and MBSA constraints. Updates the `Builder` with the new portfolio. * **Output:** Returns various results like identified MBSAs, the final portfolio, and performance metrics. ```python= def run_portfolio_backtest( prices, stock_lists, spreads, covariances, P_max, moving_midpoint, midpoint_memory, T_max, shorting_cost ): # Initialize StatArbManager and Builder manager = StatArbManager() b = Builder(prices=prices, initial_aum=1) # Main loop for iteration, (t, state) in enumerate(tqdm(b, total=len(all_times))): # Identify new stat arbs (periodic) if iteration % update_freq == 0: new_stat_arb_results, _ = _find_and_filter_stat_arbs(...) manager.update(prices.index[time : time + update_freq], new_stat_arbs) # Update portfolio (daily) quantities_new, weights_new, stat_arb_quantities, holding_cost = _update_portfolio(...) # Update metrics and Builder b.position = quantities_new b.aum = state['aum'] # Build final portfolio portfolio = b.build() return (results, manager, portfolio, holdings, all_weights, all_stat_arb_quantities, values, cashs, interest_and_fees, trading_costs) ``` ##### `_update_portfolio` * **Purpose:** Handles the core portfolio rebalancing logic. * **Key Functionality:** * **Calculates Portfolio Value:** Computes the current portfolio value, including cash and asset holdings. * **Sizes Stat Arbs:** Calls the StatArbManager to determine optimal MBSA holdings. * **Updates State:** Updates the `StatArbManager` and `Builder` with new positions and performance metrics. ```python= def _update_portfolio( manager, state, quantities_prev, prices, time, risk_limit, eta, xi, kappa_spread, shorting_cost, covariance, active_stat_arbs, tradeable_assets, interest_and_fees, trading_costs ): # Calculate portfolio value and previous weights cash = state['cash'] + interest_and_fees.sum() - trading_costs.sum() portfolio_value = cash + state['value'] weights_prev = quantities_prev / portfolio_value # Size stat arbs and update portfolio quantities_new, weights_new, stat_arb_quantities, _, active_stat_arbs_filtered = manager.size_stat_arbs(...) # Calculate trading costs and holding costs trades = quantities_new - quantities_prev trade_cost = compute_trading_costs(trades, spreads.loc[date_time]) short_pos = quantities_prev.clip(upper=0).abs() holding_cost = (short_pos @ latest_prices[short_pos.index]) * shorting_cost return quantities_new, weights_new, stat_arb_quantities, holding_cost ``` ##### `_get_tradable_assets` * **Purpose:** Filters out assets that are not eligible for trading (e.g., due to lack of recent price activity or missing covariance data). ```python= def _get_tradable_assets(prices_recent, covariance): asset_names = prices_recent.columns tradeable_assets = [ asset for asset in asset_names if (prices_recent[asset].max() != prices_recent[asset].min()) and (not prices_recent[asset].isnull().any()) and (asset in covariance.columns) and (not covariance[asset].isnull().all()) and (not (covariance[asset] == 0).all()) ] non_tradeable_assets = list(set(asset_names) - set(tradeable_assets)) return tradeable_assets, non_tradeable_assets ``` #### Backtesting Process 1. Initialization: The `StatArbManager` and `Builder` are set up. 2. **Main Loop:** * The code iterates over each time period in the backtest. * **Periodically:** New MBSAs are identified and added to the `StatArbManager`. * **Daily:** The portfolio is rebalanced using Markowitz optimization. Costs (trading and holding) are calculated. The `Builder` is updated with the new portfolio state. 3. **Results:** Once the backtest is complete, the function returns various performance metrics, the final portfolio constructed by the `Builder`, and other data collected during the process. **Note:** The code snippet also references the `_find_and_filter_stat_arbs` function, which is defined in defined in the previous note. ### Results and Analysis A comprehensive backtest was conducted using the `run_portfolio_backtest` function to rigorously assess the efficacy of the dynamic MBSA strategy. The subsequent analysis delves into various facets of the strategy's performance, encompassing overall metrics, turnover, comparative analysis with the market benchmark, and correlation dynamics. #### Backtest Setup ```python= shorting_cost = 0.5 / 100 / 252 # Annualized shorting cost results = run_portfolio_backtest( prices_full, stocks_list, spread, covariances, P_max=100, moving_midpoint=True, midpoint_memory=21, T_max=500, shorting_cost=shorting_cost ) (manager, portfolio, holdings, all_weights, all_stat_arb_quantities, values, cashs, interest_and_fees, trading_costs) = results ``` #### Performance Metrics The strategy's performance is meticulously dissected, both before and after incorporating the impact of costs. ```python= def analyze_performance(nav, costs=None): if costs is not None: nav = nav + costs.cumsum() rets = nav.pct_change().dropna() mean = rets.mean() * 252 std = rets.std() * np.sqrt(252) sharpe = mean / std max_drawdown = (nav.cummax() - nav).max() / nav.cummax().max() return { "Return": f"{mean:.2%}", "Risk": f"{std:.2%}", "Sharpe": f"{sharpe:.2f}", "Drawdown": f"{max_drawdown:.2%}" } nav_bf = portfolio.nav.dropna() nav_af = nav_bf + (interest_and_fees.fillna(0) - trading_costs.fillna(0)).cumsum() print("Performance Before Costs:") print(analyze_performance(nav_bf)) print("\nPerformance After Costs:") print(analyze_performance(nav_af)) ``` ``` Performance Before Costs: Return: 13.42% Risk: 12.67% Sharpe: 1.06 Drawdown: 14.38% ``` ``` Performance After Costs: Return: 10.2% Risk: 11.48% Sharpe: 0.89 Drawdown: 15.29% ``` **Key Insight:** The inclusion of trading and holding costs exerts a substantial influence on the strategy's overall performance. The annualized return experiences a decline from 13.42% to 10.20%, accompanied by a reduction in the Sharpe ratio from 1.06 to 0.89. This underscores the criticality of astute cost management in practical implementations of the strategy. #### Turnover Analysis A hallmark of numerous statistical arbitrage strategies is their propensity for high turnover. We proceed to compute and visualize the turnover inherent to the implemented strategy. ```python= def calculate_turnover(weights): return 0.5 * weights.diff().abs().sum(axis=1).resample("YE").mean() * 250 weights = portfolio.weights.dropna() turnover = calculate_turnover(weights) print(f"\nMean annual turnover: {turnover.mean():.0f}%") plt.figure(figsize=(10, 6)) turnover.plot() plt.ylabel("Turnover") plt.gca().yaxis.set_major_formatter(FuncFormatter(lambda y, _: f"{y:.0f}%")) plt.tight_layout() plt.show() ``` ``` Mean annual turnover: 134 ``` ![image](https://hackmd.io/_uploads/Hy-IxN7cA.png) **Key Insight:** The mean annual turnover clocks in at a substantial 134%. This elevated turnover aligns seamlessly with the quintessential nature of statistical arbitrage strategies, which necessitate frequent rebalancing to adeptly capitalize on fleeting price inefficiencies in the market. #### Comparison with Market Benchmark (S&P 500) To gauge the strategy's risk-adjusted performance and diversification potential, a comparative analysis is undertaken, juxtaposing its performance with that of the S&P 500 index. ```python= def compare_with_market(portfolio_returns, market_returns): merged_index = portfolio_returns.index.intersection(market_returns.index) portfolio_returns = portfolio_returns.loc[merged_index] market_returns = market_returns.loc[merged_index] active_returns = portfolio_returns - market_returns mean_active = active_returns.mean() * 252 std_active = active_returns.std() * np.sqrt(252) reg = LinearRegression(fit_intercept=False) reg.fit(market_returns.values.reshape(-1, 1), portfolio_returns.values.reshape(-1, 1)) residual = portfolio_returns - reg.predict(market_returns.values.reshape(-1, 1)).flatten() alpha = residual.mean() * 252 residual_risk = residual.std() * np.sqrt(252) market_beta = reg.coef_[0, 0] return { "Active Return": f"{mean_active:.2%}", "Active Risk": f"{std_active:.2%}", "Alpha": f"{alpha:.2%}", "Residual Risk": f"{residual_risk:.2%}", "Information Ratio": f"{alpha / residual_risk:.2f}", "Market Beta": f"{market_beta:.3f}" } sp500 = yf.Ticker("^GSPC").history(start="2014-07-31", end="2024-07-31")["Close"].pct_change().dropna() sp500.index = sp500.index.tz_localize(None) portfolio_returns = nav_af.pct_change().dropna() print("\nComparison with Market:") print(compare_with_market(portfolio_returns, sp500)) fig, axes = plt.subplots(3, 1, figsize=(10, 18)) axes[0].scatter(sp500, portfolio_returns, alpha=0.5) axes[0].set_xlabel("Market Returns") axes[0].set_ylabel("Portfolio Returns") axes[0].set_title("Returns Comparison") (1 + portfolio_returns).cumprod().plot(ax=axes[1], label="Portfolio") (1 + sp500).cumprod().plot(ax=axes[1], label="Market") axes[1].set_ylabel("Cumulative Returns") axes[1].legend() axes[1].set_title("Cumulative Returns Comparison") portfolio_returns.rolling(window=250).cov(sp500) / sp500.rolling(window=250).var().plot(ax=axes[2]) axes[2].set_ylabel("Rolling Beta") axes[2].set_title("Rolling 250-day Beta") plt.tight_layout() plt.show() ``` ``` Comparison with Market: Active return: -0.43% Active risk: 20.65% Alpha (residual return): 11.25% Residual risk: 12.70% Information ratio: 0.89 Market beta: 0.121 ``` ![image](https://hackmd.io/_uploads/Hy3xYuhcR.png) ![image](https://hackmd.io/_uploads/H1l8ZKun5R.png) ![image](https://hackmd.io/_uploads/HJobYO39C.png) **Key Insight:** While the MBSA portfolio trails the S&P 500 in terms of absolute returns, it showcases a pronounced superiority in risk-adjusted performance. This is eloquently evidenced by its positive alpha (11.25%), markedly low market beta (0.121), and a favorable information ratio of 0.89. #### NAV Evolution A visual comparison is drawn between the NAV (Net Asset Value) evolution of the strategy and the market, with the latter scaled to match the volatility of the former. ```python= plt.figure(figsize=(10, 6)) (1 + portfolio_returns).cumprod().plot(label="Portfolio of MBSAs") (1 + sp500 * portfolio_returns.std() / sp500.std()).cumprod().plot(label="Market (scaled)") plt.legend() plt.yscale("log") plt.ylabel("NAV") plt.gca().yaxis.set_major_formatter(ScalarFormatter(useOffset=False, useMathText=True)) plt.gca().yaxis.set_major_locator(MaxNLocator(integer=True)) plt.tight_layout() plt.show() ``` ![image](https://hackmd.io/_uploads/SkyEY_ncA.png) | Metrics | Sharpe | Drawdown | | -------- | -------- | -------- | After-costs Performance |0.89|15.29% | Market Performance(scaled to same volatility as MBSAs) |0.72|33.92% **Key Insight:** When subjected to an equitable volatility adjustment, the MBSA portfolio not only exhibits a superior Sharpe ratio but also boasts a drastically lower maximum drawdown in comparison to the S&P 500. #### Correlation Analysis The correlation dynamics between the strategy and the market are scrutinized over time. ```python= ewma_correlations = portfolio_returns.rolling(window=250).corr(sp500) plt.figure(figsize=(10, 6)) ewma_correlations.plot() plt.ylabel("EWMA Correlation") plt.gca().yaxis.set_major_formatter(FuncFormatter(lambda y, _: f"{100*y:.0f}%")) plt.tight_layout() plt.show() ``` ![image](https://hackmd.io/_uploads/HypM2FnqA.png) **Key Insight:** The persistently low EWMA (Exponentially Weighted Moving Average) correlation observed between the portfolio and the market emphatically underscores the strategy's diversification merits and its inherent capacity to mitigate market risk. #### Annual Performance The annual performance metrics of the strategy are evaluated and contrasted with those of the market. ```python= def plot_annual_metrics(portfolio_returns, market_returns): yearly_returns = portfolio_returns.resample("YE").mean() * 252 yearly_returns_market = market_returns.resample("YE").mean() * 252 yearly_volatility = portfolio_returns.resample("YE").std() * np.sqrt(252) yearly_volatility_market = market_returns.resample("YE").std() * np.sqrt(252) yearly_sharpe = yearly_returns / yearly_volatility yearly_sharpe_market = yearly_returns_market / yearly_volatility_market fig, axes = plt.subplots(3, 1, figsize=(20, 18)) for ax, data, title in zip(axes, [(yearly_returns, yearly_returns_market), (yearly_volatility, yearly_volatility_market), (yearly_sharpe, yearly_sharpe_market)], ["Return", "Volatility", "Sharpe Ratio"]): ax.plot(data[0], marker="o", label="Portfolio") ax.plot(data[1], marker="o", label="Market") ax.set_ylabel(title) ax.legend() ax.xaxis.set_major_locator(MaxNLocator(integer=True)) plt.tight_layout() plt.show() plot_annual_metrics(portfolio_returns, sp500) ``` ![image](https://hackmd.io/_uploads/S1ldd4Xs0.png) **Key Insight:** The MBSA portfolio consistently delivers annual returns and Sharpe ratios that are on par with, if not superior to, the S&P 500, while remarkably exhibiting lower volatility across the majority of the years under consideration. This serves as a testament to the strategy's adept risk management capabilities. ### Key Insights and Conclusion #### Key Observations The backtest and analysis of the dynamic MBSA strategy yielded several noteworthy findings: 1. **Impact of Costs:** While the strategy delivers impressive returns before costs, trading and holding costs significantly impact performance. The annualized return decreases from 13.42% to 10.20% after accounting for costs, with a corresponding drop in the Sharpe ratio from 1.06 to 0.89. This emphasizes the need for careful cost management in real-world implementation. 2. **High Turnover:** Consistent with the short-term nature of statistical arbitrage, the strategy exhibits a high mean annual turnover of 134%. This frequent rebalancing is essential to capitalize on transient market inefficiencies. 3. **Risk-Adjusted Performance:** Despite underperforming the S&P 500 in absolute returns, the MBSA portfolio shines in terms of risk-adjusted performance. Its positive alpha of 11.25%, low market beta of 0.121, and favorable information ratio of 0.89 highlight its ability to generate returns not explained by the broader market while maintaining lower risk. 4. **Diversification Benefits:** The consistently low correlation (generally below 20%) between the portfolio and the market underscores its potential for diversification, reducing overall portfolio risk. 5. **Volatility Management:** The strategy showcases effective volatility management, exhibiting lower volatility than the S&P 500 in most years. Even when scaled to match market volatility, it maintains a higher Sharpe ratio and experiences a significantly lower maximum drawdown. 6. **Annual Performance:** The MBSA portfolio consistently delivers annual returns and Sharpe ratios comparable to the S&P 500 while demonstrating notably lower volatility in most years, further emphasizing its focus on risk management. #### Conclusion The dynamic MBSA strategy emerges as a promising approach for investors seeking enhanced portfolio diversification and risk-adjusted returns. Its ability to generate alpha while maintaining low correlation with the market makes it a valuable tool for mitigating market risk. The strategy's dynamic nature, evidenced by its high turnover, allows it to adapt to evolving market conditions and continually uncover new arbitrage opportunities. Furthermore, its resilience to costs, even with the observed impact on performance, underscores its potential for generating attractive returns in real-world scenarios. However, future research should explore avenues for optimizing trading costs, given the strategy's high turnover. Additionally, assessing the strategy's scalability and its performance under diverse market regimes would provide deeper insights into its potential and limitations. A comprehensive sensitivity analysis of key parameters could further refine its implementation. Overall, the dynamic MBSA strategy presents a compelling investment proposition, particularly for those seeking alpha generation and risk mitigation within a diversified portfolio framework. ## Reference * K. Johansson, T. Schmelzer, and S. Boyd, Finding Moving-Band Statistical Arbitrages via Convex-Concave Optimization * K. Johansson, T. Schmelzer, and S. Boyd, A Markowitz Approach to Managing a Dynamic Basket of Moving-Band Statistical Arbitrages * [cvxstatarb](https://github.com/cvxgrp/cvxstatarb/tree/main)