Deployment

Deployment

Production now runs on Docker + PostgreSQL (Hostinger) As of June 2026 the live stack is Docker Compose on a Hostinger VPS with PostgreSQL — see Infra & Services (Docker) for the current topology, service definitions, and make docker-* targets. The systemd / SQLite material below describes the legacy Oracle setup and the still-present make deploy/sync/restart targets.

Package entry points

# All three are equivalent — pick whichever you prefer
python run.py
python -m arb_bot
# On Linux server the systemd service uses:
ExecStart=venv/bin/python run.py

Makefile shortcuts (from local machine)

Requires .env.deploy with DEPLOY_SSH_KEY, DEPLOY_USER (root), DEPLOY_HOST (187.127.181.8), DEPLOY_REMOTE_DIR (/opt/trading-bot). See Infra & Services for the full Docker reference.

CommandWhat it does
make docker-deployBuild HTML+frontend → rsync → DB migrate → rebuild & restart bot services (and re-resolve nginx)
make docker-deploy-codeCode-only deploy (skip the frontend rebuild)
make docker-statusdocker compose ps on the server
make docker-logsTail live container logs (Ctrl+C to stop)
make docker-restartRestart arb-bot dashboard token-refresher
make docker-sshSSH into the Hostinger server
make docker-setupOne-shot VPS provisioning (Docker + Node + ufw)
make ssl-initIssue the Let's Encrypt certificate (after DNS points here)
make build-htmlRebuild public/index.html + React app locally (run after editing any section file)
Deploys restart nginx on purpose Recreating the dashboard container gives it a new IP. nginx caches the dashboard upstream at startup, so docker_deploy.sh restarts nginx after every deploy — otherwise the proxy serves 502s (and OTP/login stop working) until nginx re-resolves.
Editing the playground Edit the relevant file in public/sections/, then run make build-html. The generated public/index.html is what gets deployed to Netlify. Never edit index.html directly.
Metrics workflow make fetch-metrics → downloads legacy metrics/*.json files locally → run python backtest_recovery.py to analyse partial-fill recovery performance, or use /analyze-trading-metrics in Claude Code for Config tuning recommendations.

Architecture Overview

The dashboard follows a modern decoupled architecture for better performance and interactivity:

  • Frontend: A React Single Page Application (SPA) built with Vite and Tailwind CSS. Located in frontend/, built into public/dist/.
  • Backend: A FastAPI server (dashboard_server.py) that provides real-time PnL, position tracking, and documentation metadata via REST APIs.
  • Nginx Proxy: Acts as the entry point, serving static files and proxying API requests to the FastAPI backend.

Systemd Services

Two separate services run on the production server:

ServicePurposeEntry Point
arb-bot.serviceThe core trading enginerun.py
token-refresher.serviceDhan token renewal independent of the bot looptoken_refresher.py --interval-sec 300
arb-dashboard.serviceFastAPI backend for dashboarddashboard_server.py

The token refresher unit template lives at scripts/token-refresher.service. The dashboard shows its last stored token health through /api/token/health.

Nginx Configuration

Nginx redirects all port 80 traffic, including direct IP requests, to https://portfolioplanner.online. The HTTPS server serves the SPA from /var/www/html/dist/ and proxies /api/* requests to localhost:8080. Configuration is maintained in scripts/nginx_dashboard.conf.

The dashboard server block includes 92.4.66.95, portfolioplanner.online, and www.portfolioplanner.online as accepted host names so domain traffic does not fall through to the default Nginx site.

Deployed documentation routes are not public. Requests to /docs/* use Nginx auth_request against /api/auth/nginx-check, which accepts only a valid dashboard session cookie. Unauthenticated users are redirected to /login before any docs route is served.

Dashboard Session Policy

Dashboard login uses Telegram OTP and an HttpOnly session cookie. Only one dashboard session is active at a time: a successful login issues a new signed JWT session id and invalidates any older dashboard cookie on its next API request or websocket connection. Logging out from an older invalidated browser cannot clear the current active session.