Skip to main content

trendfollowing

Closed-form trend-following analytics, reference system implementations, and reproducible futures evidence in Python for quantitative researchers and practitioners

It is a research and replication library, not a broker integration or general-purpose execution engine; portfolio analytics and reporting are delegated to qis.

Install: pip install trendfollowing · Import: trendfollowing · Status: Beta

PyPI Python CI Docs License Downloads Monthly

Paper: Sepp, A. and Lucic, V., The Science and Practice of Trend-Following Systems. Read and download the paper on SSRN: ssrn.com/abstract=3167787 (doi:10.2139/ssrn.3167787); also available as arXiv:2607.19497. See Citation for the BibTeX entry. The replication material for every figure and table is in papers/tf_systems/.

trendfollowing implements the paper's central result: an exact decomposition of the European trend-following system's P&L into an autocorrelation channel and a squared-drift channel,

\bar F_{1y} = h \sum_{m=1}^{\infty} \nu^{m}\rho(m)
+ \frac{l \sigma_{\mathrm{target}}}{\sqrt{a}} \mu^{2},
\qquad h = l \sigma_{\mathrm{target}} \sqrt{a} \frac{1-\nu}{\nu}

where $\rho(m)$ is the autocorrelation function of volatility-normalised returns, $\mu$ their annualised drift, and $\nu$ the filter smoothing parameter of the span. The annualised Sharpe ratio follows in closed form for any causal linear process, with the excess kurtosis of the innovations entering through a single loading. On 84 liquid futures contracts, the closed form applied to sample moments reproduces the realised Sharpe ratios of the European system with a pooled correlation of 0.99 and a regression slope of 0.96.

Why trendfollowing

Key differentiators

The package is useful when three things matter:

  • You want to select the filter span analytically rather than by grid search: the AR-1 break-even cost is nearly span-invariant ($c^{*}_{\infty} = \sqrt{\pi/2a} \phi/(1-\phi)$, 37–41bp at $\phi = 0.05$), while ARFIMA long memory creates an interior cost-optimal span — two regimes the closed forms separate cleanly.
  • You want to predict a contract's trend-following Sharpe ratio from its autocorrelation function and drift before running a backtest, and to attribute realised performance to trend, mean reversion, and drift.
  • You want three reference system implementations — continuous EWMA-filter weights, binary crossover positions with ATR stops, and sign-based time series momentum — that run out of the box on the packaged dataset, net of volume-based costs, with portfolio volatility targeting.

The analytics layer is pure numpy/scipy: every formula is a function you can read. The backtest layer builds on qis.

When to use it — and when not

The package is useful when selecting the filter span analytically rather than by grid search, predicting a contract's trend-following Sharpe ratio from its autocorrelation function and drift before running a backtest, or using the three reference system implementations.

It is a research and replication library, not a broker integration or general-purpose execution engine; portfolio analytics and reporting are delegated to qis.


Installation

pip install trendfollowing

Python >= 3.10 and qis >= 5.0.9. The analytical layer and the Monte Carlo verification run without any data. Wheels and source checkouts include the empirical dataset (84 futures contracts, benchmarks, volume-based costs; 1959–2026).

Development installation

For an editable checkout with the test and lint tools:

git clone https://github.com/ArturSepp/TrendFollowingSystems.git
cd TrendFollowingSystems
uv sync --locked --group test --group lint

Five-minute quickstart

The authoritative first-success script is repository-only, so run it from a cloned checkout: examples/quickstart.py. It uses only the installed top-level API to compute deterministic AR(1) and ARFIMA closed forms:

python examples/quickstart.py

It prints the installed version, AR(1) Sharpe 0.200195, ARFIMA(0,d,0) Sharpe 0.288820, and the 260-day annualization, zero-drift, and single-EWMA conventions. It runs without network or data access, writes no files, and points to PHI, D, and LONG_SPAN as the first parameters to change. The documentation quickstart includes the same file mechanically.

A portfolio backtest of the paper's LS(250,20) filter on the packaged universe:

from trendfollowing.universe import load_data
from trendfollowing.systems.european import run_european_tf_system

prices, volume_costs, benchmark_prices, descriptive_df, group_order = load_data()
outputs = run_european_tf_system(prices=prices,
                                 long_span=250,
                                 short_span=20,
                                 vol_span=33,                  # volatility estimator span, days
                                 portfolio_covar_span=63,      # portfolio-level volatility targeting
                                 portfolio_target_vol=0.15,
                                 volume_costs=volume_costs,
                                 warmup_period=250)
