Audits › chrisleekr/binance-trading-bot

Open-source trading bot · audit

chrisleekr/binance-trading-bot

Automated Binance trading bot with pluggable strategies, historical backtesting, and a live dashboard

Repository facts

Repository
chrisleekr/binance-trading-bot
Stars
5,558
Forks
1,168
Open issues
4
Language
TypeScript
License
Apache-2.0
Created
2020-08-10
Last push
2026-09-17
README names exchanges
binance
README mentions an LLM
yes
Backtesting mentioned
yes
Paper trading / dry run mentioned
no

Source: GitHub API, 2026-09-17. Exchange and LLM mentions come from a keyword scan of the README, not from running the bot.

Risk-control audit

Audited 2026-09-17 · main @ 6f278

21 of 21 controls checked: 13 present, 4 partial, 2 absent, 2 not applicable, 0 not verified.

Main risk
Binance API keys and notifier credentials are stored unencrypted in Postgres (SECURITY.md, explicitly acknowledged and out of scope for the project's own security fixes) — the practical mitigation is mandatory IP-allowlisting of the key with withdrawal permission disabled, not a code-level control.
Summary
chrisleekr/binance-trading-bot is a self-hosted, open-source (Apache-2.0, ~5,558 GitHub stars) automated trading bot for Binance Spot, with pluggable strategies (trailing-trade grid, momentum, rebalance), a Postgres+Redis backend, and an optional LLM-based backtest advisor that is strictly advisory and never touches live trading. Risk engineering is unusually mature for an open-source project: automatic daily-loss/loss-streak/drawdown circuit breakers, exchange-resting protective stops with orphan-order reconciliation, a rate-limit-aware websocket layer that prioritises order traffic, graceful process shutdown, and a rich audit-logged notifier system all have direct code or documentation evidence. The clearest weakness, acknowledged by the project itself, is that Binance API keys and notifier credentials are stored unencrypted in Postgres; the documented compensating control is to IP-allowlist the key and never grant it withdrawal permission. Two checklist points (whether momentum/rebalance strategies share trailing-trade's per-order budget cap, and the exact scope of the account-level risk-cap guard) could not be fully confirmed within this session's budget and are marked parcial.
ControlVerdictEvidenceNote
Size and exposure
Maximum order sizePresentpackages/strategy/trailing-trade/src/feasibility.ts (checkTTOrderFeasibility)Every grid level's maxPurchaseAmount or the single fixed entry amount (quote-currency budgets set by the operator) is validated against Binance's live minNotional/minQty at save time via the same finalise()/parseFilters() sizing the live tick uses, so a config that passes cannot silently drift; percentOfAccount orders are risk-sized against equity/stop-loss and are explicitly left unchecked by this pre-check per the file's own comment.
Execution paths that skip the capPartialapps/api/src/lib/order-feasibility.ts (dispatches via the shared AnyStrategy contract's checkOrderFeasibility); GET /search/code filename:feasibility.ts repo-wide returns only 2 files (the generic dispatcher + trailing-trade's implementation)Trailing-trade's cap is confirmed. Did not verify within session budget whether the momentum and rebalance strategy packages implement the same checkOrderFeasibility contract member with an equivalent budget cap, or route through it at all.
Total exposure and leveragePartialapps/web/src/features/backtest/lib/decision-breakdown.ts, code comment: "regime / risk-cap / discovery guards from the grid path"The comment names a risk-cap guard as an entry veto category distinct from technical/indicator gates, indicating an exposure-limiting mechanism exists for the grid path. Did not open the risk-cap guard's own implementation file within budget to confirm its unit (max positions, % exposure, leverage) or whether it is per-symbol or whole-account.
Brakes and stops
Global circuit breakerPresentdocs/concepts/notifiers.md (daily-loss-halt, loss-guard-halt rows); docs/operations/kill-switch.md ("automatic entry breakers" paragraph)Daily-loss circuit breaker, loss-streak guard, and drawdown guard all trip automatically and pause new buys on their own; kill-switch.md explicitly states these are "a separate mechanism from the kill switch", i.e. automatic, not manual.
Orphaned stop-lossPresentdocs/user-guide/account/orphan-orders.mdProtective stops are real resting orders on Binance's book, not in-memory state; they are recoverable after a restart via a deterministic clientOrderId scheme (documented as a "derivable id" for both trailing-trade and momentum), and a background orphan-orders-detect cron (every 10 min) re-associates any order the DB lost track of back to its owning profile.
Orderly shutdownPartialpackages/core/src/shutdown/index.ts (installGracefulShutdown); docs/operations/kill-switch.md ("What it does — and does not — do")SIGTERM/SIGINT are handled: every process's registered shutdown runs via Promise.allSettled and the process exits only once all drains finish (no shutdown races between api/worker/study when co-located). However no shutdown path cancels resting orders on Binance — kill-switch.md states explicitly orders "stay live until they fill or you cancel them" — by deliberate crash-only design (recovery relies on the orphan-order reconciliation loop on restart, not on cancel-on-stop).
Connection and operation
Websocket and data reconnectionPresentapps/worker/src/boot/builders/market-data.tsSingle combined-stream WebSocket + REST fallback (fetchClosedKlines), with a Redis-backed per-IP weight governor (budget 6000/min = Binance's real spot REST ceiling, 80% utilisation headroom) that reserves the top 8 weight units for order placement/cancellation ahead of bulk-read crons, so a protective SELL is never rate-limited behind a discovery/technicals scan; the governor fails open for orders and closed for bulk reads if Redis is down. A "reconnect resync" is referenced as late-bound in the fleet builder but that specific file was not opened within budget.
Duplicate-process lockPartial.env.example / docs/_generated/config/env.md, WORKER_REPLICAS entryWORKER_REPLICAS is documented to "keep at 1: multi-replica is not yet enabled (epic #561 scale-out plumbing merged but the propagation seams stay dormant at one replica)"; boot only validates the value is a positive integer (0 or non-integer aborts), it does not refuse a value >1. The README's claim of "a version-aware per-symbol state commit" (DB-level optimistic concurrency) suggests duplicate-instance order collisions may be mitigated at the data layer, but that commit-versioning code was not opened within budget to confirm.
Heartbeat and alertsPresentdeploy/README.md step 9; docs/_generated/config/env.md (ADMIN_PORT); docs/concepts/notifiers.md (alive, discovery-health, job-failed rows)/healthz, /readyz (checks DB+Redis ping) and /metrics are exposed on a separate admin listener (default 127.0.0.1:9100). Notifier categories include a recurring "alive" balance/holdings digest, a "discovery-health" staleness alert, and an account-wide "job-failed" alert for dead-lettered background jobs, sent to Slack/Telegram/webhook.
Accounting and reconciliation
Position reconciliation with the exchangePresentapps/worker/src/queues/pipeline-handlers/reconcile-fees.ts; docs/user-guide/account/orphan-orders.md (orphan-orders-detect cron)A background cron diffs Binance's live open-order list against the local orders table every 10 minutes and surfaces/adopts mismatches (orphan orders); a separate worker pipeline handler reconciles fees.
Fees and funding in the accountingPresentpackages/db/migrations/0093_trade_archive_fee_basis.sql; apps/worker/src/queues/pipeline-handlers/reconcile-fees.ts; LIVE_DEMO description in docs/_generated/config/env.md (blocks the "fee-reconciliation" route)A dedicated fee-basis column/migration and a fee-reconciliation worker job exist. Funding-rate handling for perpetuals is not applicable: every source read this session (deploy prerequisites, exchanges_soportados) points to Binance Spot only, no futures/perp endpoints found.
Order rejection handlingPresentdocs/concepts/notifiers.md, order-failed rowExplicit, bounded backoff: repeated failures for the same symbol collapse into one alert per 15-minute window; a non-recoverable placement refused three times identically triggers a dedicated alert that the bot has throttled that exact request to one probe per minute — not unbounded retries.
Auditable logPresentdocs/operations/kill-switch.md ("Every on and off is written to the profile's audit log"); apps/api/src/routes/kill-switch.ts (c.set('auditEvent', {...}) on every route)Kill-switch and symbol-pause actions are written to a persistent per-profile audit log with event name, target and timestamp; the broader notifier event taxonomy (order-filled, order-failed, override-unresolved, etc.) implies persisted records beyond the push notification itself.
Security
API key permissionsPresentSECURITY.md ("Compensating control" section); deploy/README.md step 8 ("IP-allowlist your Binance API key")Documentation explicitly instructs: restrict the key to the server's IP and "do not enable withdrawal permission"; only "Spot & Margin Trading" is asked to be checked. Whether any code path ever calls a withdraw/transfer endpoint was not individually verified within budget (a repo-wide search for "withdraw" returned 16 hits, mostly documentation, not opened one by one).
Secrets handlingAbsentSECURITY.md, verbatim: "Secrets are stored unencrypted. Binance API keys and notifier credentials live in plaintext in Postgres. There is no encryption at rest."This is the project's own documented, deliberate design choice, explicitly marked "out of scope" for security fixes. Infra-level secrets (AUTH_SECRET, Postgres/Redis passwords) are handled well — generated with openssl and delivered via Docker secret files or .env, never hardcoded — but the actual trading credential, the Binance API key, has no encryption at rest: anyone with DB access, a DB backup, or host filesystem access has it.
Authenticated remote controlNot applicableSECURITY.md ("Authentication is a single operator account. No email verification, no second factor."); apps/api/src/routes/kill-switch.ts (requireUser() middleware); docs/concepts/notifiers.mdAll control actions found are authenticated web/API routes behind a single-operator session, not a chat-bot command surface. Slack/Telegram/webhook are documented and coded as outbound-only alert channels; no inbound command handling for any of them was found this session, so there is no remote-control attack surface to rate as present or absent.
AI and learning
The model cannot override the brakesPresentapps/web/src/features/backtest/components/backtest-llm-advisor.tsx (doc-comment: "Nothing touches live config, and the out-of-sample gate still decides go-live"); apps/web/src/features/backtest/lib/decision-breakdown.ts (doc-comment: suggestions "never bypass the bearish technical-rating veto")The LLM has no write path to live trading. Every suggestion requires a human to load it into the Setup form, re-run a backtest, and clear an out-of-sample gate before it can reach a live profile; it can also only REMOVE a whitelisted entry constraint, never widen sizing or bypass the core veto gate.
Model output validationPresentapps/web/src/features/backtest/lib/decision-breakdown.ts (ConfigRecommendation.apply as a pure transform over the same TTConfig schema)AI suggestions compose onto a schema-valid base config through pure, whitelisted patch functions and must still pass the same schema/feasibility validation as any manual edit before they can be saved or backtested — a malformed or hallucinated suggestion cannot become an arbitrary order.
Learns from its mistakesAbsentapps/web/src/features/backtest/components/backtest-llm-advisor.tsx ("On-demand config advisor for a finished run"); docs/concepts/notifiers.md, edge-decay-warning rowThe advisor is triggered manually per finished backtest, not automatically. The one related signal, edge-decay-warning ("live results fall below the pinned backtest baseline"), is explicitly "Advisory only — the bot does NOT pause buys" and does not feed back into config automatically. No automatic retraining or unsupervised self-correction loop was found.
Model poisoningNot applicableapps/web/src/features/backtest/components/backtest-llm-advisor.tsxThe only LLM integration found this session consumes the operator's own backtest run data and config (first-party), not third-party text. No news/tweet/sentiment ingestion into any model was found in the docs or code read this session, though the full ~900-file tree was not exhaustively searched within budget.
Behavior without model quota or responsePresentapps/web/src/features/backtest/components/backtest-llm-advisor.tsx (errorNote() function; "needs a configured AI provider; 503 → inline note")A missing provider, quota exhaustion, or model failure surfaces as an inline note ("AI suggestions are not configured" / "The AI couldn't generate suggestions for this run. Try Regenerate.") with no crash, and — because of decision_ia_filtrada above — has zero effect on live trading regardless of availability.

Audit summary: chrisleekr/binance-trading-bot

**What it is:** A free, open-source, self-hosted bot (Apache-2.0 license, ~5,558 stars on GitHub) that automatically buys and sells crypto on your behalf. You run it on your own server, give it a Binance API key, and it trades three strategies you can pick from: a grid ("trailing-trade"), a momentum strategy, and a simple rebalancer.

**Which exchange:** Binance Spot only, live or testnet (paper trading). No futures, margin, or other exchanges.

**Audited:** the `main` branch, snapshot `6f2788e6...` (checked out 2026-09-17), read-only via GitHub's public API — nothing was installed or run.

What's solid

What's missing or weak

Bottom line

This is a well-engineered, actively maintained project with real safety mechanisms most hobby trading bots lack — automatic loss limits, crash-resistant stops, and an AI feature that can't touch live money. Its main risk isn't in the trading logic; it's that your exchange credentials sit in the database unprotected, so the operator must be disciplined about IP-locking the API key and keeping withdrawals disabled on Binance's side.

**Snapshot audited:** `main` branch, commit `6f2788e6...`, 2026-09-17.

Badge for the README

risk controls audit: 13/21 present

<a href="https://saasfactoryagents.com/bots/chrisleekr-binance-trading-bot/"><img src="https://saasfactoryagents.com/bots/chrisleekr-binance-trading-bot/badge.svg" alt="risk controls audit: 13/21 present"></a>

Fixed a control? Request a free re-audit at the new commit.

Exchanges

The README does not name Binance.US, Coinbase, Kraken or Robinhood.

Also named in the README: binance.

Running binance-trading-bot with real money?

The watchdog runs apart from the bot with a read-only key and alerts you when drawdown, position size or heartbeat cross your limits. Founding price 19 USD/month, early access open.