PAPER MODE — SIMULATION. This is a prototype, not financial advice. Past performance does not predict future results.
A multi-strategy paper-trading bot for ETH on Base. Three strategies, one paper ledger, one dashboard. No real money moves — every fill is simulated against live/coinGecko ETH prices.
config.json has mode: "paper" and live.enabled: false. In paper mode the runner simulates fills against the paper ledger: no wallets are touched, no transactions are built or sent, no funds move. If mode is ever set to anything else, fills are skipped entirely with "live not enabled" — there is no code path that spends real funds, and no autonomous live spending is wired. Leonard's main wallet is never touched by this system, in any mode.
Assumptions: $1,000 starting per strategy, 0.3% taker fee, slippage not modeled.
| Strategy | Return | Trades | Win rate | Max drawdown |
|---|---|---|---|---|
| Grid | +1.35% | 435 | 59.5% | 1.02% |
| DCA | +49.31% unrealized | 40 | — (never sells) | 9.09% |
| Trend | +0.74% | 69 | 26.5% | 2.18% |
| Buy & hold (ETH) | +58.07% | — | — | — |
Said plainly: buy-and-hold beat all three strategies over this window. These are historical simulations, not predictions.
One command per cycle:
cd ~/workspace/trading-bot && node scripts/run.mjs
Suggested: every 30 minutes. Each cycle fetches the ETH price, asks each strategy for a decision, gates trades through the risk engine, records everything in state.json, rebuilds the dashboard, and PUT-updates the live BrewPage dashboard page in place.
Cron command for the runbook: cd ~/workspace/trading-bot && node scripts/run.mjs (every 30 min)
The bot wallet does not exist yet — no keystore.json, no key material anywhere in this repo. Activation is a deliberate, Leonard-only sequence:
BOT_KEYSTORE_PASSWORD=<from vault> node scripts/keystore.mjs generate — this prints the public address only. No private key is ever displayed or logged.It doesn't run live until Leonard says so, explicitly, per trading window: live.enabled stays false until then. There is no mechanism for the bot to enable live spending on its own, and no live-spend code path exists. One window = one explicit go from Leonard.
Three ways, all checked before every cycle:
kill the bot. (The public dashboard page cannot write the kill flag directly: BrewPage requires the owner key to write, and that key is never embedded in public HTML.)node scripts/kill.mjs — writes the KILL file and sets the remote BrewPage KV flag to KILLED. Re-arm with node scripts/arm.mjs.kill the bot.When engaged, the runner logs KILLED — halting and exits. Verified 2026-09-21.
Paper mode costs nothing. On Base mainnet, a real swap costs fractions of a cent in gas — cheap, but real, which is why this stays in paper mode until Leonard approves a window.
| Limit | Value |
|---|---|
| Max per trade | $50 |
| Daily loss cap | $100 |
| Max drawdown | 15% |
| Starting capital per strategy (paper) | $1,000 |
Config fix 2026-09-21: strategies.trend.positionUsd was 100 but risk.maxPerTradeUsd is 50, so the trend strategy could never trade (the risk gate correctly blocked its $100 buy). Fixed to 50 — the risk limit stays authoritative.
~/workspace/trading-bot/
├── config.json # mode, risk limits, strategy params, dashboard pageId, kill KV key
├── state.json # paper ledger state: accounts, trades, decisions, memory
├── backtest-results.json # historical simulation results (above)
├── dashboard/
│ └── build.mjs # generates dashboard.html (dark, mobile, single file, no external JS)
├── dashboard.html # generated; PUT to BrewPage in place each cycle
├── lib/
│ ├── ledger.mjs # paper-trading ledger (simulated fills only)
│ ├── risk.mjs # risk gates + kill-switch check (KILL file + remote KV)
│ ├── prices.mjs # ETH price + history (CoinGecko, cached)
│ ├── keystore.mjs # encrypted keystore (no key material committed)
│ └── strategies/ # grid.mjs, dca.mjs, trend.mjs
└── scripts/
├── run.mjs # cycle entry point (cron calls this)
├── kill.mjs / arm.mjs # kill switch engage / disengage
├── backtest.mjs # historical backtest
└── keystore.mjs # keystore generate (Leonard's step only)