Skip to content

silentcall/sisyphus

Repository files navigation

Sisyphus

The boulder always rolls back down. We profit on the way.

test license: MIT

A self-hosted, fee-aware grid trading bot for Binance Spot, running on your own PC. Static-range grid, maker-only orders (LIMIT_MAKER), with a deterministic two-layer regime gate that pauses trading in hostile conditions and hard-halts on real danger.

Honest framing: this is a validation and learning project, not an income system. Grid trading harvests volatility and loses money in strong trends. The gate logic is deterministic risk management, not prediction. There is deliberately no martingale or double-down logic, and there never will be.

Authoritative design document: BUILD_SPEC.md (includes the v2 measurement/upgrade sections, BUILD_SPEC.md §12–16).

Status

All ten base build steps (BUILD_SPEC.md Section 10) plus the full autopilot and v2 measurement layer (Sections 11–16, steps 11–22) are implemented and tested — 270 passing tests including the kill/restart chaos suite, the rotation state machine, and a 6-agent correctness review with every critical/high finding fixed (see CODEBASE_REVIEW.md). Stage B autopilot (automatic rotation) is active on testnet. Live trading remains locked behind the promotion gates in Section 9useTestnet stays true until a human deliberately flips it.

Known limitation: commissions paid in an asset that's neither the pair's quote nor base currency (e.g. BNB, if the fee discount is active) are not converted to quote value for PnL — the bot warns the first time it sees this, and PnL is slightly overstated until it's addressed. Separately, the bot cross-checks the BNB discount configuration (feePctPerSide) against the actual fee asset on the first live fill and warns loudly on any mismatch.

Requirements

  • Node.js ≥ 22
  • A Binance account (start on the Spot Testnet — the bot defaults to testnet and stays there until you explicitly flip it)

Creating your API key (read this before anything else)

These steps are non-negotiable. A key created wrong is the single biggest risk in running this bot.

  1. Testnet first. Create a key at https://testnet.binance.vision and complete validation Phase 1 (see below) before you even think about a live key.
  2. When (and only when) you create a live key:
    • Enable Spot trading only. Do not enable margin, futures, or transfers.
    • Withdrawals: DISABLED. The bot never needs them; a leaked key must not be able to move funds off the exchange.
    • IP whitelist: restrict the key to the public IP of the machine running Sisyphus. An un-whitelisted key is disabled by Binance after 90 days for good reason.
  3. Copy .env.example to .env and fill in the key and secret. .env is git-ignored from the first commit — keep it that way.
  4. If a key ever ends up in a commit, a log, or a paste: revoke and rotate it immediately. History rewriting is not sufficient.

Fees matter more than you think

Every grid cycle pays two maker fees (buy + sell). At Binance's standard 0.10% per side that is 0.20% round trip — a meaningful bite out of a 1% grid step. Enable the BNB fee discount (hold a small BNB balance and turn on "Use BNB for fees"): 0.075% per side instead of 0.10%. The startup spacing check enforces that your grid step is at least minSpacingFeeMultiple × the round-trip fee, and will refuse to start otherwise. The bot nags at startup while feePctPerSide is above 0.00075, and the first real fill cross-checks the actual fee asset against the config — a mismatch in either direction is warned about loudly.

Zero-fee pairs (13.1): Binance periodically runs zero-maker-fee promos (historically FDUSD-quoted). Point the scanner at them with "quoteAsset": "FDUSD" and declare the promo fee per symbol: "feeOverridesBySymbol": { "BTCFDUSD": 0 }. A zero fee collapses the fee-derived spacing rule, so an absolute floor takes over: steps can never go below minStepAbsPct (default 0.15%) — below that, spread and tick noise eat the cycle no matter what fees are. The scanner scores each symbol at its own fee-adjusted sMin, so a 0-fee pair with modest oscillation can legitimately outrank a fee-paying mover.

Setup

The interactive wizard asks for everything (mode, symbol, grid range with a suggestion from the live price, sizes, fees, Telegram, API keys with hidden input), validates with the same rules the bot enforces at startup, and writes gridbot.config.json + .env (created with 0600 permissions):

npm install
npm run setup

Prefer doing it by hand? The equivalent is:

cp .env.example .env   # then fill in your credentials
cp gridbot.config.example.json gridbot.config.json

Edit gridbot.config.json and set lowerBound / upperBound around the current market price — they are the only required fields and have no defaults. Every other key is optional and falls back to the spec defaults (BUILD_SPEC.md Section 5). Unknown keys are rejected on purpose, so typos fail loudly instead of silently using a default.

Validation refuses to start (listing all problems at once) if, among other things:

  • the grid step at the top of the range is less than minSpacingFeeMultiple × the round-trip fee — a grid tighter than this pays more in fees than it earns per cycle;
  • numGrids × quotePerOrder exceeds totalQuoteCapital;
  • bounds are missing, inverted, or any value is out of range.

At startup the bot logs a 12-character config fingerprint so every run is attributable to an exact configuration. useTestnet defaults to true; going live is an explicit, deliberate edit.

Running

npm start          # the Sisyphus app: a menu for Setup / Start / Flatten / Events —
                   # nothing to remember, arrow keys + Enter
npm run bot        # run the trading engine directly (dashboard in a TTY,
                   # headless logs otherwise — e.g. under nohup)
