ANIKA Trading Bot — Runbook

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.

The three strategies (one line each)

Paper mode is the default — what that means

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.

Backtest results (real numbers, 2026-06-23 → 2026-09-21, ~90 days hourly)

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.

How it runs on schedule

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)

How Leonard funds the bot wallet (when the day comes)

The bot wallet does not exist yet — no keystore.json, no key material anywhere in this repo. Activation is a deliberate, Leonard-only sequence:

  1. Secure Vault captures a keystore password — set by Leonard on the capture page, never in chat.
  2. Generate the keystore: BOT_KEYSTORE_PASSWORD=<from vault> node scripts/keystore.mjs generate — this prints the public address only. No private key is ever displayed or logged.
  3. Leonard funds it himself — he sends a small amount of ETH/USDC to that address from his own wallet. His main wallet is never touched by the bot.

How live mode gets approved

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.

Kill switch

Three ways, all checked before every cycle:

  1. Dashboard button — opens a panel with a one-tap copy of the phrase 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.)
  2. Local: node scripts/kill.mjs — writes the KILL file and sets the remote BrewPage KV flag to KILLED. Re-arm with node scripts/arm.mjs.
  3. Message the agent: kill the bot.

When engaged, the runner logs KILLED — halting and exits. Verified 2026-09-21.

Gas costs

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.

Custody tradeoffs (honest)

Risk limits (authoritative — config.json)

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.

File map

~/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)

Links