Audits › jesse-ai/jesse

Open-source trading bot · audit

jesse-ai/jesse

An advanced crypto trading bot written in Python

Repository facts

Repository
jesse-ai/jesse
Stars
8,527
Forks
1,236
Open issues
16
Language
Python
License
MIT
Created
2018-11-09
Last push
2026-09-14
README names exchanges
none found
README mentions an LLM
yes
Backtesting mentioned
yes
Paper trading / dry run mentioned
yes

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 · master @ 432

21 of 21 controls checked: 3 present, 10 partial, 1 absent, 1 not applicable, 6 not verified.

Main risk
The framework's only visible order-size/margin cap is hard-coded to skip itself in real live trading, and the actual live execution/risk logic lives in a closed-source paid plugin (jesse_live) that this audit could not inspect.
Summary
Jesse is an MIT-licensed, self-hosted Python framework (8,528 GitHub stars) for backtesting, optimizing and live-trading crypto strategies, with a web dashboard and 300+ indicators. Its most important structural fact for this audit: the actual live/paper trading execution engine ('jesse_live') is a separate, proprietary, license-gated plugin imported by this public repo ('from jesse_live import live_mode') and is not open source, so most execution-time risk controls (real order-size caps, circuit breakers, reconnection, position reconciliation, order-rejection handling) cannot be inspected and are marked no_verificado or parcial. What IS visible and verified in the public repo shows two clear gaps even for the code that exists: the only margin/order-size check found (FuturesExchange.on_order_submission) explicitly disables itself in live trading ('if jh.is_livetrading(): return'), and exchange/AI-provider API keys are stored in the local database in plain, unencrypted text columns.
ControlVerdictEvidenceNote
Size and exposure
Maximum order sizePartialjesse/models/FuturesExchange.py (on_order_submission: raises InsufficientMargin if effective_order_size > available_margin, but the function starts with 'if jh.is_livetrading(): return', skipping the check entirely in real live trading) + jesse/services/broker.py (_validate_qty only checks qty != 0, no maximum)A margin-based order-size cap exists but only runs in backtest/paper simulation; in real live trading no cap is enforced anywhere in this public repo.
Execution paths that skip the capPartialjesse/services/broker.py routes market/limit/stop/reduce orders all through self.api -> jesse/services/api.py -> the exchange driver, so in backtest every route hits the same on_order_submission check; jesse/exchanges/sandbox/Sandbox.py confirms the simulated driver has one entry point (order_service.create_order)Single code path in backtest/paper is consistent, but since the check itself is disabled in live mode (see tope_orden), the question of 'which live routes skip it' cannot be answered from this repo.
Total exposure and leveragePartialjesse/models/FuturesExchange.py (available_margin sums open positions' cost minus PnL plus pending order cost, divided by futures_leverage, for backtest only) + jesse/config.py (per-exchange 'futures_leverage' and 'futures_leverage_mode' are the only exposure-related settings; no max-total-exposure, max-number-of-positions or max-drawdown key exists anywhere in config.py)No config key limits total exposure or position count across routes. In live trading, available_margin is whatever jesse_live's exchange stream reports (FuturesExchange.update_from_stream), which is unverifiable from this repo.
Brakes and stops
Global circuit breakerAbsentjesse/config.py (searched full file: no daily-loss, drawdown or consecutive-failure threshold key) + jesse/services/failure.py (the only global stop is register_custom_exception_handler / terminate_session, triggered solely by an uncaught exception/crash, not by a loss or drawdown metric)No proactive P&L-based or failure-count-based freeze exists in the audited code; only a crash-triggered hard stop.
Orphaned stop-lossNot verifiedjesse/exchanges/sandbox/Sandbox.py shows that in backtest/paper a 'stop_order' is just an Order object kept in the in-memory store (jesse.store), not sent to any exchange. Whether the real live-trading drivers place a genuine server-side stop order on the exchange is implemented in the closed-source 'jesse_live' package (imported in jesse/controllers/live_controller.py as 'from jesse_live import live_mode'), which is not part of this repository.Cannot verify without access to jesse_live.
Orderly shutdownPartialjesse/services/failure.py: terminate_session() logs the error, publishes an 'unexpectedTermination' event over redis, stores the exception in the DB, and then calls os._exit(1) — there is no call to cancel_all_orders() or any position/order cleanup before the hard exitThe only shutdown path visible in the public repo does not cancel open orders. A user-initiated graceful stop (e.g. from the dashboard) may behave differently but its implementation is inside jesse_live and was not found in this repo.
Connection and operation
Websocket and data reconnectionNot verifiedjesse/config.py only has a logging flag 'env.logging.exchange_ws_reconnection': True (a log toggle, not an implementation). The websocket clients themselves are constructed in jesse/services/api.py via 'jh.get_config(f"app.live_drivers.{name}")', i.e. classes supplied by the closed-source jesse_live plugin.Reconnection/backoff/429 handling logic is not present in this repository.
Duplicate-process lockPartialjesse/controllers/live_controller.py (the 'live' endpoint only rejects a request if a session with the exact same request_json.id already exists and is not in DRAFT status) + jesse/services/multiprocessing.py (ProcessManager tracks one OS process per client_id in Redis, with no dedup by exchange+symbol+route)Two different live sessions (different IDs) trading the same exchange/symbol/route are not prevented anywhere in this code; there is no OS-level lockfile/PID file either.
Heartbeat and alertsPartialjesse/services/notifier.py (notify() queues Telegram/Discord/Slack messages; jesse/services/logger.py calls it on error) + jesse/services/failure.py (sync_publish('exception', …) and sync_publish('unexpectedTermination', …) over redis for the dashboard)Outbound alerts on error/crash exist, but no externally pollable health/heartbeat endpoint (e.g. a '/health' or '/ping' route) was found in jesse/controllers; monitoring depends on the dashboard being open or on redis pub/sub events.
Accounting and reconciliation
Position reconciliation with the exchangeNot verifiedjesse/store/state_positions.py is a plain in-memory dict (get_position/count_open_positions) with no method that compares stored positions against an exchange query. Population of that store from a live account happens via jesse_live (not in this repo).No startup/periodic reconciliation code found in the public repo; cannot confirm presence or absence inside jesse_live.
Fees and funding in the accountingPartialjesse/models/FuturesExchange.py (charge_fee() computes fee_rate * amount, but starts with 'if jh.is_livetrading(): return', so it is skipped in live) + jesse/models/Position.py (funding_rate / next_funding_timestamp properties read from '_funding_rate'/'_next_funding_timestamp' fields that must be populated externally)Fee/funding accounting is fully modeled for backtest/paper; for live trading it depends on data pushed by jesse_live's stream, which cannot be inspected here.
Order rejection handlingNot verifiedjesse/services/order_service.py exists (order state transitions) but exchange-specific rejection/partial-fill handling for real accounts is implemented in the per-exchange live drivers inside jesse_live, not present in this repositoryCould not verify retry/backoff behavior on exchange rejection within the audited repo.
Auditable logPresentjesse/config.py ('env.logging' has explicit booleans for strategy_execution, order_submission, order_cancellation, order_execution, position_opened/increased/reduced/closed, balance_update, exchange_ws_reconnection) + jesse/services/logger.py + jesse/controllers/live_controller.py ('/live/download-log/{session_id}' endpoint) + jesse/services/failure.py (persists exception + traceback to live_session_repository)Persistent, structured logging of orders/positions/decisions is present and downloadable per session.
Security
API key permissionsNot verifiedNo withdrawal/transfer API calls exist anywhere in this repository (the only exchange driver present, Sandbox, never calls a real exchange). The real exchange integrations and their required API-key scopes are defined inside the closed-source 'jesse_live' plugin (docs.jesse.trade/docs/livetrade confirms live/paper trading 'is supported by Jesse via an official plugin' requiring a separate license).Cannot confirm what permissions jesse_live's documentation requests or whether its code ever calls a withdrawal endpoint.
Secrets handlingPartialjesse/services/auth.py (dashboard password comes from ENV_VALUES['PASSWORD'], i.e. environment — good) BUT jesse/models/ExchangeApiKeys.py stores 'api_key' and 'api_secret' as plain peewee CharField columns in the local database with no visible encryption wrapper, and jesse/models/AiModel.py stores an LLM provider 'api_key' the same wayExchange and AI-provider secrets are kept in the local SQLite/DB file in plaintext columns, not only in env vars; a copy of that DB file exposes all configured exchange keys.
Authenticated remote controlPresentjesse/controllers/live_controller.py (router-level 'dependencies=[Depends(require_auth)]' on every /live endpoint) + jesse/controllers/ai_model_controller.py (same pattern) + jesse/services/auth.py (require_auth checks a sha256(password) bearer token against ENV_VALUES['PASSWORD'])All control endpoints require the shared dashboard password; jesse/services/notifier.py only sends outbound Telegram/Discord/Slack messages and has no inbound command listener, so there is no unauthenticated chat-based control surface. Weakness: one single shared secret for all users/devices, no per-user allowlist or roles.
AI and learning
The model cannot override the brakesPartialREADME.md ML example: 'ml_predict_proba()' returns a bounded probability used inside the user's own should_long()/go_long() strategy code, which then calls the same self.buy/self.sell -> jesse/services/broker.py -> jesse/services/api.py path as any manual ruleThe classical ML pipeline's output is just a signal gate inside ordinary strategy code, not a path that bypasses order submission; however, whether the separate generative 'AiModel' (provider/base_url/api_key/model_id, see jesse/models/AiModel.py) can size or submit orders directly could not be located in the time available.
Model output validationPartialjesse/models/AiModel.py + jesse/controllers/ai_model_controller.py define storage for a generic LLM-style provider (name/provider/base_url/api_key/model_id) but no code path consuming this model at runtime (e.g. to produce an order) was found in the files readThe scikit-learn ml_predict()/ml_predict_proba() pipeline described in README returns a bounded scalar (probability or regression value), which structurally cannot contain an invented symbol/side/price the way a free-text LLM order proposal could; that specific risk (hallucinated order fields) does not apply to it. Could not verify the generative AiModel's consumption path.
Learns from its mistakesPresentREADME.md ML pipeline: explicit three-phase workflow — 'gather' (record_features/record_label during a backtest), 'train' (train_model() call, a separate manual mode), 'deploy' (ml_predict()/ml_predict_proba() inside the strategy)Retraining requires a manual, separate step (running gather mode then train_model()); no evidence of the model rewriting itself online during a live session without review.
Model poisoningNot applicableREADME.md ML example only feeds numeric technical-indicator values (ta.rsi, ta.adx) computed from candle data into record_features/ml_predict; no code path was found that feeds news, tweets or other third-party free text into any model used for trading decisionsNo_aplica because the only documented trading-relevant model (the scikit-learn pipeline) consumes numeric OHLCV-derived features, not external text.
Behavior without model quota or responseNot verifiedNo runtime consumption of the AiModel/provider registry (jesse/models/AiModel.py) was located in the files read, so its behavior on a failed/quota-exhausted call, and any daily token-cost cap, could not be determinedNot applicable to the scikit-learn ml_predict pipeline, which runs a locally-loaded model file with no external API quota.

Risk-control audit: jesse-ai/jesse

**Repo audited:** https://github.com/jesse-ai/jesse — branch `master`, tree `432cce8` (repo last pushed 2026-09-17T16:19 UTC). MIT license, Python, 8,528 GitHub stars, 1,236 forks (confirmed via GET /repos/jesse-ai/jesse, 2026-09-17).

**What it is:** Jesse is a self-hosted framework for writing, backtesting, optimizing and (via a separate plugin) live-trading crypto strategies on Binance, Bybit, Coinbase, Kraken, KuCoin, Bitfinex, Gate, Hyperliquid, Lighter and Apex, with a web dashboard, 300+ indicators, Monte Carlo/significance testing, and a scikit-learn machine-learning pipeline.

**The single most important finding:** the real live/paper trading engine is not in this repository. `jesse/controllers/live_controller.py` literally does `from jesse_live import live_mode` — Jesse's own documentation confirms live trading "is supported by Jesse via an official plugin" that requires a separate paid license (docs.jesse.trade/docs/livetrade). That means most of the checklist's execution-time controls (order-size enforcement in live mode, circuit breakers, websocket reconnection, position reconciliation with the exchange, order-rejection handling) physically cannot be verified from public code — they live inside closed-source software.

**What we could still verify in the open code, and what's missing:**

**Bottom line:** anyone relying on Jesse for real-money live trading is trusting a paid, closed-source component for almost everything that keeps a bot from losing control of its risk, and the one guardrail visible in the open code is switched off precisely when it's live.

**Audited:** `master` branch, tree/commit `432cce8a4b91828ce34dcaffac3f8e67354d061c`, as of 2026-09-17 (repo `pushed_at` timestamp).

Badge for the README

risk controls audit: 3/21 present

<a href="https://saasfactoryagents.com/bots/jesse-ai-jesse/"><img src="https://saasfactoryagents.com/bots/jesse-ai-jesse/badge.svg" alt="risk controls audit: 3/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.

Running jesse 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.