Skip to content
All strategies

Strategies

Quantiacs Futures Trend-Following (WMA200 / ROCP20 / 2% sideways band), faithful port on a 7-name OKX USDT-perp basket, daily, long/short, equal-weight across active legs

Outcome: Abandoned

QuantiacsTrendFollowingOkxBasket

Outcome Summary

This run ported Quantiacs' WMA/ROCP trend-following state machine onto a seven-name OKX USDT-perp basket, trading daily long/short with equal weights. On the first iteration's backtest the basket lost 15.57% over about 4.7 years, with 58 trades, profit factor 0.920, Sharpe 0.19 and a 64.31% drawdown. Price moves produced only a small gain, and funding costs turned the result into a loss. The analyst abandoned it at backtest review, before optimization, while noting that this does not reject the original Quantiacs strategy or every possible parameter variation.

Hypothesis

Faithful port of Quantiacs' MIT-licensed 'strategy-futures-trend-following' (strategy.ipynb, multi-pass `strategy(data)` function); credit to Quantiacs. The source's universe is the Quantiacs futures set. Our platform cannot trade CME futures, so the basket is OKX USDT perpetual futures with daily history back to at least 2021-01-24: BTC, ETH, SOL, ADA, LINK, BCH and TRX. OKX is under-represented in the corpus at 2.7%. RULES, identical per asset, computed on each asset's own 1-DAY close: ma = WMA(close, 200), a……Show moreShow less

Faithful port of Quantiacs' MIT-licensed 'strategy-futures-trend-following' (strategy.ipynb, multi-pass `strategy(data)` function); credit to Quantiacs. The source's universe is the Quantiacs futures set. Our platform cannot trade CME futures, so the basket is OKX USDT perpetual futures with daily history back to at least 2021-01-24: BTC, ETH, SOL, ADA, LINK, BCH and TRX. OKX is under-represented in the corpus at 2.7%. RULES, identical per asset, computed on each asset's own 1-DAY close: ma = WMA(close, 200), a TA-Lib linear-weighted moving average with weights 1..200, newest weight highest. roc = ROCP(ma, 20) = ma[t]/ma[t-20] - 1. sideways_threshold = 0.02. positive_trend = roc > 0.02; negative_trend = roc < -0.02; sideways = |roc| <= 0.02. side_positive_trend = the last non-sideways state was positive; side_negative_trend = the last non-sideways state was negative; both are False before any non-sideways bar. Position state machine per asset, a port of the xarray code: start at 0. On each bar, in this order: if positive_trend, raw = +1; if negative_trend, raw = -1; otherwise raw is unset. fp = the forward-filled previous position, taking raw into account. If side_negative_trend and fp > 0, raw = 0. If side_positive_trend and fp < 0, raw = 0. position = raw if set, else the previous position (ffill), initialised to 0. In effect the asset goes long when the WMA's 20-day change exceeds +2% and short when it is below -2%. While it is sideways the position is held. Note that a positive trend sets side_positive_trend, so a long can only be stopped by a negative regime, which already flips it short through sell_signal. The stop branches therefore rarely produce a flat state, and the developer must reproduce the source's ordering exactly rather than simplify it. WEIGHTS: output = position / sum(|position|) across the basket, so each non-flat asset gets ±1/N_active of the gross book and nothing is held when all are flat. PLATFORM ADAPTATIONS (not in the source): (1) Gross notional = account equity × leverage 1.0, so each active leg's notional = equity / N_active, signed. (2) Execution: Quantiacs applies day-t weights from the next day. On a 24/7 venue our `next_bar_open_fill` capability is `use_equivalent`, so orders are market orders at the signal bar's close, which equals the next daily bar's open. (3) The source rebalances to its target weights every day. To avoid paying taker fees on tiny drift trades, a leg is resized only when its target sign changes, N_active changes, or its notional drifts more than rebalance_tolerance (fixed at 20%) from target. This is the only rule added beyond the source. (4) The source has no stop-loss, and none is added; exits come only from the state machine. (5) OKX fees are 0.05% taker, about 0.10% round trip, and funding is applied through the ledger. WARM-UP: indicators need 200 + 20 bars. The registered warm-up is 360 daily bars, which also covers the tunable maximum of 300 + 40. This fixes the prior contract's 1825-bar warm-up, which was the source's 5-year `lookback_period` data window rather than an indicator requirement and exceeded the basket's joint history. The source's own performance claims are unverified; our backtest decides.