nav = outputs.portfolio_pnl_net                                # compounded nav, net of costs

Net of volume-based costs and gross of fees, this configuration delivers a Sharpe ratio of 1.10 at a 15.2% realised volatility over 1960–2026 (examples/backtest_european_system.py).

The three systems

European (systems/european.py): continuous weights from a variance-preserving EWMA filter, single or long-short, applied to volatility-normalised returns, with volatility-targeted position sizing. The system of the closed forms.

American (systems/american.py): binary positions from the crossover of two price EWMA filters with an ATR entry buffer and ATR trailing stop-losses, in the tradition of the turtle systems. Position size is fixed at trade inception.

TSMOM (systems/tsmom.py): the normalised sum of signs of volatility-normalised period returns, generalising Moskowitz–Ooi–Pedersen time series momentum to a period length L and lookback of M periods.

At matched lookbacks the three systems correlate at 80% on average with the SG Trend Index and deliver statistically indistinguishable Sharpe ratios by the Ledoit–Wolf test: 0.47, 0.50, and 0.55 against 0.47 for the SG Trend Index, on monthly returns net of costs and 2/20 fees. The European closed form therefore ranks the performance of all three designs.

Closed-form results

For volatility-normalised returns with autocorrelation function $\rho(m)$ and annualised drift $\mu$, the annualised Sharpe ratio of the European system is

SR = \frac{\sqrt{a} A_{\nu} + \mu^{2}/\sqrt{a}}
{\sqrt{B_{\nu} + A_{\nu}^{2} + \kappa K_{\nu} + (\mu^{2}/a)(1 + B_{\nu} + 2A_{\nu})}}

closed-form under any causal linear process, with the excess kurtosis $\kappa$ of the innovations entering through the single loading $K_{\nu}$. Under trading costs per unit of volatility-normalised turnover, the net Sharpe ratio follows at leading order from an independence-based signal-turnover proxy, and the ARFIMA autocorrelation generating function is the Gauss hypergeometric function $F(d, 1, 1-d; \nu)$. trendfollowing.analytics implements all of the above:

  • sharpe.compute_annualised_sharpe(rho, long_span, short_span, sr_underlying) — the generic formula
  • sharpe.compute_realized_sharpe(returns, af, ddof) — the canonical estimator $\sqrt{a} \hat E[f_t]/\sqrt{\widehat{\mathrm{Var}}[f_t]}$, equal to qis.compute_sharpe_arithmetic (guarded in the tests)
  • sharpe.sharpe_ar1, sharpe.compute_kurtosis_loading, sharpe.compute_signal_moments — per-process forms and loadings
  • autocorrelation.population_acf(n_lags, phi, d) — white noise, AR(1), ARFIMA(0,d,0), ARFIMA(1,d,0) (Sowell 1992)
  • expected_return.expected_pnl_*, expected_return.expected_turnover — expected return and turnover per process

The closed forms are exact rather than fitted, and Monte Carlo confirms them process by process. The figure below is Figure 6.3 of the paper: the expected annual return, the gross Sharpe ratio, and the net Sharpe ratio of the European system under the ARFIMA process with long memory $d = 0.02$ and AR-1 feature $\phi \in {-0.05, 0, 0.05}$, with the analytic values as lines and the Monte Carlo estimates as markers.

ARFIMA process: analytic closed forms against Monte Carlo

Analytic and Monte Carlo values agree at every span. The net Sharpe ratio in panel (C) attains an interior cost-optimal span, which long memory creates and the AR-1 process does not, because there the cost-optimal span diverges at the break-even cost.

Skewness of aggregated returns

Trend-following returns acquire positive skewness under time aggregation with no drift and no predictability. The daily return multiplies the lagged signal by the current return, so the $T$-day cumulative return loads on the realised autocovariance of the volatility-normalised returns, which makes it a convex payoff on the realised trend. Under white noise the skewness is available in closed form,

\varsigma(T) = \frac{6\nu \left( T(1-\nu^{2}) - 1 + \nu^{2T} \right)}
{(1-\nu^{2})^{3/2} T^{3/2}}

which is zero at one day, positive at every horizon beyond one day, and peaks near half the filter span.

Skewness of aggregated trend-following returns

