Skip to content
All library documents

Breakout, Carry, Bias, and Cross-Sectional Mean-Reversion Forecasts

Code pysystemtrade

Summary

This Python module defines four types of trading forecasts from price or carry series. Its breakout rule locates the rolling high-low range, measures the current price relative to the range midpoint, scales that reading, and smooths it with an exponentially weighted average. The lookback controls the range window, while the smoothing span defaults to a fraction of that window. Separate long- and short-bias functions return constant positive or negative forecasts. A relative-carry rule subtracts an asset-class median carry series from the instrument’s smoothed carry.

The cross-sectional mean-reversion function compares an instrument’s normalized price with its asset-class counterpart, computes changes in relative outperformance, averages them over a horizon, then reverses and smooths the result. These are unscaled, uncapped forecast components rather than complete position or execution rules. The source provides implementation details but no empirical evaluation, sizing method, risk limits, or evidence that any forecast is profitable. Practical use requires aligned and appropriate input series, plus independent testing and portfolio-level controls.

Key ideas

  • The breakout forecast positions price within its rolling high-low range and smooths the signal.
  • The long- and short-bias functions produce constant forecasts with opposite signs.
  • The relative-carry signal measures an instrument’s smoothed carry against its asset-class median.
  • The mean-reversion signal fades sustained relative outperformance within an asset class.
  • The functions supply raw forecasts and do not specify sizing, execution, or risk controls.

Tags

Full text
# _TO_DELETE_OLD.py


```py
from copy import copy

import numpy as np


def breakout(price, lookback=10, smooth=None):
    """
    :param price: The price or other series to use (assumed Tx1)
    :type price: pd.DataFrame

    :param lookback: Lookback in days
    :type lookback: int

    :param smooth: Smooth to apply in days. Must be less than lookback! Defaults to smooth/4
    :type smooth: int

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

    With thanks to nemo4242 on elitetrader.com for vectorisation

    """
    if smooth is None:
        smooth = max(int(lookback / 4.0), 1)

    assert smooth < lookback

    roll_max = price.rolling(
        lookback, min_periods=int(min(len(price), np.ceil(lookback / 2.0)))
    ).max()
    roll_min = price.rolling(
        lookback, min_periods=int(min(len(price), np.ceil(lookback / 2.0)))
    ).min()

    roll_mean = (roll_max + roll_min) / 2.0

    # gives a nice natural scaling
    output = 40.0 * ((price - roll_mean) / (roll_max - roll_min))
    smoothed_output = output.ewm(span=smooth, min_periods=np.ceil(smooth / 2.0)).mean()

    return smoothed_output


def short_bias(price):
    """

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

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

    """

    avg_abs_forecast = 10

    forecast = -1.0 * avg_abs_forecast

    forecast_ts = copy(price)
    forecast_ts[:] = forecast

    return forecast_ts


def long_bias(price):
    """

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

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

    """

    avg_abs_forecast = 10.0

    forecast = 1.0 * avg_abs_forecast

    forecast_ts = copy(price)
    forecast_ts[:] = forecast

    return forecast_ts


def relative_carry(smoothed_carry_this_instrument, median_carry_for_asset_class):
    """
    Relative carry rule
    Suggested inputs: rawdata.smoothed_carry, rawdata.median_carry_for_asset_class

    :param smoothed_carry_this_instrument: pd.Series
    :param median_carry_for_asset_class: pd.Series aligned to smoothed_carry_this_instrument
    :return: forecast pd.Series
    """

    # should already be aligned
    relative_carry_forecast = (
        smoothed_carry_this_instrument - median_carry_for_asset_class
    )

    return relative_carry_forecast


def cross_sectional_mean_reversion(
    normalised_price_this_instrument,
    normalised_price_for_asset_class,
    horizon=250,
    ewma_span=None,
):
    """
    Cross sectional mean reversion within asset class

    :param normalised_price_this_instrument: pd.Series
    :param normalised_price_for_asset_class: pd.Series
    :return: pd.Series
    """

    if ewma_span is None:
        ewma_span = int(horizon / 4.0)

    ewma_span = max(ewma_span, 2)

    outperformance = (
        normalised_price_this_instrument.ffill()
        - normalised_price_for_asset_class.ffill()
    )
    relative_return = outperformance.diff()
    outperformance_over_horizon = relative_return.rolling(horizon).mean()

    forecast = -outperformance_over_horizon.ewm(span=ewma_span).mean()

    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.