near-momentum-bot v2
Binance spot · backtesting · walk-forward validation

An honest account of what a trend engine can and cannot prove.

This project exists to demonstrate cost-aware backtesting and walk-forward validation done honestly — including the part where the honest answer is "not distinguishable from noise." Its one robust finding: on 91 days of real data, the v1 4-minute scalper lost 82% out-of-sample — the edge never survived 0.24% round-trip costs. v2 is the disciplined response: a slower, volatility-targeted trend engine, evaluated with the statistical caution the small sample demands. On six years of data across eight symbols, the honest answer stayed the same — the multi-year verdict.

no API key needed for simulation fees + slippage on every fill walk-forward out-of-sample only standard errors stated, not hidden MIT licensed

Validation discipline, not a performance claim

Read this as a worked example of honest evaluation, not as evidence that this strategy makes money. The 91-day sample below is now the trailer, not the movie: far too small to conclude anything on its own, it stays here to show how to see that, rather than hide it. The movie — six years, eight symbols — follows the caveats.

Setup: 91 days of real NEARUSDT 1-minute data (mid-April → mid-July 2026), aggregated to 60-minute bars, with Binance taker fees (0.10%/side) and slippage (0.02%/side) charged on every fill. Walk-forward re-optimises a deliberately small grid on rolling 30-day windows and reports only the unseen 10-day windows that follow.

ConfigurationOOS returnSharpeMax DDTrades
v1 scalper @ 4m (the old bot)−82.4%—−82.5%714
v1 scalper @ 60m (same strategy, slower)−9.4%—−17.0%39
v2 @ 60m — walk-forward OOS+10.7%+2.84−7.5%38
v2 @ 60m — 2× cost stress+13.3%+3.54−5.9%36
v2 @ 60m — ETHUSDT (held-out symbol)+5.8%+2.15−7.5%26
v2 @ 60m — SOLUSDT (held-out symbol)+0.3%+0.18−5.7%35

Read the +10.7% row together with the caveats below — on this sample it is one draw from a wide distribution, not evidence of skill. Every figure is reproducible in two commands (see Quickstart), including the ones that undercut the strategy.

The numbers above are statistically inconclusive, on purpose

Six years later: the multi-year verdict

The caveats above named what a real claim would need: multi-year data, bootstrapped confidence intervals and a deflated Sharpe ratio. That run is done — NEAR, ETH, SOL, BTC, BCH, DOGE, ADA and LINK 1-minute klines from 2021-01-01 to 2026-09-30 (SHA-256-verified Binance dumps), walk-forward on two window sizes, 10,000-resample block-bootstrap confidence intervals, independent decisions counted, and a deflated Sharpe charged against the trials ledger. All eighteen symbol × window blocks: not statistically distinguishable from noise.

Walk-forward OOS, 2021–2026Sharpe, 90d/30d windows [95% CI]Sharpe, 30d/10d windows [95% CI]Verdict
NEARUSDT (production)+0.35 [−0.50, +1.17]+0.53 [−0.27, +1.28]noise
ADAUSDT+0.28 [−0.61, +1.11]+0.61 [−0.22, +1.43]noise
BCHUSDT−0.22 [−1.17, +0.63]−0.21 [−1.10, +0.60]noise · a loss (−34% / −35%)
DOGEUSDT+0.68 [−0.28, +1.54]+0.89 [−0.02, +1.71]noise · closest call (30d/10d)†
ETHUSDT+0.27 [−0.62, +1.13]+0.42 [−0.47, +1.29]noise
LINKUSDT+0.47 [−0.35, +1.24]+0.24 [−0.57, +1.02]noise
SOLUSDT+0.47 [−0.37, +1.26]+0.75 [−0.12, +1.58]noise
Pooled (7 symbols)+0.52 [−0.36, +1.36]+0.70 [−0.16, +1.53]noise
BTCUSDT (context stream)−0.20 [−1.20, +0.75]+0.36 [−0.56, +1.24]noise

Every Sharpe and annualised-return confidence interval includes zero. Independent decisions per block: 288–464; engine max drawdowns −22% to −61% (the pooled blocks −22% / −25%; per symbol −29% to −61%, BCH worst). BCH lost money on both window sizes: total return −34% / −35%, max drawdown −60% / −61%. † DOGE 30d/10d is the closest call: a deflated-Sharpe probability of 0.949 against the 0.95 gate, and the one per-decision expectancy CI that excludes zero — but its Sharpe CI [−0.02, +1.71] still includes zero. Full report: reports/validate.md.

The obvious next question — could better parameters rescue it? — got a pre-registered answer: 125 configurations on 2021–2024 (NEAR/ETH/SOL), with the grid, the selection rule and the trials-ledger charge committed before any configuration was scored. It selected the corner cell theta_in=0.15, trail_atr=4.0, er_min=0.1: in-sample Sharpe +1.13 [95% CI +0.15, +2.07], but a deflated-Sharpe probability of 0.69 against the required 0.95 after charging for 219 configurations — not established. The corner is the tell: the search converged toward the loosest filters, i.e. toward more time in the market, and on the same bars it trails plain buy-and-hold.