Figure 7.5 of the paper: panel (A) is the closed form across filter spans with Monte Carlo markers, panel (B) is Monte Carlo under white noise, AR(1), and ARFIMA at the span of 100 days, and panel (C) is the empirical profile across the 84 futures contracts, whose median attains 2.33 at the horizon of 55 days against the closed-form 2.35 and whose interquartile range stays positive at every horizon. The right tail of trend-following returns is structural: it requires no forecasting skill, because it holds exactly where the expected return is zero. analytics.skewness.skewness_white_noise(horizon, span) implements the formula.

Empirical illustration

The figure below is Figure 7.3 of the paper: the Sharpe ratio of the European system predicted from each contract's sample autocorrelation function and drift, against the realised backtest Sharpe ratio, across 84 futures contracts and the paper's span grid.

Predicted versus realised Sharpe ratios across 84 futures contracts

The pooled correlation is 0.99 and the regression slope 0.96 for the European system, 0.89 and 0.73 for TSMOM, and 0.92 and 0.61 for the American system at spans above one month. The practical content: two sample moments of a contract's volatility-normalised returns — its autocorrelation function and its drift — carry nearly all the information a trend-following backtest on that contract produces. Span selection, contract screening, and performance attribution can run on the closed form directly, and the same formula prices the trade-off that costs impose: at realistic futures costs of 40–60bp per unit of volatility-normalised turnover, a short-memory AR-1 alpha at $\phi = 0.05$ sits below its 37–41bp break-even at every span, while long-memory alpha survives at the one-to-three-month cost-optimal spans.

You can reproduce the per-contract exercise in three lines (examples/predict_sharpe_from_acf.py): ES1 predicts 0.227 against a realised 0.206, and Corn predicts 0.625 against 0.620.

The three systems also run out of the box on the packaged dataset. The figure below is Figure 7.2 of the paper: the European, American, and TSMOM systems net of volume-based costs against the SG Trend Index, with the cumulative performance, the running drawdown, and the one-year EWMA correlations.

The three systems against the SG Trend Index

Examples

Self-contained usage cases in examples/, each runnable directly:

  • analytic_sharpe_vs_span.py — the closed-form gross and net Sharpe ratios across spans: the AR-1 knife edge (the cost decides the sign at every span) and the ARFIMA interior optimum. Runs without data.
  • backtest_european_system.py — the LS(250,20) portfolio backtest on the packaged 84-contract universe with volume-based costs and portfolio volatility targeting.
  • predict_sharpe_from_acf.py — the attribution exercise in miniature: predict the per-contract Sharpe ratio from the sample autocorrelation function and drift, and compare with the realised backtest on the same sample.

Reproducing the paper exhibits

One entry point reproduces every figure, driven by the PaperFigure enum:

python -m papers.tf_systems.replication.reproduce_all_figures

Simulation figures are seed-exact (seed 8) and need no data. The Monte Carlo aggregates behind the process figures and the verification table are cached in papers/tf_systems/replication/results/, so those figures re-render in seconds without re-simulation. See papers/tf_systems/README.md for the figure-by-figure map and the verification catalogue.

Repository layout

src/
    trendfollowing/                     the installable library
        analytics/                          closed-form results of the paper
        systems/                            european.py, american.py, tsmom.py
        processes/                          simulation of return-generating processes
            run_local/                      process development runners; not distributed
        run_local/                          package development runners; not distributed
        resources/futures/                  84 futures series (1959-2026), benchmarks,
                                           volume-based costs, and metadata
        universe.py                         futures universe data layer
        backtests.py                        portfolio-level backtests of the three systems (qis)
resources/
    papers/                            writable paper-replication caches; not installed
examples/                           self-contained usage cases; kept at repository root
papers/
    tf_systems/                         'The Science and Practice of Trend-Following Systems'
        paper/                              LaTeX source, siamonline class, compiled PDF, figures
        replication/                        exhibit generators, verification scripts, MC caches
tests/                              pytest suite

Data

The dataset installed under trendfollowing/resources/futures contains the daily prices and USD returns of the 84 futures contracts used in the paper (July 1959 to July 2026), the benchmark series, the volume-based cost schedule, and the instrument metadata. The universe covers the most liquid contracts across global equity, bond, short-rate, currency, and commodity markets. The continuous series are constructed so that their relative returns carry no roll-related jumps and equal the excess returns of the held contract. trendfollowing.universe.load_data() resolves these files through package resources; set TF_RESOURCE_PATH to override them with an external folder.

Sharpe convention

