An automated cryptocurrency trading bot with eight built-in strategies, built with the investing-algorithm-framework v8 and a public interactive backtest dashboard hosted on Hugging Face Spaces.
- Overview
- Strategies
- Project Structure
- Prerequisites
- Step 1 – Install Dependencies
- Step 2 – Configure the App
- Step 3 – Backtest the Strategy
- Step 4 – Analyse the Backtest Results
- Step 5 – Deploy the Trading Bot
- Step 6 – Live Dashboard
- Configuration Reference
- Backtest Dashboard
tradebot buys and sells Bitcoin automatically by detecting when short-term momentum (9-period SMA) crosses the long-term trend (50-period SMA) on 2-hour candles.
The full pipeline runs in five phases:
| Phase | Description |
|---|---|
| ① Data Sources | Bitvavo exchange provides 2-hour OHLCV candles and real-time ticker via CCXT |
| ② Core Application | app.py wires the data providers together; strategy.py implements the SMA crossover logic |
| ③ Development | backtest.py replays history; plot.py saves a QF-Lib-style 6-panel report |
| ④ Production | azure_function.py deploys the bot as a serverless Azure Functions timer trigger |
| ⑤ Monitoring | dashboard/dashboard.py serves a live Plotly Dash UI that reads the bot's SQLite state database |
The diagram below shows exactly how strategy.py evaluates each 2-hour candle and decides whether to buy, sell, or hold.
| Decision point | Condition | Outcome |
|---|---|---|
| Enough candles? | window has ≥ 50 bars (slow SMA period) | YES → compute SMAs, NO → skip cycle |
| Golden Cross | SMA(9) crosses above SMA(50) | YES + no position → BUY 25 % of balance |
| Death Cross | SMA(9) crosses below SMA(50) | YES + position open → SELL full holding |
| No signal | Neither crossover detected | Hold current state, wait for next trigger |
The project ships eight ready-to-use strategies, all living in the strategies/ package.
Each strategy follows the same interface so you can swap them in app.py with a single line.
| Strategy | File | Signal – Buy | Signal – Sell | Key params |
|---|---|---|---|---|
| Golden Cross / Death Cross | strategies/golden_cross.py |
Fast SMA(9) crosses above Slow SMA(50) | Fast SMA crosses below Slow SMA | fast=9, slow=50 |
| RSI Reversion | strategies/rsi_strategy.py |
RSI(14) falls below 30 (oversold) | RSI(14) rises above 70 (overbought) | period=14, oversold=30, overbought=70 |
| MACD Signal Cross | strategies/macd_strategy.py |
MACD line crosses above Signal line | MACD line crosses below Signal line | fast=12, slow=26, signal=9 |
| Bollinger Bands | strategies/bollinger_strategy.py |
Close touches or falls below lower band | Close touches or rises above upper band | period=20, stddev=2.0 |
| EMA Crossover | strategies/ema_cross.py |
Fast EMA(9) crosses above Slow EMA(21) | Fast EMA(9) crosses below Slow EMA(21) | fast=9, slow=21 |
| Stochastic Oscillator | strategies/stochastic_strategy.py |
%K crosses above %D below 20 | %K crosses below %D above 80 | k=14, oversold=20, overbought=80 |
| CCI Reversion | strategies/cci_strategy.py |
CCI(20) drops below −100 | CCI(20) rises above +100 | period=20, oversold=-100, overbought=100 |
| Williams %R | strategies/williams_r_strategy.py |
WR(14) drops below −80 | WR(14) rises above −20 | period=14, oversold=-80, overbought=-20 |
# Use any strategy by importing from the strategies package
from strategies import StochasticTradingStrategy # or any other
app.add_strategy(StochasticTradingStrategy)tradebot/
├── strategies/ # Trading strategy library
│ ├── __init__.py # Exports all eight strategies
│ ├── golden_cross.py # SMA 9/50 crossover (original)
│ ├── rsi_strategy.py # RSI-14 mean reversion
│ ├── macd_strategy.py # MACD signal-line cross
│ ├── bollinger_strategy.py # Bollinger Bands touch
│ ├── ema_cross.py # EMA 9/21 crossover
│ ├── stochastic_strategy.py # Stochastic %K/%D crossover
│ ├── cci_strategy.py # CCI mean reversion
│ └── williams_r_strategy.py # Williams %R mean reversion
│
├── dashboard/ # Dashboard applications
│ ├── backtest_dashboard.py # Interactive public backtest dashboard (Hugging Face Spaces)
│ ├── dashboard.py # Live trading monitoring dashboard
│ └── requirements.txt # Dashboard-only dependencies
│
├── .github/
│ ├── workflows/
│ │ ├── hf_sync.yml # Sync to Hugging Face Spaces (push to main + weekly schedule)
│ │ └── hf_status.yml # Periodic health-check of the HF Space (every 6 hours)
│ └── hf_space_header.md # HF Spaces metadata (prepended to README on sync)
│
├── app.py # App factory – registers data providers and strategy
├── strategy.py # Original strategy (kept for backward compatibility)
├── backtest.py # Step 3 – Run a historical backtest (CLI)
├── plot.py # Step 4 – Generate QF-Lib-style performance charts
├── azure_function.py # Step 5 – Azure Functions timer-trigger deployment
├── Dockerfile # Hugging Face Spaces (Docker) image definition
└── docs/
└── images/ # Sample output images embedded in this README
- Python 3.10 or later
- A Bitvavo account with API access (for live trading only)
- Azure subscription (for cloud deployment only)
pip install investing-algorithm-framework tulipy \
matplotlib scipy \
dash plotly flaskapp.py wires together the data providers and the strategy:
from investing_algorithm_framework import create_app, CCXTOHLCVDataProvider, \
CCXTTickerDataProvider
from strategy import GoldenCrossDeathCrossTradingStrategy
app = create_app()
app.add_data_provider(CCXTOHLCVDataProvider(
symbol="BTC/EUR", market="BITVAVO",
time_frame="2h", window_size=204,
))
app.add_data_provider(CCXTTickerDataProvider(
symbol="BTC/EUR", market="BITVAVO",
))
app.add_strategy(GoldenCrossDeathCrossTradingStrategy)Run a historical backtest over any date range:
python backtest.py <start_date> <end_date>Example:
python backtest.py 2023-01-01 2023-12-30The framework replays every 2-hour candle in the date range, executes buy/sell orders according to the strategy signals, and prints a detailed report to the terminal:
The report shows:
- Backtest report – period, number of runs, order count
- Portfolio overview – initial/final balance, total net gain, growth rate
- Positions overview – amounts, costs, values
- Trades overview – win rate, average size, average duration
Generate a professional performance report (saved as backtest_report.png):
python plot.py 2023-01-01 2023-12-30The report contains six panels that match the industry-standard QF-Lib style:
| Panel | Description |
|---|---|
| Strategy Performance | Normalised equity curve starting at 1.0 |
| Monthly Returns | Colour-coded heatmap (green = gain, red = loss) with % values in each cell |
| Yearly Returns | Horizontal bar chart per calendar year with a dashed mean line |
| Distribution of Monthly Returns | Histogram with a dashed mean marker |
| Normal Distribution Q-Q | Quantile plot to assess return normality |
| Rolling Statistics [6 Months] | Rolling 6-month return (blue) and annualised volatility (dark) |
The report is also importable from other scripts:
from plot import plot_backtest
plot_backtest(backtest, output_path="my_report.png",
strategy_name="My Strategy")Once you have found a profitable strategy, deploy it to Azure Functions as a timer-triggered function that runs every 2 hours.
-
Copy
azure_function.pyinto your Azure Functions project. -
Set the following application settings (environment variables):
Variable Description BITVAVO_API_KEYYour Bitvavo API key BITVAVO_SECRET_KEYYour Bitvavo secret key -
Set them locally for testing:
export BITVAVO_API_KEY=your_api_key export BITVAVO_SECRET_KEY=your_secret_key
-
Deploy with the Azure Functions Core Tools:
func azure functionapp publish <YOUR_FUNCTION_APP_NAME>
The function runs on the cron schedule 0 */2 * * * * (every 2 hours):
@app.timer_trigger(schedule="0 */2 * * * *", arg_name="myTimer",
run_on_startup=True, use_monitor=False)
def trading_bot_azure_function(myTimer: func.TimerRequest) -> None:
trading_bot_app.run(
payload={"ACTION": StatelessAction.RUN_STRATEGY.value}
)Monitor portfolio performance, open positions, and recent trades in real time
from any browser. The dashboard is a self-contained Plotly Dash
application (dashboard/dashboard.py) that reads directly from the same SQLite database
written by the trading bot.
trading bot (azure_function.py / app.py)
│ writes portfolio snapshots
▼
bot_state.db (SQLite)
│ dashboard reads every 30 s
▼
dashboard/dashboard.py (Plotly Dash server)
│ serves HTTP
▼
browser → http://127.0.0.1:8050
- The bot writes a portfolio snapshot to
bot_state.dbafter every strategy run. dashboard/dashboard.pyconnects to that database and queries the latest snapshot each time the browser polls.- When no live database is found, the dashboard automatically enters demo mode so you can inspect the UI before the bot has run.
| Requirement | Notes |
|---|---|
| Python 3.10+ | same as the bot |
dashboard/dashboard.py |
already in the repo |
bot_state.db |
created automatically when the bot runs; dashboard works in demo mode without it |
| Port 8050 available | or set a custom port via the PORT environment variable |
The dashboard requires Dash, Plotly, and Flask (Flask is pulled in automatically by Dash).
pip install dash plotlyIf you installed all dependencies in Step 1 these are already present:
pip install investing-algorithm-framework tulipy \
matplotlib scipy \
dash plotly flaskVerify the installation:
python - <<'EOF'
import dash, plotly
print("dash", dash.__version__)
print("plotly", plotly.__version__)
EOFBy default dashboard/dashboard.py looks for bot_state.db in the same directory as the script.
Linux / macOS
export DATABASE_PATH=/path/to/your/bot_state.dbWindows (Command Prompt)
set DATABASE_PATH=C:\path\to\your\bot_state.dbWindows (PowerShell)
$env:DATABASE_PATH = "C:\path\to\your\bot_state.db"Leave
DATABASE_PATHunset to use the default path./bot_state.db.
python dashboard/dashboard.pyExpected output:
tradebot live dashboard -> http://127.0.0.1:8050
Open http://127.0.0.1:8050 in your browser.
To bind to a different port:
PORT=9000 python dashboard/dashboard.py # Linux / macOS
set PORT=9000 && python dashboard/dashboard.py # Windows CMDTo expose the dashboard on your local network (e.g. access from another device):
# dashboard/dashboard.py already uses host="0.0.0.0"; just set the port
PORT=8050 python dashboard/dashboard.py
# then open http://<your-machine-ip>:8050 from any device on the same network| Card | Source | Description |
|---|---|---|
| Portfolio Value | portfolio.total_value |
Current total portfolio value in EUR |
| Total Return | calculated | (current / initial − 1) × 100 % with EUR delta |
| Unallocated | portfolio.unallocated |
Cash available for new buy orders |
| Win Rate | trade table |
Percentage of closed trades with net_gain > 0 |
In demo mode the Win Rate card is replaced with a Status: Demo card.
A time-series line chart of portfolio_snapshot.total_value ordered by created_at.
- Blue line = portfolio value over time
- Dotted grey line = initial balance baseline
- Fill above/below the baseline highlights gains (blue tint) and losses
Reads position WHERE amount > 0. Columns: Symbol, Amount, Cost (€), Value (€).
Reads the last 20 rows from the trade table ordered by opened_at DESC.
- Net Gain and Return % columns are colour-coded: green for profits, red for losses.
- Paginated at 10 rows per page.
The page polls for new data every 30 seconds via a hidden dcc.Interval component.
The "Last updated" timestamp in the top-right corner updates on every refresh.
All tuneable constants are at the top of dashboard/dashboard.py:
POLL_INTERVAL_MS = 30_000 # refresh interval in milliseconds
PORT = 8050 # HTTP port (overridden by $PORT env var)
DATABASE_PATH = ... # SQLite path (overridden by $DATABASE_PATH env var)Change refresh rate to 10 seconds:
POLL_INTERVAL_MS = 10_000Add a new KPI card — extend the kpis list in the refresh() callback:
kpis.append(_kpi_card("My Metric", "42", "description"))Change the colour theme — edit the palette constants:
ACCENT = "#1f6fb2" # chart line / dot colour
BG = "#0d1117" # page background
CARD = "#161b22" # card background
BORDER = "#30363d" # card / table borderRun the bot and the dashboard side-by-side so you can monitor results in real time.
Option A — two terminal windows
# Terminal 1 – run the bot (or let Azure Functions run it on schedule)
python app.py
# Terminal 2 – start the dashboard
python dashboard/dashboard.pyOption B — background process (Linux / macOS)
python dashboard/dashboard.py &
echo "Dashboard PID: $!"Option C — screen or tmux
tmux new-session -d -s bot 'python app.py'
tmux new-session -d -s dash 'python dashboard/dashboard.py'
tmux ls # list sessions
tmux attach -t dash # attach to dashboard sessionTo keep the dashboard available 24/7, deploy it alongside the bot on a cloud VM or VPS.
- Create
/etc/systemd/system/tradebot-dashboard.service:
[Unit]
Description=tradebot live dashboard
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/tradebot
Environment="DATABASE_PATH=/home/ubuntu/tradebot/bot_state.db"
Environment="PORT=8050"
ExecStart=/usr/bin/python3 /home/ubuntu/tradebot/dashboard/dashboard.py
Restart=always
[Install]
WantedBy=multi-user.target- Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable tradebot-dashboard
sudo systemctl start tradebot-dashboard
sudo systemctl status tradebot-dashboard- View live logs:
journalctl -u tradebot-dashboard -fTo serve the dashboard on port 80/443 behind Nginx:
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:8050;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}| Symptom | Likely cause | Fix |
|---|---|---|
ModuleNotFoundError: No module named 'dash' |
Dash not installed | pip install dash plotly |
| Page shows "Demo mode" banner | bot_state.db not found |
Set DATABASE_PATH to the correct path |
| Page shows "Demo mode" even after bot ran | Bot uses a different db path | Run ls -la *.db in the bot directory to find the actual filename |
| Browser shows "connection refused" | Dashboard not running / wrong port | Check the terminal; confirm port with lsof -i :8050 |
| KPI cards show stale data | Refresh interval too long | Reduce POLL_INTERVAL_MS or hard-refresh the browser |
OSError: [Errno 98] Address already in use |
Port 8050 taken | PORT=8051 python dashboard/dashboard.py |
| Parameter | File | Default | Description |
|---|---|---|---|
market |
app.py |
BITVAVO |
Exchange identifier (CCXT name) |
symbol |
app.py |
BTC/EUR |
Trading pair |
time_frame |
app.py |
2h |
OHLCV candle interval |
window_size |
app.py |
204 |
Lookback candles (~17 days) |
initial_balance |
backtest.py |
400 |
Starting capital (EUR) |
percentage_of_portfolio |
strategy.py |
25 |
Portfolio % per buy order |
fast_period |
strategy.py |
9 |
Fast SMA period |
slow_period |
strategy.py |
50 |
Slow SMA period |
POLL_INTERVAL_MS |
dashboard/dashboard.py |
30000 |
Dashboard refresh interval (ms) |
PORT |
dashboard/dashboard.py |
8050 |
Dashboard HTTP port |
DATABASE_PATH |
dashboard/dashboard.py |
bot_state.db |
Path to the bot's SQLite database |
dashboard/backtest_dashboard.py is a fully standalone interactive web app that lets anyone run a
backtest in their browser — no Python or API keys required.
🚀 https://huggingface.co/spaces/mrrobot777/tradebot
The dashboard is hosted on Hugging Face Spaces and updated automatically on every push to main
and on a weekly schedule (every Monday at 03:00 UTC).
Features:
- 8 strategies with editable parameters (default: KuCoin · USDT · BTC/USDT · Golden Cross)
- Quote Currency selector (USDT / USD / EUR) that filters the symbol list automatically
- 9 exchanges: KuCoin, OKX, Gate.io, Binance, Binance US, Bybit, Kraken, Bitvavo, Bitfinex
- 30+ pre-loaded tickers (BTC, ETH, SOL, DOGE, UNI, …) grouped by USDT / USD / EUR quote
- Timeframes: 15m, 1h, 4h, 1d, 1w
- Plotly dark-theme charts: equity curve + monthly returns heatmap
- KPI cards: total return, final value, Sharpe ratio, max drawdown, win rate, # trades
- Colour-coded trades log table
pip install -r dashboard/requirements.txt
python dashboard/backtest_dashboard.py # → http://127.0.0.1:8050