Retry fix: the earlier contract registered the source's 5*365 lookback_period as warm-up. Indicator warm-up is 220 bars (360 with tunable maxima), which fits the basket's joint daily history from 2021-01-24 (about 2,070 days). The futures/long-short character of the source is kept; OKX fills an under-represented venue bucket.

negative_expectancy: the measured OKX basket operationalization loses after credited funding and execution costs, with PF 0.920 and avg_trade_return_pct -4.194% versus +0.15%. Funding drag overwhelms the small positive price_pnl contribution. This baseline does not justify a two-hour parameter search. The decision applies to this crypto-perpetual port and its added rebalance tolerance; it does not reject Quantiacs' original futures-universe strategy or establish that every parameter variation is unprofitable.

Implementation

Port of Quantiacs' strategy-futures-trend-following on 7 OKX USDT perps (BTC, ETH, SOL, ADA, LINK, BCH, TRX), daily. Each asset runs WMA(200) -> ROCP(20) with a 2% sideways band and the source's ffilled side-trend stop state machine. The asset is long when roc > +2%, short when roc < -2%, and holds while sideways. The book is equal-weight: each active leg is sized at +-MTM equity x leverage / N_active, and the strategy is flat when every leg is flat.

Faithful translation of the multi-pass strategy(data). The WMA uses TA-Lib's linear weights 1..n with the newest bar weighted highest. ROCP = ma[t]/ma[t-20]-1. side_positive and side_negative reproduce xarray's where(sideways==False).ffill().fillna(False), so a bar with undefined roc counts as non-sideways with both flags False. The stop branches use fp, the ffilled PRE-stop raw series, and run in the source's order. The final position is the ffill of the post-stop series. A pandas reproduction of the xarray code……Show moreShow less

Faithful translation of the multi-pass strategy(data). The WMA uses TA-Lib's linear weights 1..n with the newest bar weighted highest. ROCP = ma[t]/ma[t-20]-1. side_positive and side_negative reproduce xarray's where(sideways==False).ffill().fillna(False), so a bar with undefined roc counts as non-sideways with both flags False. The stop branches use fp, the ffilled PRE-stop raw series, and run in the source's order. The final position is the ffill of the post-stop series. A pandas reproduction of the xarray code matched this logic in 200 of 200 random trials. Weights are position/sum|position|. Every leg is its own market order on its own instrument, placed from calculate_signal on the primary bar after the base's same-timeframe barrier has aligned all 7 legs at that bar's timestamp. On the first evaluated bar, the state machine is replayed over each leg's buffered history, so it starts from history the way the source's 5-year window does. Parameters keep the source's default values. rebalance_tolerance (0.2) and leverage (1.0) are fixed per the plan, and wma_period, roc_period and sideways_threshold are tunable. No clamps are applied, so no _param_bounds are declared. Expected trade count is low: about 7 legs x a few trend flips a year, plus resizes whenever N_active changes.

Verification Results

In the full backtest, verify that OKX funding (PG funding_rates, exchange okx) is attached per leg. Treat the sandbox return as gross of funding until then.

Funding is not applied in the sandbox (funding_folded=false, funding_events_available=0), although the hypothesis says funding is applied through the ledger. This is a platform/data-attachment matter, not strategy code. The strategy never reads funding, which is correct for this price-only trend rule.

