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
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.
| Configuration | OOS return | Sharpe | Max DD | Trades |
|---|---|---|---|---|
| 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 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–2026 | Sharpe, 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 weight | Selected cell | Buy & hold |
|---|---|---|
| Sharpe | +1.13 | +1.32 |
| Total return | +150% | +2472% |
| Max drawdown | −30% | −89% |
| Exposure · correlation with buy & hold | 39% · +0.58 | 100% · — |
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 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%.
python -m near_bot walkforward --plot.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:
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.
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.
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.
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.
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.
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.
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.