A backtesting, live-trading and web-dashboard stack for crypto strategies. Based off the latest FreqTrade, we added an improved UI built into the project, multi subprocess bot handling, discord bot (with control cmds), data server (with sub servers) to counter rate limiting, pair finding tool, regime analysis, MAE/MFE analysis, monthly/daily performance boxes, uncorruptable json DBs, headless subproc bots, backtesting/hyperopt/pairfinding all in the UI, and a more compact project structure. Also support for Drift, Bitunix & Coinbase (Advanced Trade) exchanges.
Drop a Freqtrade-style IStrategy subclass into
user_data/strategies/ and it should run (just rename imports to VulcanTrader). You are still allowed to use TA Indicators, but there are faster rust indicators provided.
- Python 3.12+
- Visual Studio Code (Recommended) https://code.visualstudio.com/ install VS Code, you pick Open Folder and open this folder.
Install:
.\install.batRe-activate the venv later with:
.\.venv\Scripts\Activate.ps1./install.shLaunches the web portal so you can browse backtest results, backtest, hyperopt, find pairs, review closed trades, and run analysis tools without starting any live or paper-trading session. Manage your bots and edit configs.
Windows:
.\run-app.batLinux / macOS:
./run-app.shThen open http://localhost:8060 in your browser (default password: test).
Once a run-paper / run-live session is up, the Web Portal is
served on http://localhost:8060 (port comes from
config["api_server"]["listen_port"]). Log in with the bearer password
from config["api_server"]["password"] (default VulcanTrader).
The dashboard exposes everything you need day-to-day so you rarely have to touch the CLI:
- Trading (
/) — live open/closed trades, wallet balances, per-pair candle charts with strategy plot overlays, and pair locks. - Bot account dropdown (navbar) — the portal scans
user_data/accounts/*.jsonpersistence files and detects whichtrader_botprocesses are currently running (each running bot holds an OS-level lock on its<account>.json.lock, plus writesis_running/last_heartbeatmarkers into the account file). The dropdown lists every account —●running (with uptime),○stopped,⚠crashed (marked running but no live process) — and selecting one loads that bot's full trades/stats/metrics into the dashboard, headless bots included. The Stop button then gracefully shuts down that bot's process: the portal drops a<account>.json.stopfile which the bot's trade loop picks up within ~2 s, exiting cleanly (cleanup, state saved, lock released) once its current cycle finishes. API:GET /api/livebots,GET /api/dashboard?bot=<name>,POST /api/livebots/<name>/stop. - Backtester (
/backtester) — pick any JSON file fromuser_data/backtest_results/and inspect performance metrics, monthly/daily breakdowns, equity & drawdown curves, hourly P&L / profit-factor / drawdown, best/worst pairs, regime and MAE/MFE analysis, and the full per-trade table. - Backtest results browser — drop new result files into
user_data/backtest_results/; they show up in the dropdown automatically.
Each config under user_data/configs/*.json is treated as a individual bot config — its own strategy, its own persistence account, its own process. You can manage the whole fleet from the dashboard, or from the CLI.
is_bot—true(default): real bot, shows in the dashboard.false: template only, hidden (still works for backtest/hyperopt).strategy— which strategy to run. CLI-soverrides it. Ignored by backtest/hyperopt (use-sthere too).auto_start—true: starts automatically with the fleet.show_terminal(Windows) —true(default): own console window.tmux(Linux/macOS) —true: own tmux window.false(default): runs in the background.
Starting the fleet launches every auto_start: true bot, each with its own
trade history.
Dashboard login, Discord, and Telegram settings live once here instead of in every config. A bot config can still override one key just for itself.
data_server.json is different — it's auto-managed, don't hand-edit it. To
change the data server's window/tmux behavior, set that on a bot config's
own data_server block instead.
By default each bot is its own program — safe, but slower to start with a big fleet (every bot reloads pandas/numpy/etc. from scratch).
Turn on "single_process_mode": true in system.json and bots run as
threads in one shared program instead — same isolation, far less memory
(586 MB → 186 MB on a 3-bot fleet) and a faster start. Tradeoff: bots share
one CPU core, so heavy compute won't run in parallel — rarely matters, since
bots mostly wait on network calls.
false (default) = unchanged behavior.
python main.py --port 8060python main.py --port 8060main.py at the repo root is just a shorthand for python -m VulcanTrader.bot webserver — use whichever you prefer, both read single_process_mode from
system.json the same way.
Normally you don't need to think about the data_server master at all — the
first bot to start auto-launches one (ensure_master_running) and every
later bot just connects to it. But that auto-launch is a probe-then-spawn
race: if two independent fleet launches (e.g. run-app started twice, or
under two different Python interpreters) both find nothing listening before
the first one finishes binding, both spawn their own master, and you end
up with two processes independently hammering the exchange for the same
pairs — a real cause of chronic rate-limit errors and stale candles.
run-datamaster.bat / run-datamaster.sh start the master as its own
standalone, explicitly-managed process, refusing to start if something is
already listening on its port. Run it once, before starting the fleet:
Windows:
.\run-datamaster.batLinux / macOS:
./run-datamaster.shOverride CONFIG / EXCHANGE / HOST / PORT / SUBSERVER_PORT /
TIMEFRAMES via environment variables if you're not running the default
hyperliquid, 15m, 127.0.0.1:8720 setup. Pair it with run-subserver.bat
/ .sh on a second machine to add collection capacity.
The Trading page has a Configs panel — every config in one table, with:
- Strategy dropdown and Auto Start checkbox, saved immediately.
- Start / Stop — launches or gracefully stops that config's bot
process, honouring its
show_terminal/tmuxsettings. - Edit JSON — a raw editor for the whole config file.
- Duplicate / Rename / Delete — copy an existing config to spin up a variant (different pairs, different strategy) without hand-editing JSON, rename it, or remove it (blocked while its bot is running).
If discord.bot_token is set (normally via the shared system.json), the
interactive Discord client runs from bot.py webserver — not from any
individual trade process. It's a control-plane concern that belongs to
the one persistent process watching the whole fleet (the same account scan
the dashboard uses), not something tied to whichever trading bot happens to
have a token. A trade instance's fills/entries/exits still reach Discord
either way, via the plain webhook — no interactive client needed for that.
python -m VulcanTrader.bot webserver --port 8060python -m VulcanTrader.bot webserver --port 8060!-prefix commands — !bots, !opentrades <bot>, !exit <bot> <pair>,
!stats <bot>, !stop <bot> — where <bot> is the account name shown by
!bots, so a single Discord channel can watch and control every bot in the
fleet. If you run more than one webserver instance, pass --no-discord-bot
on all but one — two interactive clients sharing one token means two
gateway logins fighting over the same session.
There are two independent pieces — use either alone, or both. Both config
keys normally go in user_data/configs_system/system.json (or a specific
config's own discord block to override just that bot).
Webhook — trade notifications, no bot required
Fills discord.webhook_url. This is all you need for entry/exit/startup
messages to show up in a channel; skip the rest of this section entirely if
that's all you want.
- In Discord, open the server and channel you want notifications posted to.
- Channel settings (gear icon next to the channel name) → Integrations → Webhooks → New Webhook.
- Name it (e.g. "VulcanTrader"), then Copy Webhook URL.
- Paste that into
discord.webhook_url.
Bot — interactive ! commands (!bots, !exit, !stop, …)
Fills discord.bot_token. Only needed if you want to control the fleet
from Discord, not just watch it.
- Go to the Discord Developer Portal → New Application → give it a name.
- Open that app's Bot page in the left sidebar
(
https://discord.com/developers/applications/<app id>/bot). - Reset Token → copy it into
discord.bot_token. - On that same page, scroll to Privileged Gateway Intents and enable
MESSAGE CONTENT INTENT — required because commands are plain
!text, not Discord's slash-command system. Skip this and the bot logs in but never sees a!command— worse, it can't even log in at all and instead crashes that one thread withdiscord.errors.PrivilegedIntentsRequired(the rest of the process, including webhook logging, is unaffected). You must click the Save Changes button at the bottom of the page after toggling it on — the toggle alone does not persist, and this is the most common reason this step "doesn't work." Refresh the page afterward and confirm the toggle is still on before assuming it saved. Also double-check you're looking at the same application the configuredbot_tokenbelongs to — if you're logged into Discord as a different account, or editing a different app, the toggle has no effect on the actual bot. - OAuth2 → URL Generator (left sidebar): check the bot scope, then under Bot Permissions check at least Send Messages, Read Message History, Attach Files (open-trade charts), and Embed Links.
- Open the URL the generator produces, pick your server, and authorize — this actually adds the bot to the server (creating the application alone doesn't).
- Optional: restrict who can issue commands via
discord.allowed_user_ids— get your Discord user ID with!myidonce the bot's running (works for anyone, whitelisted or not), or via Discord's Settings → Advanced → Developer Mode, then right-click your name → Copy User ID. Leave the list empty to allow anyone in the server.
VulcanTrader/backtester/ is a single Rust crate that is two things at
once: a fast backtest engine plus a library of the 23 standard indicators
(fast_indicators), and — when built with the extension-module feature — a
PyO3 Python extension module, vulcan_rust_indicators.
Strategies are not written in Rust — they live in Python under
user_data/strategies/; the crate holds no strategies.
A strategy has two equally valid ways to get its indicators — pick per strategy, or mix both in the same file:
Rust-bridged — pull the engine's standard indicator series straight from Rust, the exact same code the engine itself uses, instead of recomputing them in TA-Lib:
import vulcan_rust_indicators as vri
ind = vri.calculate_standard_indicators(close, high, low, volume) # float64 arrays
dataframe["rsi"] = ind[0] # RSI(14)
dataframe["atr"] = ind[14] # ATR(14)ind is a dict {index: array} of all 23 standard series. See
user_data/strategies/AllIndicatorsDemoStrategy.py
for the full index table and a worked example reading every one of them.
Plain TA-Lib — no Rust dependency at all, just the standard library every freqtrade strategy already uses:
import talib.abstract as ta
dataframe["ema9"] = ta.EMA(dataframe, timeperiod=9)
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
dataframe["macdhist"] = ta.MACD(dataframe)["macdhist"]See user_data/strategies/EmaTrendRsiAdx.py for a complete TA-Lib-only trend-following strategy (EMA stack + RSI + ADX + MACD histogram).
Anything neither covers you build yourself in pandas/numpy — custom statistics, calendar/session-anchored levels, and the like.
The crate is built automatically by install.bat / install.sh, which need a
Rust toolchain (cargo) and maturin. To rebuild the extension by hand into
the active venv:
cd VulcanTrader/backtester
maturin develop --release --features extension-module
Run bots inside tmux so they survive SSH disconnects. Set "tmux": true
in each config you want this for (see "Running multiple bots" above) — the
web portal is started automatically by the trade subcommand unless that
config has --headless (alias: --no-web).
Set "tmux": true on each config, then run autostart (or hit Start on
a config in the dashboard) — every bot with auto_start: true opens as its
own window inside one shared session named VulcanTrader:
source .venv/bin/activate
python -m VulcanTrader.bot autostartAll fleet bots live in that one shared session, as separate windows named
after each bot's bot_name:
tmux attach -t VulcanTrader # attach to the shared session
tmux list-windows -t VulcanTrader # see every bot's window name
tmux select-window -t VulcanTrader:BotName # jump straight to one botStop one bot without touching the others (its persistence + stop-file
mechanism handle a clean shutdown either way, but Ctrl-C inside the window
is the graceful path):
tmux kill-window -t VulcanTrader:BotNameKill the entire fleet at once:
tmux kill-session -t VulcanTraderCached as feather files at:
user_data/data/<exchange>/<PAIR>-<timeframe>.feather
user_data/data/<exchange>/futures/<PAIR>-<timeframe>-<candletype>.feather
http://localhost:8060/datamanager — per-exchange view of exactly what's
cached: every pair/timeframe/candle-type combo with its date range, candle
count, and file size, plus a form to launch download-data for a config
(optionally overriding pairs/timeframes/exchange/date range) as a background
job with a live log, same as backtests/hyperopt. Exchanges list includes
any exchange used by a config even before its first download, so a newly
added exchange shows up (as empty coverage) right away.
main.py ← Shorthand for `python -m VulcanTrader.bot webserver`
tests/ ← unittest suite (no pytest) - `python -m unittest discover -s tests -v`
VulcanTrader/ ← Python package (imported as VulcanTrader.*)
bot.py ← CLI entry point (all subcommands)
backtesting.py ← historical replay engine
trader_bot.py ← live / dry-run trading daemon
web_portal.py ← FastAPI dashboard + notification sink
pairs_bt_finder.py ← utility: find best pairs for backtesting
regime_analysis.py ← market-regime detection helpers
backtester/ ← Rust crate: backtest engine + `vulcan_rust_indicators` PyO3 module (no strategies)
config/ ← Configuration loader + JSON-schema validation
data/ ← OHLCV loaders, converters, btanalysis, metrics
enums/ ← All Enum types
exchange/ ← CCXT exchange wrappers + order utilities
hyperopt/ ← Bayesian parameter optimiser (Optuna-backed)
optimize/ ← Hyperopt parameter-space helpers
pairlist/ ← IPairList handlers (filter pipeline)
persistence/ ← JSON-backed Trade/Order/PairLock storage
resolvers/ ← Dynamic class loaders
strategy/ ← IStrategy interface + HyperOpt mixin
util/ ← supporting helpers and managers
template/ ← HTML served by the web portal
login.html trading.html backtester.html exampleStyle.html
user_data/ ← per-user, NOT under version control by default
configs/ ← *.json config files, one per bot (is_bot, strategy, auto_start, show_terminal, tmux, ...)
configs_system/ ← system.json (shared api_server/discord/telegram) + data_server.json (auto-managed)
accounts/ ← per-bot persistence JSON, <config-name>_<dry_run|live>.json
data/ ← OHLCV cache (per exchange / per timeframe)
strategies/ ← your IStrategy subclasses
backtest_results/ ← JSON output consumed by the web portal
*.py ← strategy files can also live directly in user_data/
All commands funnel through VulcanTrader/bot.py:
python -m VulcanTrader.bot <subcommand> [options]| Subcommand | Purpose |
|---|---|
backtest |
Run one or more strategies through the backtester (async fan-out). |
download-data |
Pull historical OHLCV for the configured pairs / timeframes. |
trade |
Start the live (or --dry-run) trading daemon + web portal (--headless skips the portal). |
autostart |
Scan user_data/configs/*.json for auto_start: true and launch each as its own bot process, then exit (see "Running multiple bots"). |
webserver |
Run the web portal + Discord bot; also does the same auto_start:true scan on startup (--no-autostart to skip). |
lookahead-analysis |
Detect look-ahead bias in strategy entry/exit signals + indicators. |
recursive-analysis |
Detect recursive-formula bias from insufficient startup_candle_count. |
hyperopt |
Bayesian strategy-parameter optimisation via Optuna. |
-c / --config accepts either a path or a bare name. Bare names are
resolved against <user_data>/configs/, with .json appended if no
extension is given. Multiple -c flags merge left-to-right. For trade /
webserver, user_data/configs_system/system.json (shared api_server /
discord / telegram settings) is always merged in first, if present, ahead
of whatever -c passes.
Re-activate the venv later with:
source .venv/bin/activate# Live (dry-run) trading
.venv\Scripts\python.exe -m VulcanTrader.bot trade -c live --strategy AlphaHunterV5 --dry-run
# Web portal only (no bot)
.venv\Scripts\python.exe -m VulcanTrader.bot webserver --port 8060
# Single backtest
.venv\Scripts\python.exe -m VulcanTrader.bot backtest -c configs/configAlphaHunterV5_Paper.json -s AlphaHunterV5 --timerange 20250101- --datadir user_data/data/hyperliquid
# Download last 90 days of OHLCV for two pairs at three timeframes
.venv\Scripts\python.exe -m VulcanTrader.bot download-data -c live `
--pairs BTC/USDT ETH/USDT --timeframes 1m 5m 1h --days 90
# Downloading Hyperliquid
Download it from: http://frequenthippo-dl.ddns.net/wp-content/uploads/hyperliquid_download-data.7z
And put it correctly in the user_data\data folder. (should be user_data\data\hyperliquid\futures with a bunch of feather files in there.
# Look-ahead bias check (signals + indicators)
.venv\Scripts\python.exe -m VulcanTrader.bot lookahead-analysis -c live -s AlphaHunterV5 `
--timerange 20250101- --pairs BTC/USDT ETH/USDT `
--minimum-trade-amount 10 --targeted-trade-amount 50
# Recursive (startup-candle) bias check
.venv\Scripts\python.exe -m VulcanTrader.bot recursive-analysis -c live -s AlphaHunterV5 `
--timerange 20250101- --pairs BTC/USDT `
--startup-candle 199 399 999Each script honors CONFIG, STRATEGY and DB_URL environment
variables, and forwards extra arguments straight to
python -m VulcanTrader.bot trade.
# Different strategy / config
set CONFIG=configBinance
set STRATEGY=AlphaHunterV4MR
.\run-paper.bat
# Disable the embedded web portal (headless)
.\run-paper.bat --headlessCONFIG=configBinance STRATEGY=YourStrat ./run-paper.sh
./run-paper.sh --headlessPass --headless (alias: --no-web) to the trade subcommand to run the
trading bot without starting web_portal.py at all — no FastAPI server, no
open port. Trade notifications that would normally go to the dashboard are
written to the log instead (user_data/logs/bot.log). Useful for servers
where you don't want an exposed HTTP port, or for running several bot
processes without port conflicts. A headless bot is still fully visible in
any running portal (e.g. run-app.bat): pick its account from the bot
dropdown on the Trading page to see its trades, stats and uptime.
.venv\Scripts\python.exe -m VulcanTrader.bot trade -c live --strategy YourStrat --dry-run --headlesspython -m VulcanTrader.bot trade -c live --strategy YourStrat --dry-run --headlessMIT