No change needed. The data-gap auditor should keep the OKX daily legs complete.

Every leg's state machine advances only when the base's same-timeframe barrier releases the primary bar, and that requires all 7 legs to have a bar at that ts. A single missing daily bar on one OKX leg skips that day for the whole basket. The next bar catches up, because _update_leg processes every buffered bar with ts > leg_last_ts, so state stays correct, but that day's rebalance is skipped. There is no look-ahead: the barrier guarantees contemporaneous closes.

None. The static base_shadow warning is a false positive here.

min_bars_required() is overridden on purpose: max(360, wma+roc+1). This holds back calculate_signal until 360 primary bars are buffered. The worst-case need, 300+40, fits inside the base buffer of _max_bars=500, so the WMA history is never truncated.

The sandbox shows 48.4% max drawdown, above the plan's 35% constraint, with a bootstrap DD CI of 33-84%. The book is always 1x gross when any leg is active and has no stop, which is faithful to the source. Short legs win only 29% of the time, and the calm regime loses -19.7%. The sandbox also reports funding_folded=false with funding_events_available=0, so OKX funding on these multi-month holds (avg 209 days) is NOT yet in PnL. Confirm the full backtest attaches the OKX funding ledger before trusting the result, because longs in a crypto bull market pay positive funding for long stretches. Fees are not a concern: the sandbox shows avg trade return 5.0% of notional against a ~0.10% round trip, and commissions are 0.75% of gross. The ledger counts 37 closed positions but 368 fills, because resizes happen on every N_active change.

Backtest Review

Sharpe
0.19
Total return
-15.57%
Max drawdown
64.31%
Trades
58
Win rate
36.2%
Profit factor
0.92

Trade records contain long and short positions across all seven instruments, consistent with the basket's intended directionality and slow turnover.

Funding is included: 36,185 available events and explicit funding cash flows in the closed trades resolve QA's missing-funding concern.

Over 1,725 evaluated days, 58 closed positions lose $15,573.51: PF 0.920 and average trade return -4.194%, versus the supplied OKX futures floor of +0.15%.

Trade records reconcile $2,348.73 of price_pnl with -$17,922.24 of funding to the net loss. The implemented long-duration book does not earn enough to cover its actual carrying costs.

QA's risk concern is borne out: drawdown reaches 64.31%, with 2025 losing 44.05%. Drawdown is supporting evidence, not the sole rejection reason.

The basket benchmark is marked non-meaningful; no buy-and-hold alpha conclusion is warranted.

Outcome Summary

Slow, long-holding trend books on crypto perps need their price edge measured net of funding carry, because funding (-$17.9k here) can easily overwhelm a modest directional edge.

The backtest-review analyst voted to abandon on negative expectancy: avg_trade_return_pct of -4.194% against the +0.15% OKX futures floor, with funding drag outweighing the small positive price PnL, so the two-hour optimization was judged unjustified. The analyst said this verdict applies only to the crypto-perp port and its added rebalance tolerance, not to Quantiacs' original futures-universe strategy.

A faithful port of Quantiacs' MIT-licensed futures trend-following strategy (WMA200, 20-day ROCP of the WMA, ±2% sideways band, per-asset long/short state machine) onto an equal-weighted daily basket of 7 OKX USDT perps (BTC, ETH, SOL, ADA, LINK, BCH, TRX) at 1.0x leverage, with a 20% rebalance tolerance added.

Over 1,725 evaluated days (2022-01-19 to 2026-10-09) it closed 58 positions for a total return of -15.57% (CAGR -3.52%), Sharpe 0.19, profit factor 0.920, win rate 36.2% and avg_trade_return_pct -4.194%, with a 64.31% max drawdown. Trade records reconcile +$2,348.73 of price PnL against -$17,922.24 of funding.

Analysis

strategy-futures-trend-following

Analysis

strategy.ipynb

Backtest and paper results are hypothetical. Trading involves risk of loss.