Momentum
Long/short time-series momentum on SOL-PERP, ETH-PERP and BTC-PERP, running long-only in production at Phoenix — three bots, one strategy and one set of indicator settings. Trend and breakout must agree before an entry; size scales inversely with realised volatility; exits are an ATR trailing stop or the trend filter turning over.
Intent
Time-series momentum is the oldest surviving systematic edge: instruments that have been going up tend to keep going up for a while, and the same in reverse. It is a low-hit-rate, high-payoff shape — most trades are small losses and a minority of large trends pay for them. Everything in the design serves that shape. Two independent conditions must agree before risking anything, position size shrinks when the market gets violent, and the exit is deliberately loose so a winning trend is not cut short by ordinary noise.
Model
Timeframe
The strategy consumes the same one-minute bars as everything else but does not act on them. One-minute bars accumulate in a buffer and are aggregated into four-hour bars — first open, highest high, lowest low, last close — and a decision is taken only on the bar that closes a four-hour bucket. Nothing at all is emitted until there are complete four-hour bars, which is 368 hours — 15.3 days — of history.
That warm-up is the single most consequential number on this page, because the buffer is not persisted. It is not restored from a snapshot and nothing seeds it from stored candles, so a momentum bot that restarts begins from an empty buffer and cannot take a position for 15.3 days of live bars, however long it ran before. Under the previous fifteen-minute cadence the same rule cost 15.5 hours and was a footnote; at four hours it is a fortnight, and it is the reason a restart is an operational event for this strategy and not for the others.
Trend filter
Two exponential moving averages of the resampled close, recursive, with no adjustment for the finite history at the start:
The fast leg uses (five days), the slow leg (fifteen days). The sign of is the trend state, and it does double duty — it gates entries and it also closes positions on its own.
Breakout filter
A Donchian channel over the previous forty resampled bars — 160 hours, about 6.7 days — computed on highs and lows, and deliberately shifted back one bar so the current bar's own extreme cannot trigger its own breakout:
Volatility and sizing
Realised volatility is an exponentially weighted standard deviation of log returns on the resampled closes, annualised over the 2,190 four-hour bars in a year:
Position size is then set so that, at the estimated volatility, the position carries roughly the target volatility — but only ever scaling down:
The is the important part. When annualised realised volatility is above the 60% target the position shrinks proportionally; when it is below, the fraction is capped at one rather than levering up. The sizing rule therefore never asks for more than the $10 allocation, whatever the volatility estimate says. The entry is a market order priced off the resampled close, so the notional actually filled can land a little either side of what was asked for.
is the same 0.60 on all three markets, and that is the point of it rather than an untuned default. It is a risk unit, not a fit: one target is what makes a $10 position on SOL and a $10 position on BTC carry comparable risk, which is the only sense in which an even split across the three is an even split. At the volatilities measured over the last ninety days — SOL 0.618 annualised, ETH 0.522, BTC 0.398 — SOL sizes to about 0.97 of its allocation and ETH and BTC to a full 1.0. Tuning per market would undo the equalisation the parameter exists to produce.
Trailing stop
Average true range over fourteen resampled bars — 56 hours, about 2.3 days — Wilder-smoothed, an exponential average with , not a simple mean:
The stop trails the best close seen since entry — for a long, for a short, both seeded at the entry price and updated on every decision bar:
Signal rules
Evaluated once per four-hour close, in this order. The position state is checked first, so an exit and a fresh entry never happen on the same bar.
| State | Condition | Action |
|---|---|---|
| Flat | and | Buy qₜ — open long. |
| Flat | and | Sell qₜ — open short. |
| Long | or | Sell the whole position — flat. |
| Short | or | Buy back the whole position — flat. |
Neither entry fires on one condition alone. A trend without a breakout is a drift the strategy sits out; a breakout against the trend is treated as noise. Both exits flatten completely — there is no partial de-risking and no pyramiding.
Parameters
| Parameter | Live value | Meaning |
|---|---|---|
| capital_usd | 10 | Allocation for each of the three momentum bots; also the sizing base. |
| fast / slow | 30 / 90 | EMA spans on four-hour resampled closes (5 d / 15 d). |
| breakout | 40 | Donchian lookback in resampled bars (6.7 d). |
| atr_period | 14 | Wilder ATR period (2.3 d). |
| atr_mult | 4.0 | Trailing stop distance in ATRs. |
| target_vol | 0.60 | Annualised volatility target for sizing (σ*). One desk-wide unit, not tuned per market. |
| resample_min | 240 | Resample interval in minutes — four-hour bars. |
| allow_short | false | Short entries disabled (11 Aug 2026) — no backtest covers the short side, so the runner clips every sell to the position on hand. |
| max_leverage | 2.0 | Hard platform ceiling, enforced by the risk gate. |
| max_order_pct | 100 | Order-size cap: a full entry deploys the whole $10 by design. |
Why these parameters
Until 2026-08-08 this strategy resampled to fifteen minutes with a 20 / 60 EMA pair and a 3 × ATR trail. It re-evaluated 96 times a day, and the EMA-cross exit turned over on every stretch of chop. Over the 90-day backtest window that produced 116 / 111 / 122 fills on SOL / ETH / BTC and paid 19.66% / 18.49% / 20.32% of capital in fees and slippage, against a gross signal result of −3.48% / −6.53% / −8.12% and net returns of −23.14% / −25.02% / −28.44%. Read those three numbers together: the signal lost a few percent and the turnover lost twenty. Cost was not a drag on the result, it was the result.
Slowing the resample to four hours, widening the EMA pair to 30 / 90 and loosening the trail to 4 × ATR takes 2 fills on every market and 0.31% / 0.41% / 0.40% of capital in cost. Fills fall by 58× / 55× / 61× and cost by 63× / 45× / 51×. Net returns over the same window are +1.78% / +5.11% / +0.30%, on gross of +2.09% / +5.52% / +0.70%.
The cost reduction is the only claim this evidence supports, and it is mechanical: fewer fills cost less, the arithmetic follows from the fill counts alone, and it would hold in any window. The returns support nothing. Two fills is a single completed round trip per market in ninety days — one trade a quarter. Three positive numbers drawn from one trade each carry no information about whether the strategy has an edge, and they should not be read as though they do. A system that trades once a quarter and holds through everything in between is close to buy-and-hold with a stop attached, and the honest description of these curves is that they are quiet, not that they are good.
One set of values runs on all three markets. They were chosen for consistency across SOL, ETH and BTC rather than for the best result on any one of them — a parameter set fitted per market on three markets and ninety days of data would be curve-fitting with extra steps.
Venue and leverage
This is the only strategy that trades perpetuals: SOL-PERP, ETH-PERP and BTC-PERP on Phoenix, rather than spot. Two separate numbers are often confused here, so both are stated.
- Position leverage is capped at 2.0x, enforced by the risk gate itself rather than read from configuration — configuration is validated, but validation is bypassable and a ceiling that matters should not be. In practice the sizing rule keeps notional at or below the $10 allocation, so the position sits at 1x of allocation or less.
- Collateral transfer is a separate setting: opening a position moves notional ÷ 1.5 of collateral to the perp account, so a full-size $10 position posts about $6.67. The reason is mundane and was found live — asking to transfer the entire balance fails when fees have eroded it by a few cents, and an open that starves on rounding is worse than one with headroom.
- Orders sent to Phoenix are checked instruction by instruction against the Phoenix program before anything is signed, and simulated before broadcast.
Risk behaviour
- The stop is the risk control. A 4 × ATR trail is loose by design: at a typical ATR it lets a position give back a meaningful fraction of an unrealised gain before closing. That is the cost of not being shaken out of a real trend, and it was widened from 3 × for exactly that reason.
- Stops are evaluated on the four-hour close. An excursion that breaches the stop level intrabar and recovers before the bucket closes does not trigger it; an excursion that keeps going is exited at the close, not at the stop level. The stop is a decision rule, not a resting order at the venue, and four hours is a long time to hold a leveraged position between decisions.
- Volatility scaling only ever reduces. A calm market does not produce a larger position.
- Stale prices. Bars older than 90 seconds at receipt never reach the strategy, so a degraded feed produces no orders rather than orders on old prices.
- Daily loss floor. Equity below 95% of the day's opening equity pauses the bot — but an order that genuinely reduces the open position still passes, because pausing a bot with a live perp position open and then refusing every attempt to close it is how a bad day becomes a catastrophic one.
- Short exposure is bounded, and currently zero. With
allow_short: falsethe runner clips every sell to the position on hand, so the book cannot go negative at all. When shorts were enabled the bound was 1x of equity, applied before the gate's 2x ceiling was even consulted.
Failure modes
Chop is the tax. A market that oscillates enough to keep flipping the EMA cross and poking through the Donchian channel produces entry-and-stop round trips that each pay two fills for nothing. This is the normal state of the strategy between trends, and it is where most of its losing trades come from. Requiring both filters reduces the frequency, and the four-hour resample reduces it a great deal further; neither removes it.
A restart costs 15.3 days. The buffer of one-minute bars is held in memory only. It is not written to a snapshot and nothing rebuilds it from stored candles, so a restarted bot must accumulate 92 fresh four-hour groups before it evaluates anything — 15.3 days during which it is flat, in cash, and holds no view whatever the market does. At the previous fifteen-minute cadence this cost 15.5 hours. It is the same code with a consequence twenty-four times longer.
The volatility estimate is noisy early. It shares the slow EMA's 90-bar span, so for weeks after a restart the denominator in the sizing rule is estimated from little data. An underestimate of volatility produces a full-size position in a market that does not deserve one.
Half of this strategy has never been backtested, so that half is switched off. The backtest engine is long-only at the ledger — it clips a sell to the position on hand — so a short entry from a flat book fills nothing while the buy that closes it still transacts. Enabling shorts in the backtest config does not model them, it produces phantom longs and a ledger that disagrees with the strategy's own position, so the configs stay long-only and the curve stays coherent. That left the short side of the live strategy covered by no equity curve on this site, while the live bots took shorts anyway — a hole in the backtest gate every other bot clears. On 11 August 2026 the three momentum bots were set to allow_short: false. The strategy is still long/short in the code and the table above still describes it; what is disabled is the live short entry, until the engine carries a signed position and a published curve covers that behaviour. Perpetual funding payments are not itemised either. The curve is labelled hypothetical for reasons beyond the usual ones.
Gap risk. Exits are decisions taken at a four-hour close on a leveraged instrument. A fast move against an open position is realised at the close after it, not at the level the stop nominally sat at — and the wait for that close is now up to four hours rather than fifteen minutes. On a perp this is the failure mode with the widest tail, and the retune widened it.