All Sharpe ratios of the theory, the attribution, and the report exhibits are annualised arithmetic means over annualised volatility of periodic simple excess returns, $SR = \sqrt{a}\cdot\text{mean}/\text{std}$ — the convention of equation (5.1) of the paper, computed by the shared estimator trendfollowing.compute_realized_sharpe. The regime-conditional Sharpe ratios route through the qis SharpeConvention.ARITHMETIC switch at the manuscript's one-sigma 16/84 quantiles, where the bear, normal, and bull contributions sum to the total Sharpe exactly. See qis/docs/sharpe_conventions.md for the decision record.

Verification

papers/tf_systems/replication/ carries the verification scripts behind the manuscript's claims: the boundary term of the sample-path identity, the Appendix C asymptotics, the GARCH pipeline and ARFIMA truncation checks, and a Monte Carlo regression test of the long-short normalisation and the turnover closed form.

cd papers/tf_systems/replication && PYTHONPATH=../../.. python verify_ls_normalization.py

Tests

uv run --no-sync pytest

Ecosystem

This package is part of Artur Sepp's open-source Python stack for quantitative finance. The maintainer profile is the canonical ten-package catalogue.

Package Purpose
qis Performance analytics, factsheets, and visualisation
optimalportfolios Portfolio construction and backtesting
factorlasso Sparse factor models and factor covariance estimation
bbg-fetch Bloomberg data fetching
option-chain-analytics Point-in-time option-chain normalisation, reconstruction, queries, and visualisation
trendfollowing (this package) Trend-following systems: closed-form theory and replication
privateassets Money-weighted multi-factor alpha from private-asset cash flows
goal-based-allocation Dynamic MV allocation under regime-switching jump-diffusions
stochvolmodels Stochastic volatility pricing analytics
vanilla-option-pricers Vectorised vanilla option pricers and implied volatility fitters

trendfollowing depends on qis for portfolio analytics, backtesting, and reporting. The closed-form trend analytics and paper replication remain this package's distinct job; option-chain workflows belong to option-chain-analytics and pricing under stochastic volatility belongs to stochvolmodels.

Feedback & contributing

Citation

If you use trendfollowing in academic work, please cite the paper and the software (see also CITATION.cff):

@article{SeppLucic2026trendfollowing,
  author        = {Sepp, Artur and Lucic, Vladimir},
  title         = {The Science and Practice of Trend-Following Systems},
  year          = {2026},
  eprint        = {2607.19497},
  archivePrefix = {arXiv},
  primaryClass  = {q-fin.ST},
  note          = {SSRN: \url{https://ssrn.com/abstract=3167787}},
  doi           = {10.2139/ssrn.3167787}
}

The paper states the results; this package is what produced them, so a replication should also cite the version it ran:

@software{sepp2026trendfollowing,
  author  = {Sepp, Artur and Lucic, Vladimir},
  title   = {trendfollowing},
  year    = {2026},
  version = {1.2.0},
  url     = {https://github.com/ArturSepp/TrendFollowingSystems}
}

License

GPL-3.0-or-later — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

trendfollowing-1.2.0.tar.gz (15.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

trendfollowing-1.2.0-py3-none-any.whl (15.4 MB view details)

Uploaded Python 3

File details

Details for the file trendfollowing-1.2.0.tar.gz.

File metadata

  • Download URL: trendfollowing-1.2.0.tar.gz
  • Upload date:
  • Size: 15.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for trendfollowing-1.2.0.tar.gz
Algorithm Hash digest
SHA256 cec24d2f559a686aa697c6191ca41010ba779a598265cd7780d3e0040420065a
MD5 cffc37e80a7f5aa0184c6a1687fa6237
BLAKE2b-256 2e0160018cd33133cc19e97cfff5593b219eafbc38cde77300dda827523b6fab

See more details on using hashes here.

Provenance

The following attestation bundles were made for trendfollowing-1.2.0.tar.gz:

Publisher: release.yml on ArturSepp/TrendFollowingSystems

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file trendfollowing-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: trendfollowing-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 15.4 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for trendfollowing-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 341a7b77f2a5c56cbd80bda7ca3ff21883df5bd371d767540d61082f7e496277
MD5 a57f63f4920b59029970875dfc99c465
BLAKE2b-256 a96a47439d45eb5cd956c586f01095f3421e1ee1fe3638609a8d1ae1273e40e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for trendfollowing-1.2.0-py3-none-any.whl:

Publisher: release.yml on ArturSepp/TrendFollowingSystems

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 files

1.1.0

2 files

1.0.5

2 files

1.0.4

2 files

1.0.3

1 file

1.0.2

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page