VMOT Trend Hedging with 12-Month Moving Average and Return Rules
Summary
This note describes the trend sleeve associated with the Alpha Architect Value/Momentum/Trend ETF. It applies two long-horizon conditions to a US ETF: reduce exposure by half when the price falls below its 12-month average, and reduce it by another half when the trailing 12-month return is negative. If both conditions hold, the resulting target is fully hedged; if neither holds, exposure remains unhedged.
The implementation calculates these tests from daily closes using 252 trading days, updates target weights weekly, and shifts positions to reflect delayed entry. It models close-to-close returns and specifies market-on-close orders. The document provides implementation details but no performance results, comparison, or evidence that the rules improve returns. It also does not explain how the selected ETF represents the broader market or discuss behavior across other assets and market regimes, so the strategy should be understood as a rules-based hedge example rather than a demonstrated source of alpha.
Key ideas
- The strategy halves exposure when price is below its 12-month moving average.
- A second 50% reduction applies when the trailing 12-month return is negative.
- Both conditions together produce a fully hedged target position.
- Signals are sampled weekly, while the rules use 252 trading days of price history.
- The source describes implementation but reports no backtest results.
Tags
Full text
# VMOTTrend
# VMOTTrend
Hedging strategy that sells the market based on 2 trend rules:
1. Sell 50% if market price is below 12-month moving average
2. Sell 50% if market 12-month return is below 0
This strategy constitutes the "Trend" portion of the Alpha Architect
Value/Momentum/Trend (VMOT) ETF.
## Source (Apache-2.0)
```python
# Copyright QuantRocket LLC - All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pandas as pd
from moonshot import Moonshot
from moonshot.commission import PerShareCommission
class USStockCommission(PerShareCommission):
BROKER_COMMISSION_PER_SHARE = 0.005
class VMOTTrend(Moonshot):
"""
Hedging strategy that sells the market based on 2 trend rules:
1. Sell 50% if market price is below 12-month moving average
2. Sell 50% if market 12-month return is below 0
This strategy constitutes the "Trend" portion of the Alpha Architect
Value/Momentum/Trend (VMOT) ETF.
"""
CODE = "vmot-trend"
DB = "sharadar-us-etf-1d"
SIDS = "FIBBG000BDTBL9"
REBALANCE_INTERVAL = "W"
COMMISSION_CLASS = USStockCommission
def prices_to_signals(self, prices: pd.DataFrame):
closes = prices.loc["Close"]
one_year_returns = (closes - closes.shift(252))/closes.shift(252)
market_below_zero = one_year_returns < 0
mavgs = closes.rolling(window=252).mean()
market_below_mavg = closes < mavgs
hedge_signals = market_below_zero.astype(int) + market_below_mavg.astype(int)
hedge_signals = -hedge_signals
return hedge_signals
def signals_to_target_weights(self, signals: pd.DataFrame, prices: pd.DataFrame):
# Resample using the rebalancing interval.
# Keep only the last signal of the period, then fill it forward
signals = signals.resample(self.REBALANCE_INTERVAL).last()
signals = signals.reindex(prices.loc["Close"].index, method="ffill")
# Divide signal counts by 2 to get the target weights
weights = signals / 2
return weights
def target_weights_to_positions(self, weights: pd.DataFrame, prices: pd.DataFrame):
# Enter the position the day after the signal
return weights.shift()
def positions_to_gross_returns(self, positions: pd.DataFrame, prices: pd.DataFrame):
# Enter on the close
closes = prices.loc["Close"]
# The return is the security's percent change over the period,
# multiplied by the position.
gross_returns = closes.pct_change() * positions.shift()
return gross_returns
def order_stubs_to_orders(self, orders: pd.DataFrame, prices: pd.DataFrame):
orders["Exchange"] = "SMART"
orders["OrderType"] = "MOC"
orders["Tif"] = "DAY"
return orders
```Shown in full with attribution under the source's licence. Licence: Apache-2.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.