AlgoPro UniversityCourse home
Part 8 · Lesson

Paper trading → deploy to a VPS

Prove the bot on a testnet, then deploy it headless on a VPS with a systemd unit, log rotation, and a real kill switch — with safety flags standing between you and a mistake.

Your bot works on your laptop. Two things remain: prove it does the right thing without real money, and run it somewhere that stays up when your laptop sleeps. This lesson does both — and every step keeps a safety switch between you and an accident.

Stage 1 — paper/testnet, for real time

A backtest proved the *logic* over history. Paper trading on a live testnet proves the *plumbing* over real time: that your bar-close detection is right, that reconnection actually reconnects, that reconciliation survives a restart, that fills come back in the shape you expect. Run it for days, not minutes — long enough to cross a network hiccup, a full trade cycle, and a deliberate restart.

bashRun on the testnet with dry-run off (still fake money, but real orders on the sandbox)
# Talk to the sandbox, and let it place REAL orders — on fake balances.
export TESTNET=true       # sandbox endpoints, fake money
export DRY_RUN=false      # actually place orders (on the testnet)
export LIVE=true          # arm the loop

source .venv/bin/activate
python bot.py             # watch the logs; leave it for a few days

Stage 2 — the kill switch

Before deploying anything that can trade real money, you need a way to stop it instantly that does not depend on the bot behaving. We built two into the loop already: a STOP file and the LIVE flag. Either one flattens the position and idles the bot. On a server you can trip it with a single command, even over a flaky connection.

bashThe kill switch — flatten and idle, two ways
# Instant stop #1: drop a STOP file. The loop sees it, flattens, idles.
touch /opt/livebot/STOP

# Instant stop #2: disarm and restart (LIVE=false makes it idle).
sudo systemctl set-environment LIVE=false
sudo systemctl restart livebot

# Resume trading: remove the file (and re-arm if you disarmed).
rm /opt/livebot/STOP

Stage 3 — a VPS, running headless

A VPS (a cheap always-on Linux server near the exchange) runs the bot when your laptop is closed. Deployment is unglamorous and that is good: copy the code, create a virtual environment, put the secrets in the environment (never in the repo), and hand the process to systemd so the OS keeps it alive.

bashProvision the bot on the VPS
# On the VPS (Ubuntu), as a non-root user in a dedicated directory.
sudo mkdir -p /opt/livebot && sudo chown "$USER" /opt/livebot
cd /opt/livebot

git clone <your-private-repo> .        # code only — NO secrets committed
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

# Secrets live in a root-only env file, NOT in the repo or the unit file.
sudo tee /etc/livebot.env >/dev/null <<'EOF'
EXCHANGE_ID=binance
SYMBOL=BTC/USDT
TIMEFRAME=1h
API_KEY=your_LIVE_key
API_SECRET=your_LIVE_secret
TESTNET=false
DRY_RUN=false
LIVE=true
RISK_FRACTION=0.01
EOF
sudo chmod 600 /etc/livebot.env        # readable only by root

Stage 4 — the systemd unit

systemd is Linux’s service manager. A unit file tells it how to start the bot, where to read the environment, and — critically — to restart it if it ever exits. Combined with the loop’s own resilience, this gives two layers of "keep running": the loop survives errors, and if the process dies entirely, systemd brings it back.

bash/etc/systemd/system/livebot.service
[Unit]
Description=AlgoPro live trading bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=botuser
WorkingDirectory=/opt/livebot
EnvironmentFile=/etc/livebot.env            # secrets injected here, not in code
ExecStart=/opt/livebot/.venv/bin/python /opt/livebot/bot.py
Restart=on-failure                          # bring it back if it crashes
RestartSec=10                               # ...after a 10s pause
StartLimitIntervalSec=300
StartLimitBurst=5                           # but stop flapping if it can't stay up
# Hardening: limit what a compromised bot can touch.
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/livebot                 # only its own dir is writable

[Install]
WantedBy=multi-user.target
bashEnable, start, and watch it
sudo systemctl daemon-reload
sudo systemctl enable livebot        # start automatically on boot
sudo systemctl start livebot
sudo systemctl status livebot        # is it running?
journalctl -u livebot -f             # tail the live logs (from stdout)

Stage 5 — log rotation

A bot that runs for months will fill the disk with logs if you let it. The systemd/stdout logs are managed by journald automatically. For the file log our logger writes, rotate it so it never grows unbounded — either swap FileHandler for a RotatingFileHandler, or hand it to the system logrotate.

bash/etc/logrotate.d/livebot — cap and compress the file log
/opt/livebot/livebot.log {
    daily
    rotate 14            # keep 14 days
    compress
    missingok
    notifempty
    copytruncate         # rotate without needing to restart the bot
}

You built a live bot

Step back and look at what you assembled: a config that defaults to safe, a ccxt exchange wrapper that refuses to touch real money until armed, an order manager with idempotent client IDs and per-loop reconciliation, a resilient loop with backoff and a kill switch, and a systemd deployment that keeps it alive and its logs bounded. That is a real system — the five components of Part 1, made live.

From here the road is width, not novelty: more instruments, better sizing (Part 7), monitoring and alerts, and the same loop pointed at a different broker. The architecture does not change — which is exactly the payoff of building it properly once.