Advanced Visual Quantitative Trading Framework
ApexAlgo is a full-stack algorithmic trading platform for building, backtesting, and executing systematic trading strategies — without writing code. A node-based visual strategy builder connects directly to a high-performance async execution engine with multi-exchange market data, encrypted exchange key management, and real-time per-bot console output.
Open source, beta quality. ApexAlgo is free software under the AGPL-3.0 — issues and pull requests are welcome, see CONTRIBUTING.md. New here? Start with BETA.md — step-by-step setup, your first bot in 5 minutes, and mandatory safety rules for live trading. Want an AI to design a strategy for you? Paste STRATEGY_CONTEXT.md into any AI assistant and import the resulting
.apex.json. Ready-made strategies live inexamples/.
One command to your first bot — 24 s, with sound.
Apexalgo_Demo.mp4
Player not showing? Download the MP4.
Chart Engine — TradingView-grade candles with per-bot overlays: indicator lines, historical/live trade markers and the engine's raw buy/sell "thoughts", all toggled per algorithm.
Strategy Builder — the node graph that is the strategy: indicators → conditions → logic gates → order routing, with risk blocks (take profit / stop loss) attached to the entry.
Algorithms — every bot card shows the engine's live phase (fetching → backtesting → monitoring), the backtest variant and data range with a jump to Analytics, why the engine stopped it, and one-click restart / start-all / stop-all.
Trade Analytics — net PnL, win rate, profit factor, drawdown, equity curve, strategy vs buy & hold, breakdown per algorithm or pair, monthly PnL — all scoped by the period slider and filters.
Exchange Setup — verified, encrypted connections with latency, linked bots, wallet valuation in USD and a per-exchange setup guide.
- Visual Node Editor — drag-and-drop canvas with 51+ technical indicators, logic gates, conditions, and action nodes
- No-Code Strategy Design — connect indicator, condition, and logic nodes to build complex entry/exit rules
- Exchange Routing Node — select an API key (exchange auto-derived from the key) or manually pick a data exchange when running without a key
- ReactFlow Graph Serialization — strategies are compiled to JSON and evaluated per candle
- Three Execution Modes — forward test (simulated), paper trading (sandbox API), and live exchange execution
- Async Event-Driven Architecture — FastAPI backend with concurrent bot management via asyncio
- Multi-Exchange Market Data — universal REST polling via CCXT; each
(exchange, symbol, timeframe)gets its own independent polling stream - CCXT Integration — exchange-agnostic order execution with market precision handling
- Perpetual swaps with leverage, long and short — a bot trades spot or perpetuals (1–10×, isolated or cross margin), linear (stablecoin-settled,
BTC/USDT:USDT) or inverse (coin-margined,BTC/USD:BTC); the kind is a property of the symbol, not of the key. On perpetuals the strategy can open shorts (SHORT/COVERaction blocks) with mirrored stop-loss/take-profit rules; backtest, forward test and live share the same margin/liquidation model, and spot bots are untouched - One cash currency per bot — every pair of a whitelist must be quoted (spot) or settled (swap) in the same currency (validator-enforced), so a bot can run in USDT, USDC, EUR, USD or BTC. Money is always shown in that currency and never summed across currencies
- 13 Exchanges out of the box — OKX, Binance, Bitvavo, Coinbase, Crypto.com, Kraken, KuCoin, Bybit, Gate, Bitget, MEXC, HTX, BingX; perpetual swaps on nine of them (linear and inverse contracts, BingX linear only; Binance COIN-M runs through ccxt's
binancecoinmclass) - Isolated data streams — bots on different exchanges poll independently and store candles separately; no cross-exchange data mixing
- Exchange Registry — centralized
exchange_registry.pyhandles per-exchange config (OKX EU hostname, passphrase exchanges, sandbox modes) - Automatic migration — existing databases are upgraded non-destructively on startup; all historical data is preserved
- Live Bot Console — each bot card has an expandable scrollable console showing real-time engine events (backfill progress, BUY/SELL fills, errors, max drawdown triggers). Persisted in a
bot_logstable — survives backend restarts - Export to File — save any bot's strategy and settings to a portable
.apex.jsonfile - Import from File — restore a bot from a previously exported file; name collisions are resolved automatically
- Duplicate Bot — clone a bot's configuration without copying its trade history or signal cache
- Cache Wipe — clears chart signals, resets the bot's log buffer, and clears the frontend console in one operation
- SQLite WAL Mode — write-ahead logging enables concurrent reads during writes; bots no longer block each other on database access
- Incremental Backfill Commits — candle data is committed to the database after each exchange batch, not all at once; eliminates startup race conditions when multiple bots start simultaneously
- Indicator Fingerprinting — bots sharing the same indicator configuration reuse computed results via MD5-based fingerprint keys, avoiding redundant pandas_ta calls in the live processing loop
- Evaluator Memoization —
resolve_node()caches resolved Series per evaluation cycle so diamond-shaped node graphs don't recompute shared indicator nodes - Drawdown Caching — drawdown is tracked per
(bot, mode_group)with separatebacktest,forward(forward test) andlive(paper + live) caches, so a simulated forward loss never counts against the real-money curve. Lazy-initialized from DB, updated incrementally on every closed leg - Backfill Lock —
_backfilling_botsset prevents live processing from creating duplicate signals while a bot is mid-backfill - Signal Deduplication — unique constraint on
(bot_name, symbol, timestamp)withINSERT OR IGNOREprevents duplicate signals on bot restart - Incremental Signal Polling — ChartEngine uses
since_idto fetch only new signals after initial load, reducing per-poll payload from thousands of rows to near-zero - Composite Indexes — dedicated indexes on orders (cooldown checks), candles (lookup by exchange/symbol/timeframe/timestamp), positions (bot/status/mode/symbol), and signals (bot/symbol/timestamp + unique constraint) for hot-path query performance
- Batched Live Processing — all open positions, exchange keys, and cooldown counts pre-loaded in 1–2 queries before the bot loop (not per-bot). All signals collected and committed in a single batch after all bots process. One
db.commit()per candle close event - Market Info TTL Cache — ticker data cached for 10 seconds per symbol, reducing exchange API calls during UI polling
- Bounded Event Bus —
asyncio.Queue(maxsize=1000)with drop-oldest overflow prevents unbounded memory growth from slow subscribers - Numpy-Backed Backtest Loop — indicator and signal arrays are pre-extracted from DataFrames before the per-candle iteration
- Vectorized Streak Detection —
increasing_for/decreasing_forconditions userolling().sum()instead of Python loops - Batched Signal Inserts — signals are committed in 500-row chunks to reduce SQLite write lock duration
- Background Bot Deletion — deleting a bot returns instantly; heavy cleanup of orders, positions, signals, and logs runs asynchronously after the response
- Gzip Compression — nginx compresses JSON, HTML, JS, CSS, and XML responses (threshold: 512 bytes, level 6)
- Static Asset Caching — nginx serves hashed Vite assets with
Cache-Control: max-age=31536000, immutable; index.html is never cached - Container Resource Limits — docker-compose sets CPU/memory caps per service to prevent resource starvation
- Shared Capital Pool —
backtest_capitalis a single pool in the bot's cash currency, shared across all whitelist pairs (engine/capital.py,CapitalPools). When BTC uses 140 of it, only the remainder is available for ETH/SOL/XRP. An entry locks margin plus the entry fee; on close the proceeds (spot) or margin + PnL − exit fee (derivatives) return to the pool - Dynamic Trade Sizing —
percentagesizing is a percentage of the free cash left in the pool (not of mark-to-market equity), so sizes compound across pairs and shrink as capital is locked. Afixedamount is a cash amount in the bot's currency.max_order_value(a quote-currency notional) caps every entry in the backtest, forward test and live alike and is therefore part of the strategy fingerprint. Trading halts when the pool reaches zero - Honest returns —
profit_pctis the return on the capital a trade had locked (margin + entry fee), in the backtest, forward test, live tick and manual close alike; liquidation is modelled in the backtest and forward test and detected live - Capital Depletion Guard — before opening any position, the engine verifies sufficient capital. No phantom-money trades
- Post-Backtest Drawdown Gate — backtest always runs to completion; max drawdown is evaluated over all closed positions afterward. If the threshold is exceeded, the bot is stopped and not allowed to go live
- Vectorized Historical Evaluation — fast backtest over configurable lookback periods with numpy-backed arrays
- Stable Backfill Detection — waits for 5 consecutive stable candle counts (10 seconds) before proceeding, preventing premature backtest starts when exchange data is still loading
- Backfill Retry Logic — transient API errors during backfill are retried up to 3 times with exponential backoff; per-thread exchange instances prevent shared rate-limit interference
- Exchange Timeframe Validation — unsupported timeframes are detected before backfill/polling; a clear warning is shown when a timeframe isn't available on the selected exchange
- Adaptive Lookback — if the exchange has fewer candles than requested (e.g. 5,000 available vs. 50,000 requested), the backtest runs on whatever is available (minimum 20 candles required)
- Fee-Adjusted P&L — entry/exit fees and slippage applied to all profit calculations; computed fee amounts are stored on each Order record so the analytics page can report accurate total fees paid
- Automatic Position Closure — open positions at backtest end are closed at last price with proper P&L
- Tiered Take Profit / Stop Loss — multiple TP/SL levels with percentage or fixed close amounts
- ATR & Trailing Stops — dynamic stop-loss adjustment based on price action
- Trade Cooldown — configurable max entries per N candles, counted per symbol and in candles (the window starts at the N-th most recent stored candle, in the backtest and live alike)
- Position Limits — per-pair or global max concurrent positions
- Max Drawdown Guard — evaluated after full backtest to gate live entry; during live trading, checked mark-to-market on every candle (forward-test and real-money curves are tracked separately). Default action
close_allcloses every position and stops the bot; opt-inblock_entriespauses new entries until drawdown recovers below half the limit — or the bot has been flat for a configurable cooldown (default 7 days), after which the peak resets — while exits keep running (simulated identically in the backtest). A separate Max Capital Loss guard (loss of starting capital) is the hard stop and follows the same action: close everything immediately, or wind down (no new entries, exits finish, then stop) - Max Order Value Guard — caps every entry to a configurable quote-currency notional (USD on
BTC/USD:BTC, EUR onBTC/EUR); mandatory for live bots, applied in the backtest and forward test too
- Order Fill Validation — verifies exchange order status after every CCXT call
- Unique Order Identification — UUID-suffixed local order IDs prevent duplicate entries
- Exchange Instance Caching — single CCXT connection per bot cycle for performance
- Structured Audit Logging — order responses logged with id, status, filled amount, and fees
- Fernet Encryption — exchange API credentials encrypted at rest
- Timing-Safe Authentication —
X-API-Keyheader for scripts, HttpOnly session cookie for the UI, both HMAC-compared - Local TLS — self-signed or mkcert-generated trusted certificates
- Environment Isolation — secrets auto-generated during setup, never committed
- Equity Curve — inline SVG cumulative P&L chart over time (no external chart library)
- Buy & Hold Comparison — per-symbol strategy return vs. passive buy-and-hold; strategy % is
total_pnl / backtest_capital * 100using the same capital base as B&H for fair comparison; reference price anchored to the true first entry across all positions (open and closed); live prices come from the bot's own data exchange - 8-Metric Stats Strip — Net P&L, Win Rate, Profit Factor, Max Drawdown (percentage of peak equity using backtest_capital), Avg Hold Time, Total Fees, Return/Risk ratio
- Per-currency money — every amount is printed in the currency it is denominated in (
1,234.50 USDT,€1,234.50,0.0213 BTC) and a currency filter scopes the view; with several currencies (or several modes) in view the money tiles switch to per-currency chips instead of summing. Open positions, the ledger and the execution log carry a Side column (long/short) and label ordersBUY / SELL / SHORT / COVER; a short is force-closed with "Buy to cover" - Avg Hold Time — computed from entry order timestamps as fallback for backtests where position
created_atreflects wall-clock run time rather than the candle entry time - Exchange Filter — filter all analytics sections by exchange, bot, symbol, or execution mode
- Real-Time Charts — TradingView lightweight-charts with indicator overlays on correct axis scales
- Position & Order Tracking — detailed P&L with explicit +/- signs, fee tracking, and trade history
- Signal Recording — every entry/exit signal stored with indicator snapshot values
- CSV Export — download trade history for external analysis
Requires only Docker Desktop. No Python, Node, or manual setup.
git clone https://github.com/Stenvro/ApexAlgo.git
cd ApexAlgo
docker compose up -dFirst start takes ~2 minutes (builds images + compiles frontend). Subsequent starts are instant unless you need a full rebuild (see Development Workflow).
| Service | URL |
|---|---|
| Web UI (frontend + API proxy) | https://localhost:5173 |
| Backend API (direct, optional) | https://localhost:8000 |
| API Docs (Swagger) | disabled by default — start backend with ENABLE_DOCS=1 |
Accept the self-signed certificate warning in your browser on first visit — only once, on port 5173. The UI proxies all API calls through the same origin, so the backend certificate never needs to be trusted separately. Ports bind to
127.0.0.1by default (local machine only). For LAN access see below.
| Command | Description |
|---|---|
docker compose up -d |
Start (detached) |
docker compose down |
Stop |
docker compose logs -f |
View all logs |
docker compose logs -f backend |
Backend logs only |
docker compose logs -f frontend |
Frontend logs only |
docker compose build && docker compose up -d |
Full rebuild (after pulling new code or changing Dockerfiles / dependencies) |
docker compose restart backend |
Apply backend code changes (~3–5 sec) |
Source code is bind-mounted into the running containers so you can iterate without rebuilding the Docker images:
| Path | Mounted to | Effect |
|---|---|---|
./backend/ |
/app/backend/ |
Restart container to apply changes |
./frontend/src/ |
/app/frontend/src/ |
Requires a frontend rebuild trigger (see below) |
After saving a backend file, restart the container:
docker compose restart backendChanges take effect in ~3–5 seconds. Hot-reload (--reload) is intentionally disabled to avoid unnecessary restarts from bind-mounted volume events.
After saving any file under frontend/src/, trigger a Vite rebuild:
rm -f data/.frontend-env-hash && docker restart apexalgo-frontend-1The rebuild takes ~10–15 seconds. nginx automatically serves the new bundle.
Required when changing package.json, requirements.txt, Dockerfiles, or entrypoint scripts:
docker compose build && docker compose up -dThe backend test suite (244 tests) runs against a throw-away SQLite file (your data/ database is never touched) and covers the exit-rule table, a full live tick against a mocked exchange, swaps and shorts, contract economics (test_contracts.py), a parity suite (test_parity_modes.py: the same candles through backtest, forward test and a live mock, long/short, 1×/5×), property tests with hypothesis (test_properties.py: PnL sign, liquidation monotone in leverage, sizing never exceeds the pool), the routers, and golden backtests: every example strategy (plus a synthetic long/short swap fixture) is run on deterministic synthetic candles and its order stream is compared with tests/golden/*.json, so an engine change can never silently alter a strategy's trades.
pip install -r requirements-dev.txt
python -m pytest -q tests # ~75 s
ruff check backend tests scripts
cd frontend && npm run lint && npm run buildIf a golden snapshot changes on purpose, regenerate it with UPDATE_GOLDEN=1 python -m pytest -q tests/test_golden_backtest.py and review the diff. To reproduce the example results on real exchange data (STRATEGY_CONTEXT.md §4.9):
python scripts/verify_examples.py # all examples, as shipped
python scripts/verify_examples.py --only Supertrend --one-per-pairThe same checks run in GitHub Actions on every push and pull request.
By default both ports bind to 127.0.0.1 (local machine only). To reach the UI from other devices on your network, start with:
BIND_ADDR=0.0.0.0 docker compose up -dThen browse to https://YOUR-LAN-IP:5173 and accept the certificate warning. All API traffic flows through the same origin (the built-in proxy), so no additional configuration is needed. Never expose these ports to the public internet.
VITE_API_BASE_URL in data/.env is now optional: leave it empty (or unset) to use the same-origin proxy. Only set it if the frontend must call a backend on a different host, and expect one extra certificate acceptance on that origin.
All data lives in the data/ folder on your host (bind-mounted into both containers):
data/
├── ApexAlgoDB.sqlite3 ← database
├── .env ← secrets + config
└── cert/
├── cert.pem ← SSL certificate
└── key.pem ← SSL key
This is the same data/ folder used by the manual install scripts — both methods are interchangeable. Stopping containers never deletes data. To fully reset, delete data/ and restart.
Setup scripts in install/ run ApexAlgo directly on the host using screen sessions.
| Dependency | Version | Notes |
|---|---|---|
| Python | 3.11+ | Auto-installed if missing |
| Node.js | 18+ | Auto-installed if missing |
| mkcert | latest | Auto-installed if missing |
| screen | any | Auto-installed if missing |
git clone https://github.com/Stenvro/ApexAlgo.git
cd ApexAlgo
chmod +x install/*.sh
./install/Setup.sh
./install/Start_ApexAlgo.shServices run in detached screen sessions:
screen -r apex_backend # attach to backend
screen -r apex_frontend # attach to frontend- Detach (keep running):
Ctrl+AthenD - Stop process:
Ctrl+C
Both methods share the same data/ directory (database, certs, .env). Use the switch script to move between them:
./install/Switch_Mode.shThe script auto-detects which mode is currently running and switches to the other. It handles:
- SQLite WAL checkpoint before stopping (prevents data corruption)
- Cleanup of Docker artifacts (stale symlinks, build cache, cert ownership)
- Cleanup of bare-metal symlinks when switching back to Docker
- Health check after starting Docker
If nothing is running, it prompts which mode to start.
Both setup methods (Docker and manual) auto-generate a .env file with secure keys on first run. The only value you may need to edit:
VITE_API_BASE_URL=https://<your-ip>:8000In Docker this is optional: an empty value means the frontend uses its own origin (the built-in API proxy). Manual (non-Docker) installs set it to the backend's address.
| Variable | Description | Auto-generated |
|---|---|---|
MASTER_API_KEY |
Backend authentication key for all API requests | Yes |
DATABASE_URL |
SQLAlchemy database connection string | Yes |
ENCRYPTION_KEY |
Fernet key used to encrypt exchange API credentials at rest | Yes |
VITE_API_BASE_URL |
Backend base URL for the frontend (empty = same-origin proxy) | Yes — optional in Docker |
CORS_ORIGINS |
Comma-separated list of allowed browser origins | Yes |
BIND_ADDR |
Host interface for published ports (default 127.0.0.1; set 0.0.0.0 for LAN) |
No — set when needed |
ENABLE_DOCS |
Set 1 to enable Swagger UI at /docs |
No |
The frontend never embeds the API key. On first visit, enter the MASTER_API_KEY from data/.env in the login screen; the backend sets an HttpOnly session cookie (invalidated by a backend restart). Scripts and curl can still authenticate with the X-API-Key header.
ApexAlgo/
├── backend/
│ ├── core/
│ │ ├── database.py # SQLAlchemy engine, WAL mode, session, idempotent migrations
│ │ ├── exchange_registry.py # CCXT exchange factory for all supported exchanges
│ │ ├── bot_log_buffer.py # Thin wrapper: push/get/clear bot logs in DB
│ │ ├── encryption.py # Fernet credential encryption/decryption
│ │ ├── events.py # Async event bus (CANDLE_CLOSED, BOT_STATE_CHANGED)
│ │ └── security.py # API key authentication
│ ├── engine/
│ │ ├── bot_manager.py # Engine front door: state, locks, CANDLE_CLOSED dispatch to the modules below
│ │ ├── backtest.py # Chronological multi-pair simulation against one capital pool (pyramiding, MTM drawdown)
│ │ ├── live_cycle.py # One live candle: entries, exits, signal batch, close-all
│ │ ├── startup.py # Bot start: data wait/backfill, backtest, drawdown gate, reconciliation, go-live
│ │ ├── exits.py # SL/TP/trailing/ATR/multi-tier exit rule table, one direction-parameterised body for longs and shorts
│ │ ├── contracts.py # ContractSpec: spot / linear / inverse economics (notional, margin, PnL, liquidation, fees) in the cash currency
│ │ ├── capital.py # CapitalPools: per-currency cash/locked/start pool (one currency per bot)
│ │ ├── pnl.py # Side-aware seam: direction, price_pnl, liquidation_price, locked_capital, order sides
│ │ ├── risk.py # Drawdown / capital-loss tracking per (bot, backtest|forward|live group), unrealized PnL
│ │ ├── sizing.py # Trade sizing, max_order_value cap, forward pool, config fingerprints
│ │ ├── broker.py # ccxt plumbing: order reconciliation, cancel, min-notional, wallet checks
│ │ ├── candle_poller.py # Universal multi-exchange REST polling with incremental backfill
│ │ ├── evaluator.py # Node graph resolver using pandas_ta (memoized per evaluation cycle)
│ │ ├── indicator_registry.py # Single source of truth for builder indicators (served at /api/indicators)
│ │ └── settings_validator.py # Bot settings integrity checks
│ ├── models/
│ │ ├── bots.py # BotConfig ORM
│ │ ├── bot_logs.py # BotLog ORM — per-bot engine event log (persisted)
│ │ ├── positions.py # Position ORM
│ │ ├── orders.py # Order ORM
│ │ ├── signals.py # Signal ORM
│ │ ├── candles.py # Candle ORM (exchange-isolated)
│ │ └── exchange_keys.py # ExchangeKey ORM
│ ├── routers/ # API route handlers (bots, trades, data, keys)
│ └── main.py # App init, CORS, lifespan, migrations
├── frontend/
│ └── src/
│ ├── api/ # Axios client (same-origin by default), error humanizer
│ ├── theme.js # Light/dark theme state + token access
│ ├── utils/money.js # fmtMoney(value, ccy), per-currency sums, cash currency / contract kind of symbols and rows
│ ├── utils/orders.js # Side-aware order helpers (isShortOpen, isCover, orderAction, modeTag)
│ ├── examples/ # One-click loader; imports the strategies from the root examples/
│ └── components/
│ ├── Builder/ # Visual strategy editor (BotBuilder, CustomNodes, indicatorConfig)
│ ├── ui/ # Design-system primitives (Button, Toast, Modal, DataTable, …)
│ ├── ChartEngine.jsx # TradingView charts, live ticker candle, signal overlays
│ ├── BotManagerUI.jsx # Bot cards: start/stop, console, export/import/duplicate
│ ├── BotConsole.jsx # Per-bot live log console (polling, auto-scroll, level colors)
│ ├── DataManager.jsx # Historical data download and management (multi-exchange)
│ ├── TradeManager.jsx # Quant analytics: equity curve, buy & hold, drawdown
│ ├── Home.jsx # Dashboard landing (stats, recent strategies)
│ ├── ApiKeyGate.jsx # Login screen (master key entry)
│ └── Settings.jsx # Exchange key management (multi-exchange)
├── docker/
│ ├── backend.Dockerfile # Python 3.11 + FastAPI + uvicorn (non-root)
│ ├── frontend.Dockerfile # nginx + Node.js (builds frontend at startup)
│ ├── backend-entrypoint.sh # Auto-generates .env + SSL certs, drops privileges
│ ├── frontend-entrypoint.sh # Waits for .env, builds frontend, starts nginx
│ └── nginx.conf # SPA fallback + same-origin /api proxy + CSP + gzip
├── install/
│ ├── Setup.sh # Full setup (Python, Node, venv, deps, certs, .env)
│ ├── Start_ApexAlgo.sh # Start backend + frontend in screen sessions
│ └── Switch_Mode.sh # Switch between Docker and screen sessions
├── examples/ # Verified importable strategies (.apex.json) — results in STRATEGY_CONTEXT.md §4.9
├── scripts/
│ └── verify_examples.py # Re-runs the examples on real exchange candles in a temp DB (reproduces §4.9)
├── tests/ # pytest suite: exit rules, live tick with exchange mock, swaps/shorts, contracts, parity, hypothesis properties, routers, golden backtests
│ └── golden/ # Pinned order streams of the example strategies (+ a synthetic swap long/short fixture) on synthetic candles
├── .github/workflows/ci.yml # pytest + ruff + npm lint/build on push and PR
├── docker-compose.yml # Two services; ports on 127.0.0.1 by default (BIND_ADDR opt-in)
├── data/ # Database, .env, SSL certs (gitignored)
├── requirements.txt
├── requirements-dev.txt # pytest, ruff, httpx, hypothesis (tests + lint)
├── BETA.md # Beta tester guide (setup, safety rules, troubleshooting)
└── STRATEGY_CONTEXT.md # AI context: builder reference + .apex.json import schema
Strategy Builder (ReactFlow) ──serialize──> Bot Settings JSON
│
POST /api/bots/
│
Settings Validator
│
Save to DB
│
Bot Start ──> Backfill (historical backtest)
│
CandlePoller (per exchange/symbol/timeframe)
└── fetch_ohlcv polling ──> CANDLE_CLOSED event
│
NodeEvaluator resolves indicator → condition → logic
│
BotManager processes entries/exits
│ │
bot_log_buffer Exchange API
(DB-persisted) (CCXT paper/live)
│
┌───────────────┼──────────────────┐
Forward Test Paper (Sandbox) Live Exchange
(local simulation) (exchange sandbox) (exchange production)
│
Position + Order + Signal ──> DB
│
┌───────────────────┴───────────────────┐
ChartEngine TradeManager
(TradingView signals) (equity curve, buy & hold, stats)
│
BotManagerUI
(live console via /logs polling)
Each bot card in the Bot Manager has an expandable console panel. It streams the bot's engine output in real time by polling GET /api/bots/{name}/logs?since={seq} every 2 seconds while the panel is open — zero overhead when closed.
Logs are written to the bot_logs SQLite table by the engine (backend/engine/) at key execution points:
| Event | Level |
|---|---|
| Bot starting (symbol, timeframe, mode, lookback) | INFO |
| Waiting for candle data / data ready / data stalled | INFO / WARN |
| Backfill progress (every 100 candles) | INFO |
| Backfill complete (candle count + trade count) | INFO |
| Exchange limit reached (fewer candles than requested) | INFO |
| Running backtest on N candles | INFO |
| BUY filled / rejected / failed | INFO / WARN / ERROR |
| SELL filled / failed | INFO / ERROR |
| Max drawdown auto-stop | WARN |
| API key not found, falling back to forward_test | WARN |
| General strategy execution error | ERROR |
Log entries persist across backend restarts. Wiping the cache also clears the log buffer for that bot.
Click Export on any bot card to download a .apex.json file containing the bot's name, strategy graph, settings, and sandbox flag. Trade history and signals are not included.
{
"apex_version": "1.0",
"exported_at": "2026-03-31T14:07:33Z",
"bot": {
"name": "My Strategy",
"is_sandbox": true,
"strategy": "<ReactFlow graph JSON>",
"settings": { "timeframe": "1h", "symbols": ["BTC/USDT"] }
}
}Click Import Bot in the page header and select a .apex.json file. If the bot name already exists, (imported) is appended automatically. Bots imported without visual layout data (e.g. programmatically created) have their node graph automatically reconstructed in the editor.
Click Duplicate on any stopped bot card to create a clone with (copy) appended to the name. The duplicate starts inactive with no trade history.
| Exchange | Passphrase | Spot sandbox | Perpetual swaps | Contract kinds | Notes |
|---|---|---|---|---|---|
| OKX | Yes | Yes | up to 10× (sandbox) | linear, inverse | EU hostname (eea.okx.com); net (one-way) position mode |
| Binance | No | Yes | up to 10× (sandbox) | linear, inverse | USDⓈ-M and COIN-M futures (COIN-M via ccxt binancecoinm), one-way position mode |
| Bitvavo | No | No | — | — | EU exchange |
| Coinbase | No | No | — | — | |
| Crypto.com | No | Yes | — | — | UAT environment |
| Kraken | No | No | up to 5× (demo) | linear, inverse | Kraken Futures uses its own keys (futures.kraken.com / demo-futures.kraken.com) |
| KuCoin | Yes | No | up to 10× | linear, inverse | KuCoin Futures uses its own keys (Futures → API management) |
| Bybit | No | Yes | up to 10× (testnet) | linear, inverse | Unified trading account, one-way position mode |
| Gate | No | Yes | up to 10× (testnet) | linear, inverse | |
| Bitget | Yes | Yes | up to 10× (demo) | linear, inverse | USDT-M futures, one-way position mode |
| MEXC | No | No | — | — | Futures API is closed to the public |
| HTX | No | No | up to 10× | linear, inverse | |
| BingX | No | Yes | up to 10× (demo) | linear | USDT-margined perpetuals, one-way position mode |
Adding another CCXT-compatible exchange is one ExchangeSpec entry in backend/core/exchange_registry.py; the key form, the data manager and the builder read the list from GET /api/keys/exchanges. GET /api/data/symbols/{exchange}?market_type=swap returns the tradeable symbols plus a markets map with {kind, base, quote, settle, contract_size, cash_currency} per symbol.
Every bot keeps its books in exactly one cash currency: the quote of a spot pair (BTC/EUR → EUR) or the settle currency of a perpetual (BTC/USDT:USDT → USDT, BTC/USD:BTC → BTC). All pairs of a whitelist must share it (the validator rejects BTC/USDT + ETH/BTC), backtest_capital is read in it, and positions, orders, the backtest summary and the bot summary all carry cash_currency so the UI can print 1,234.50 USDT, €1,234.50 or 0.0213 BTC — amounts are never summed across currencies. Orders additionally record fee_currency / fee_cash (what the exchange actually charged; fee is always converted to the cash currency), and GET /api/trades/stats groups its money fields in by_currency (the top-level fields carry the single-currency case; with several currencies in view they are null). Wallet valuation on the key page stays a display-only USD estimate (valuation_currency: "USD").
An API key is bound to one market: save a second key with Market: Perpetual swaps for derivatives (the same exchange login, with futures/derivatives trade permission; Kraken and KuCoin issue separate futures keys). A bot on a swap key trades the BASE/QUOTE:SETTLE symbols with the leverage and margin mode set in the builder's Exchange Routing block (1–10×, isolated by default). Two contract kinds exist, decided per symbol (the whitelist shows a linear / inverse chip):
- Linear (
BTC/USDT:USDT): settled in the quote stablecoin;amountis a base amount, PnL =±(exit − entry) × amount— the same arithmetic as spot. - Inverse (
BTC/USD:BTC, coin-margined): margin and PnL are in the base coin;amountis a number of contracts worthcontract_sizeUSD each, PnL =±contracts × contract_size × (1/entry − 1/exit). Positions storecontract_kind/contract_sizeso a later contract-size change on the exchange never rewrites history.
The engine confirms leverage and margin mode on the exchange before the first order and refuses to start when that fails; on start-up, open swap positions are reconciled against fetch_positions instead of the wallet, and a position the exchange no longer holds is booked as a liquidation rather than managed blind. A reconciliation that cannot be performed stops a real-money bot (a sandbox bot only warns).
Economics are identical across backtest, forward test and live: an entry locks notional / leverage plus the entry fee on the notional, PnL is on the full notional, profit_pct is measured on that locked capital. The liquidation level is entry × (1 − (1 − 0.5%) / leverage) for a linear long, entry × (1 + (1 − 0.5%) / leverage) for a linear short (a 1× long has none, a 1× short liquidates at +99.5%), and entry × lev / (lev + 1 − 0.5%) / entry × lev / (lev − 1 + 0.5%) on inverse contracts. Stop-losses and take-profits are evaluated first; only when nothing fills — or the fill would lie beyond the liquidation price — is the layer liquidated for its margin plus the entry fee already paid, without exit fee (liquidation in the exit list, liquidations in the backtest summary). The backtest and the forward test apply this rule on every candle; live, a liquidation is booked when the candle range reaches the level or when a rejected reduce-only close is followed by an empty fetch_positions. The 0.5% is the fallback: when the exchange's maintenance-margin tiers are stored (leverage_tiers, fetched at bot start, refreshed weekly) each position uses the rate of its bracket (mmr_source: "tiers" in the summary). Funding is simulated from the stored funding-rate history (funding_rates, fetched at bot start for the backtest window and topped up by the poller): every settlement between a position's open and close is charged on its notional at that candle's close — a long pays a positive rate, a short receives it — booked into profit_abs/funding_paid (summary funding: simulated|partial|no data, funding_paid, funding_events). With margin_mode: cross there is no level per position: the bot's whole pool backs every open position and a breach of the account's maintenance margin liquidates all of them and empties the pool. Paper/live positions are charged and liquidated by the exchange itself. Max Order Value caps the quote notional of an entry (margin × leverage) on every market kind. Exchanges without a swap testnet (KuCoin, HTX) can only forward test or trade real money.
On a perpetual market the strategy graph can also open shorts. The action block's direction has four options: BUY (open long), SELL (close long), SHORT (open short) and COVER (close short). A SHORT block carries its own TP/SL ports and entry size (trade_settings.short, falling back to the long entry settings when the block is not connected), COVER sizes like SELL (trade_settings.cover). Both are refused on a spot bot by the validator — there is no silent no-op.
Rules of the road:
- Long and short never coexist on one pair: a BUY while a short is open (or a SHORT while a long is open) is ignored with an INFO line in the bot console; when BUY and SHORT fire on the same candle the BUY wins. Pyramiding (
Max Positions) counts every layer on the pair regardless of side. - Exit rules are mirrored: a short stop-loss sits above the entry and is hit by the candle high, a take-profit below and is hit by the low; trailing and ATR rules anchor to the lowest price reached. A gap through a level fills at the open, as for longs.
- PnL is
(entry − exit) × amountminus fees on a linear contract (the inverse formula above on coin-margined ones), slippage works against the trade (sold below the close on entry, bought above it on cover). A short is liquidated when the candle high reachesentry × (1 + (1 − 0.5%) / leverage)— even at 1×, where that is a +99.5% move — losing its margin. - Live orders: a short opens with a market sell (not reduce-only) and closes with a reduce-only buy; start-up reconciliation compares the side of every open position with the exchange. The backtest summary adds
long_trades/short_trades, the Analytics Trades tile splits PnL per side, and the chart marks shorts (S-SH/T-SHORT) and covers (S-CV/T-COVER). - A ready-made long/short template ships in
examples/Supertrend_LongShort_Perp_1d.apex.json(also under Load example strategy): long while the daily Supertrend points up, short while it points down, 1×, BTC+ETH USDT perps — +50.8% / 29.1% max DD over Dec 2023 → Sep 2026 with 26 long and 23 short trades; the parameter neighbours and the 2× version are in STRATEGY_CONTEXT.md §4.9.
Backfill pages through each exchange's OHLCV history, discovers the listing date of young pairs (OKX and Crypto.com return nothing for a since before listing instead of clamping), refetches any gaps it finds, and logs a warning when less history is available than the bot's lookback asks for. Limits measured in September 2026:
| Exchange | Candles per request | History |
|---|---|---|
| Binance, KuCoin | 1000 | Full history since listing |
| Bitvavo | 1000 | Full history since listing |
| OKX, Coinbase, Crypto.com | 300 | Full history since listing (a quote like USDC may be listed years after USDT — check the "exchange has no data before …" warning) |
| Kraken | 720 | Only the most recent 720 candles per timeframe, regardless of the requested start — use a larger timeframe or another data exchange for long backtests |
Bug reports, feature requests and pull requests are welcome. dev is the working branch and master only receives releases — open pull requests against dev. Read CONTRIBUTING.md for the workflow, code style and what to include in a bug report. Ready-made strategies for examples/ are a great first contribution.
Found a security vulnerability? Please do not open a public issue — follow SECURITY.md instead.
- Exchange API keys are encrypted at rest using Fernet symmetric encryption
- All API endpoints require either the
X-API-Keyheader (scripts, curl) or the browser session cookie; both use timing-safe comparison, failed attempts are logged and rate-limited per IP - The web UI never embeds or stores the master key: you enter it once in the login screen and receive an
HttpOnly,SameSite=Strict,Securesession cookie that JavaScript cannot read; sessions end on backend restart or log-out. The browser talks to a single origin (nginx proxies/apito the backend) - Ports bind to
127.0.0.1by default; LAN access is an explicit opt-in (BIND_ADDR=0.0.0.0) - The backend container runs as a non-root user;
.envis created with restrictive permissions and excluded from version control - Live order execution requires a
max_order_valuesafety cap (quote notional per entry), sizes against the verified exchange balance in the bot's cash currency, and reconciles every order fill (fetch_order) before booking - Live sizing is wallet-based: each bot deploys up to
live_allocation_pctof the exchange wallet (free balance + positions already open on that key), so multiple bots can share one API key by splitting the percentage - Sandbox-flagged keys refuse to run on exchanges without a real testnet
- Swagger/OpenAPI docs are disabled by default; a Content-Security-Policy is set on the web UI
ApexAlgo is experimental software. Algorithmic trading carries significant financial risk. This project is provided as-is, without warranty of any kind. Use at your own risk.
Nothing in this repository is financial advice. Backtest results are not a guarantee of future performance, and the example strategies are educational — not recommendations. Always start in paper mode, use exchange API keys without withdrawal permissions and never trade with money you cannot afford to lose.
As stated in sections 15 and 16 of the license: there is no warranty for the program, and the authors and contributors are not liable for any damages — including lost funds — arising from its use.
ApexAlgo is free software, licensed under the GNU Affero General Public License v3.0 — see LICENSE for the full text.
Copyright (C) 2026 ApexAlgo contributors
In plain terms: you may use, study, modify and redistribute ApexAlgo freely, including commercially. If you distribute a modified version — or run a modified version as a service that others use over a network — you must make your modified source code available to those users under the same license.
- Charts are powered by TradingView Lightweight Charts (Apache-2.0).
- All other dependencies (CCXT, FastAPI, React, ReactFlow, pandas-ta, …) are distributed under permissive licenses (MIT, BSD, Apache-2.0, MPL-2.0); see
requirements.txtandfrontend/package.json.






