Skip to content
All library documents

VMOT Trend Hedging with 12-Month Moving Average and Return Rules

Article Strategy library · Author: QuantRocket

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.