2021–2024, pooled equal weightSelected cellBuy & hold
Sharpe+1.13+1.32
Total return+150%+2472%
Max drawdown−30%−89%
Exposure · correlation with buy & hold39% · +0.58100% · —

Post-hoc descriptive context, not used for selection. Lower Sharpe than buy-and-hold on every symbol, with a much shallower drawdown at ~39% exposure: a partially invested version of the same market exposure, not timing skill. Full report: reports/plateau_hunt.md.

The holdout (2025-01-01 → 2026-09-30) was deliberately not fired: a selection that fails its own pre-registered gate does not get to spend it. It stays sealed for a future strategy family — as a viewed holdout, not a pristine one, since those months are part of the validation run above.

The conclusion actually defended

The v1 scalper is decisively dead (−82% out-of-sample, a robust finding). v2 is not distinguishable from noise — first on 91 days, then on six years across eight symbols, with the bootstrapped confidence intervals and deflated Sharpe the 91-day sample lacked. A pre-registered search for better parameters converged toward market exposure and trails buy-and-hold.

The deliverable here is the machinery — and the discipline to reach that conclusion honestly — not the +10.7%.

Walk-forward out-of-sample equity curve on real NEARUSDT data
Walk-forward out-of-sample equity, drawdown and return distribution — real NEARUSDT data, net of all costs. Generated by python -m near_bot walkforward --plot.

What changed, and why it matters

v1 AND-ed six binary conditions into a 4-minute entry signal. Binary gates are brittle (nudge one threshold and trades flip on/off — the signature of curve-fitting), and the 4-minute timeframe made fees mathematically unbeatable. v2 keeps the two good ideas (order flow from taker-buy volume, walk-forward discipline) and rebuilds everything else:

1. A score, not a checklist

Conviction is a continuous blend in [−1, 1]: volatility-normalised time-series momentum (fast + slow), Donchian breakout position, and smoothed taker-imbalance order flow. Signal strength now means something, and parameters move results smoothly instead of flipping individual trades.

2. Regime gates

A Kaufman efficiency-ratio filter refuses to trade chop, and a 200-bar trend filter refuses to buy downtrends. On 90 days of real data the trend filter alone moved a falling asset (SOL) from −5.9% to +0.3% out-of-sample: you cannot buy your way out of a downtrend, but you can decline to try.

3. Hysteresis — the cost-aware band

Enter when the score crosses θin, exit when it fades below θout < θin. The band between them is the no-trade region where expected edge is smaller than round-trip cost. This is where most scalping systems donate their P&L to the exchange.

4. Volatility-targeted sizing

Position weight = target annualised vol ÷ realised vol, capped at 100%. Quiet markets get bigger positions, wild markets smaller ones — risk stays constant across regimes, which is what makes risk-adjusted comparisons between periods meaningful.

5. A timeframe where costs stop being decisive

Costs are fixed per trade; trends scale with time. At 4 minutes the average move is a rounding error next to 0.24% round-trip costs; at 60 minutes it isn't. The default grid searches slower bars, not faster ones.

6. Top-k walk-forward ensemble

Instead of trading the single best in-sample parameter set (mostly luck), each window trades the top-3 and averages them — selection variance traded away. On this sample it doubled the OOS trade count while raising OOS Sharpe; the mechanism matters more than the number.

Full reasoning, the failure analysis of v1, the plateau-robustness table and the named statistical gaps are in docs/STRATEGY.md and the roadmap.

Reproduce every number

git clone https://github.com/ysmouhib/near-momentum-bot.git
cd near-momentum-bot
pip install -e ".[dev,plot]"

# 1. Download a fixed window of real 1m klines (no API key; Binance public dumps)
python scripts/download_data.py --start 2026-04-17 --end 2026-07-17

# 2. Fixed-config backtest (optimistic upper bound)
near-bot backtest --csv data/NEARUSDT_1m_20260417_20260717.csv --trades --plot reports/backtest.png

# 3. Walk-forward validation (the number that actually matters)
near-bot walkforward --csv data/NEARUSDT_1m_20260417_20260717.csv --plot reports/oos.png

# 4. The validation battery over every downloaded symbol of the window
#    (bootstrap CIs, deflated Sharpe, independent decisions, verdict)
python scripts/download_data.py --symbols NEARUSDT ETHUSDT SOLUSDT BTCUSDT --start 2026-04-17 --end 2026-07-17
near-bot validate --start 2026-04-17 --end 2026-07-17 --windows 30:10   # -> reports/validate.md
#    (91 days fit only the 30d/10d variant; the default also runs 90d/30d)

# 5. (Optional) paper trade on the testnet — the overlay starts DISARMED
cp .env.example .env   # paste testnet keys from testnet.binance.vision
export $(cat .env | xargs)
near-bot test-connection
near-bot arm --note "why a directional move is plausible now"
near-bot status        # overlay state + BTC/ETH regime context
near-bot paper

Or skip the terminal entirely: the browser simulator runs the same engine (parity-checked trade-for-trade against the Python code) on live Binance data, with every parameter adjustable — including walk-forward validation in-page.

Fixed-config backtest on real NEARUSDT data with position weights
Fixed-config backtest with buy & hold comparison and the volatility-targeted position weight over time — shown for the mechanics, not as evidence of edge.