npm run flatten    # standalone kill switch: cancel ALL open orders and exit.
                   # Works even if the main process is dead.
npm run report     # performance report: cycles, PnL, banked profit
npm run scan       # autopilot Stage A: score candidate pairs, health-check the
                   # incumbent, print the R1-R6 rotation checklist
npm run rotate     # switch pairs via the crash-safe rotation state machine
                   # (-- --to SYMBOL to override the scan's pick,
                   #  -- --rerange to keep the symbol and re-center the range,
                   #  -- --dispose to authorize maker-only inventory disposal)
npm run lab        # shadow lab: N parameter variants trading the same live
                   # stream in parallel, one DB per variant (see below)
npm run backtest   # replay historical 1m klines through the same variants
                   # (-- --days 30); ranks parameters incl. trend periods
npm test           # unit tests + chaos suite

Switching pairs: never just edit symbol in the config — the exchange still holds the old pair's orders and inventory, and startup will refuse. npm run rotate does flatten → reconcile → reconfigure in the right order and survives crashes at any step (rerun it to resume). The setup wizard warns if you try the config-only route.

Autopilot (Stage B): with "autopilotEnabled": true (requires "pauseBuysOnRangeExit": true), the engine scans every scanIntervalHours and rotates automatically when ALL of R1-R6 pass; otherwise it stays and logs why. When rotation says stay but the range has merely gone stale (price out of range ≥33% of the day, trend filter green, inventory small), it re-ranges in place instead (13.3) — same crash-safe machine, same symbol, new bounds. Re-ranges have their own dwell (48h) and budget (4/30d); a near-halt unrealized loss always blocks re-ranging, because a drifted range with heavy inventory is a trend symptom, not a stale range. When the incumbent has been unhealthy ≥ 72 h but a rule blocks rotation, it sends one Telegram alert per 24 h naming the blocker. The dashboard and the 30 s status line show the next scan time and the last decision.

Dashboard keys: k twice within 3 s = kill switch (cancel all, halt). q = graceful shutdown (resting orders are left on the exchange — the screen says so).

Startup refuses to run unless everything checks out: credentials present, config valid (fingerprint logged), quotePerOrder ≥ 1.5× the symbol's minNotional, price inside the grid range with at least one grid step of margin, and local DB reconciled against the exchange. A reconciliation mismatch that can't be repaired mechanically prints a full diff and refuses to trade.

With no TTY (e.g. nohup), the dashboard is skipped and a status line is logged every 30 s instead. State lives in gridbot.db (gridbot.shadow.db in shadow mode); deleting it is safe only while no orders are resting.

Troubleshooting: everything the bot does is appended to gridbot.log (git-ignored) — startup checks, reconcile reports, every order/gate/risk/ws event, a status heartbeat every 30 s, halts with reasons, flatten runs, and refusals to start. One timestamped line per event, so grep halt gridbot.log or grep -c "\[cycle\]" answer most questions. It rotates at 5 MB (one .1 generation kept) and never contains credentials.

Shadow lab (BUILD_SPEC 12.2): cp lab.config.example.json lab.config.json, edit the variants (config overrides per label — bounds, numGrids, quotePerOrder; symbol is pinned to the base config), then npm run lab. Each variant runs the full engine in shadow mode against the same live stream and writes to its own gridbot.shadow.<label>.db. It is read-only with respect to the exchange and safe to run alongside the real bot. npm run lab -- --report prints the daily comparison (net, cycles, capture ratio per variant). Decision rule, fixed in advance: a variant must beat the incumbent's net PnL over ≥ 14 days AND ≥ 30 completed cycles before its parameters are adopted — and adoption is a manual config change. Caveat: shadow fills are simulated by price-cross and ignore queue position, so fill rates are slightly optimistic — uniformly across variants; valid for comparing them, weak for projecting income.

Try it without keys: set "shadowMode": true in gridbot.config.json and use any dummy values for the Binance env vars — shadow mode runs the full engine against live market data and only stubs the final order call, simulating fills (with fees) when price crosses a level.

Security posture

  • Credentials via environment variables only — never logged, never stored in the database, never in error messages.
  • No inbound network surface: no web server, no open ports, no remote control. Telegram is outbound-only notifications.
  • Exactly four pinned runtime dependencies (better-sqlite3, ink, react, ws), lockfile committed, npm audit run locally. No third-party Binance wrapper — signing is done directly with node:crypto.
  • Market data is trusted only from Binance's own endpoints and streams. No third-party feeds, no webhooks, no signal inputs.

Validation phases (promotion gates — do not skip)

Phase Environment Duration What must pass
1 Binance Spot Testnet 1–2 weeks Full order lifecycle, clean kill/restart reconciliation ×10, all halt paths fire and alert
2 Shadow mode on live data 1 week Realistic fill/gate statistics; gate goes red during a genuinely volatile period
3 Live micro ($10 orders, ≤$120 total) 4+ weeks Zero reconciliation mismatches; honest net-of-fee accounting (profit not required — correctness is)
4 Scaling discussion Only after 1–3 pass. Separate decision.

Disclaimer

You run this at your own risk, with money you can afford to lose entirely. Nothing here is financial advice.

About

Sisyphus — a self-hosted, fee-aware grid trading bot for Binance Spot. The boulder always rolls back down. We profit on the way.

Topics

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors