Skip to content
All library documents

EWMAC Trend Forecasts Normalized by Price Volatility

Code pysystemtrade

Summary

The document explains an exponentially weighted moving average crossover (EWMAC) forecast. It subtracts a slower exponential moving average of price from a faster one, then divides that difference by daily price volatility. A positive or negative result therefore reflects the direction and size of the recent trend relative to typical price movement. The examples assume daily data and use configurable fast and slow spans; one variant estimates volatility from price changes using a robust volatility calculation, while another accepts a precomputed volatility series.

The output is explicitly an unscaled, uncapped forecast, so it is not presented as a complete position-sizing or risk-control system. The notes recommend using a stitched futures price series rather than the currently traded contract, to avoid volatility jumps and account for roll effects. An illustrative historical output is shown, but no broader performance analysis or evidence of profitability is provided; the example settings and data should not be treated as universally suitable.

Key ideas

  • EWMAC is the difference between fast and slow exponential moving averages of price.
  • The raw crossover is divided by price volatility to normalize the forecast by market movement.
  • Volatility can be estimated from price changes or supplied as an aligned series.
  • The output is uncapped and unscaled, so further forecast processing and risk controls are outside the method shown.
  • For futures, the notes favor stitched prices over the active contract to reduce roll-related distortions.

Tags

Full text
# ewmac.py


```py
from sysquant.estimators.vol import robust_vol_calc


def ewmac_forecast_with_defaults(price, Lfast=32, Lslow=128):
    """
    ONLY USED FOR EXAMPLES

    Calculate the ewmac trading rule forecast, given a price and EWMA speeds
      Lfast, Lslow

    Assumes that 'price' is daily data

    This version recalculates the price volatility, and does not do capping or
      scaling

    :param price: The price or other series to use (assumed Tx1)
    :type price: pd.Series

    :param Lfast: Lookback for fast in days
    :type Lfast: int

    :param Lslow: Lookback for slow in days
    :type Lslow: int

    :returns: pd.Series -- unscaled, uncapped forecast


    """
    # price: This is the stitched price series
    # We can't use the price of the contract we're trading, or the volatility
    # will be jumpy
    # And we'll miss out on the rolldown. See
    # https://qoppac.blogspot.com/2015/05/systems-building-futures-rolling.html

    # We don't need to calculate the decay parameter, just use the span
    # directly

    ans = ewmac_calc_vol(price, Lfast=Lfast, Lslow=Lslow)

    return ans


def ewmac_forecast_with_defaults_no_vol(price, vol, Lfast=16, Lslow=32):
    """
    ONLY USED FOR EXAMPLES

    Calculate the ewmac trading rule forecast, given price, volatility and EWMA speeds
      Lfast, Lslow

    Assumes that 'price' is daily data and that the vol is on the same timestamp

    :param price: The price or other series to use (assumed Tx1)
    :type price: pd.Series

    :param vol: The vol of the price
    :type vol: pd.Series

    :param Lfast: Lookback for fast in days
    :type Lfast: int
    :param Lslow: Lookback for slow in days
    :type Lslow: int

    :returns: pd.Series -- unscaled, uncapped forecast


    """
    # price: This is the stitched price series
    # We can't use the price of the contract we're trading, or the volatility will be jumpy
    # And we'll miss out on the rolldown. See
    # https://qoppac.blogspot.com/2015/05/systems-building-futures-rolling.html

    # We don't need to calculate the decay parameter, just use the span
    # directly
    ans = ewmac(price, vol, Lfast=Lfast, Lslow=Lslow)

    return ans


def ewmac(price, vol, Lfast, Lslow):
    """
    Calculate the ewmac trading rule forecast, given a price, volatility and EWMA speeds Lfast and Lslow

    Assumes that 'price' and vol is daily data

    This version uses a precalculated price volatility, and does not do capping or scaling

    :param price: The price or other series to use (assumed Tx1)
    :type price: pd.Series

    :param vol: The daily price unit volatility (NOT % vol)
    :type vol: pd.Series aligned to price

    :param Lfast: Lookback for fast in days
    :type Lfast: int

    :param Lslow: Lookback for slow in days
    :type Lslow: int

    :returns: pd.Series -- unscaled, uncapped forecast


    >>> from systems.tests.testdata import get_test_object_futures
    >>> from systems.basesystem import System
    >>> (rawdata, data, config)=get_test_object_futures()
    >>> system=System( [rawdata], data, config)
    >>>
    >>> ewmac(rawdata.get_daily_prices("EDOLLAR"), rawdata.daily_returns_volatility("EDOLLAR"), 64, 256).tail(2)
    2015-12-10    5.327019
    2015-12-11    4.927339
    Freq: B, dtype: float64
    """
    # price: This is the stitched price series
    # We can't use the price of the contract we're trading, or the volatility will be jumpy
    # And we'll miss out on the rolldown. See
    # https://qoppac.blogspot.com/2015/05/systems-building-futures-rolling.html

    # We don't need to calculate the decay parameter, just use the span
    # directly

    fast_ewma = price.ewm(span=Lfast, min_periods=1).mean()
    slow_ewma = price.ewm(span=Lslow, min_periods=1).mean()
    raw_ewmac = fast_ewma - slow_ewma

    return raw_ewmac / vol.ffill()


def ewmac_calc_vol(price, Lfast, Lslow, vol_days=35):
    """
    Calculate the ewmac trading rule forecast, given a price and EWMA speeds Lfast, Lslow and number of days to
    lookback for volatility

    Assumes that 'price' is daily data

    This version recalculates the price volatility, and does not do capping or scaling

    :param price: The price or other series to use (assumed Tx1)
    :type price: pd.Series

    :param Lfast: Lookback for fast in days
    :type Lfast: int

    :param Lslow: Lookback for slow in days
    :type Lslow: int

    :param vol_days: Lookback for volatility in days
    :type vol_days: int

    :returns: pd.Series -- unscaled, uncapped forecast


    >>> from systems.tests.testdata import get_test_object_futures
    >>> from systems.basesystem import System
    >>> (rawdata, data, config)=get_test_object_futures()
    >>> system=System( [rawdata], data, config)
    >>>
    >>> ewmac(rawdata.get_daily_prices("EDOLLAR"), rawdata.daily_returns_volatility("EDOLLAR"), 64, 256).tail(2)
    2015-12-10    5.327019
    2015-12-11    4.927339
    Freq: B, dtype: float64
    """
    # price: This is the stitched price series
    # We can't use the price of the contract we're trading, or the volatility will be jumpy
    # And we'll miss out on the rolldown. See
    # https://qoppac.blogspot.com/2015/05/systems-building-futures-rolling.html

    # We don't need to calculate the decay parameter, just use the span
    # directly

    vol = robust_vol_calc(price.diff(), vol_days)
    forecast = ewmac(price, vol, Lfast, Lslow)

    return forecast

```

Shown in full with attribution under the source's licence. Licence: GPL-3.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.