Academy/Live Deployment/Deploying Your Algorithm to Railway
Live DeploymentLesson 3

Deploying Your Algorithm to Railway

Step-by-step guide to deploying a Python trading bot to Railway — from repo setup and IB Gateway configuration to environment variables, health checks, and auto-restart.

20 minute read
4 key takeaways

Why Railway?

Railway is a Platform-as-a-Service (PaaS) that runs your code on managed servers. For algorithmic trading, it offers three things you need: always-on uptime (your algo runs 24/7 even when your laptop is off), easy environment variable management for IBKR credentials, and automatic restarts if your process crashes. It is the fastest path from "working locally" to "running in production" for solo developers.

FeatureRailwayAWS EC2 / DigitalOceanLocal Machine
Setup time~15 minutes1–2 hoursImmediate but not always-on
Cost$5–$20/month$10–$40/month$0 (but laptop must stay on)
Uptime99.9% SLA99.9% SLA (with config)Depends on your machine
DeploymentPush to GitHubSSH + manual setuppython scheduler.py
Auto-restartBuilt-inRequires systemd/PM2No
LogsLive in dashboardCloudWatch / journaldTerminal only

Architecture Overview

Your Railway project will have two services: (1) IB Gateway running headless in a Docker container, and (2) your Python trading bot connecting to it via the internal Railway network.

text
Railway Project
├── Service: ib-gateway        (Docker image: ghcr.io/gnzsnz/ib-gateway)
│   ├── Port 4002 (paper)      ← your bot connects here
│   ├── VNC port 5900          ← optional remote desktop
│   └── Env: TWS_USERID, TWS_PASSWORD, TRADING_MODE=paper
│
└── Service: sma-bot           (your GitHub repo)
    ├── scheduler.py           ← entry point
    ├── sma_strategy.py        ← signal + order logic
    ├── requirements.txt
    └── Env: IB_HOST, IB_PORT=4002, SYMBOL, FAST_PERIOD, SLOW_PERIOD

Step 1 — Prepare Your Repository

Create a new GitHub repo with the following structure. Keep your IBKR username and password out of the code — they go in Railway environment variables.

text
my-sma-bot/
├── sma_strategy.py      ← signal + IBKR execution logic
├── scheduler.py         ← daily run scheduler
├── requirements.txt     ← Python dependencies
├── Procfile             ← tells Railway what command to run
└── .gitignore           ← must include .env
text
# requirements.txt
ib_insync==0.9.86
pandas==2.2.2
schedule==1.2.1
pytz==2024.1
text
# Procfile — Railway reads this to know what to run
worker: python scheduler.py

Never Commit Credentials

Add .env to your .gitignore immediately. One accidental push of your IBKR username/password to a public repo will result in unauthorized account access. Use Railway environment variables exclusively.

Step 2 — Update Your Strategy to Use Environment Variables

Replace hardcoded values in your strategy with os.environ reads. This lets you change symbols or parameters without modifying code.

python
# sma_strategy.py — top of file
import os

SYMBOL      = os.environ.get('SYMBOL',      'AAPL')
FAST_PERIOD = int(os.environ.get('FAST_PERIOD', '10'))
SLOW_PERIOD = int(os.environ.get('SLOW_PERIOD', '30'))
IB_HOST     = os.environ.get('IB_HOST',     '127.0.0.1')
IB_PORT     = int(os.environ.get('IB_PORT', '4002'))   # 4002 = IB Gateway paper
CLIENT_ID   = int(os.environ.get('CLIENT_ID', '1'))
CAPITAL_PCT = float(os.environ.get('CAPITAL_PCT', '0.95'))

Step 3 — Create a Railway Account and Project

  • Go to railway.app and sign up with your GitHub account
  • Click "New Project" → "Deploy from GitHub repo"
  • Select your my-sma-bot repository
  • Railway auto-detects the Procfile and starts a build

Step 4 — Add the IB Gateway Service

IB Gateway needs to run as a separate service in the same Railway project. The community maintains a Docker image that runs IB Gateway headlessly.

  • In your Railway project, click "+ New Service" → "Docker Image"
  • Image: ghcr.io/gnzsnz/ib-gateway:latest
  • Click on the service → "Variables" tab → add: TWS_USERID (your IBKR username), TWS_PASSWORD (your IBKR password), TRADING_MODE=paper, TWS_ACCEPT_INCOMING=yes
  • Under "Settings" → expose internal port 4002
  • Deploy the service — first startup takes ~60 seconds as it initializes IB Gateway

Use Paper Trading First

Set TRADING_MODE=paper in the IB Gateway service to connect to IBKR's paper trading environment. Only switch to TRADING_MODE=live after 30+ days of consistent results. The paper environment is completely separate from your real account.

Step 5 — Configure Environment Variables for Your Bot

In your sma-bot service → Variables tab, add the following. IB_HOST uses the Railway internal hostname for the ib-gateway service (Railway automatically handles internal service discovery).

text
# Railway Environment Variables — sma-bot service
SYMBOL=AAPL
FAST_PERIOD=10
SLOW_PERIOD=30
CAPITAL_PCT=0.95
IB_PORT=4002
CLIENT_ID=1

# Railway internal hostname for ib-gateway service:
# Go to ib-gateway service → Settings → copy "Private Domain"
# It looks like: ib-gateway.railway.internal
IB_HOST=ib-gateway.railway.internal

Step 6 — Add Reconnection Logic to Your Bot

In production, IBKR connections drop. Your scheduler must handle this gracefully instead of crashing.

python
# Add this to sma_strategy.py run() function
def run_with_retry(max_attempts: int = 3):
    """Wrap run() with retry logic for dropped connections."""
    for attempt in range(1, max_attempts + 1):
        try:
            run()
            return          # Success — exit retry loop
        except Exception as exc:
            log.error('Attempt %d/%d failed: %s', attempt, max_attempts, exc)
            if attempt < max_attempts:
                log.info('Retrying in 30 seconds...')
                time.sleep(30)
            else:
                log.critical('All %d attempts failed. Alerting and exiting.', max_attempts)
                # Optional: send an alert via email/SMS here
                raise
python
# In scheduler.py, replace run() with run_with_retry()
from sma_strategy import run_with_retry

def job():
    if not is_market_day():
        return
    print(f'Running strategy...')
    run_with_retry(max_attempts=3)

Step 7 — Deploy and Verify

  • Push your final code to GitHub — Railway auto-deploys on every push to main
  • In Railway dashboard → sma-bot → "Logs" tab — confirm "Scheduler started" appears
  • Wait for 4:05 PM ET — check logs for the signal evaluation and any orders
  • In IBKR paper account (via IBKR mobile or web) — verify any BUY orders appear in trade history
  • If no orders: check logs for errors; confirm IB Gateway service shows "Connected" status

Step 8 — Monitoring in Production

Set up basic monitoring so you know when something breaks. At minimum, log every strategy run result and set Railway to alert you on crashes.

python
# Add to scheduler.py — simple log-based monitoring
import smtplib
from email.mime.text import MIMEText

ALERT_EMAIL = os.environ.get('ALERT_EMAIL', '')
SMTP_HOST   = os.environ.get('SMTP_HOST', '')

def send_alert(subject: str, body: str):
    """Send email alert on critical failures."""
    if not ALERT_EMAIL or not SMTP_HOST:
        return
    try:
        msg = MIMEText(body)
        msg['Subject'] = f'[SMA Bot] {subject}'
        msg['From'] = ALERT_EMAIL
        msg['To']   = ALERT_EMAIL
        with smtplib.SMTP(SMTP_HOST) as server:
            server.send_message(msg)
    except Exception:
        pass  # Don't let alert failure crash the process

def job():
    if not is_market_day():
        return
    try:
        run_with_retry(max_attempts=3)
        send_alert('Daily Run OK', f'Strategy ran at {datetime.now(ET).strftime("%H:%M ET")}. Check IBKR for orders.')
    except Exception as exc:
        send_alert('CRITICAL — Bot Failed', str(exc))

Common Issues and Fixes

ErrorLikely CauseFix
Connection refused on port 4002IB Gateway not yet startedAdd a 90-second sleep before connecting; IB Gateway takes ~60s to initialize
Authentication failedWrong credentials in env varsCheck TWS_USERID and TWS_PASSWORD; IBKR usernames are case-sensitive
No historical data returnedMarket closed; requesting during off-hoursUse durationStr with a buffer; IB Gateway returns empty on weekends for TRADES
Order rejected: Outside RTHTrying to trade outside 9:30–4:00 ETAdd market hours check before placing orders (useRTH=True already on data)
Client ID already in useAnother instance still connectedChange CLIENT_ID env var; use different IDs per Railway instance

You Are Now Running Live

Once your Railway deployment shows the scheduler running, IB Gateway connected, and you see a logged signal evaluation at 4:05 PM ET — your algorithmic trading system is live. Start with paper trading for 30 days. Monitor logs daily. After 30 days of consistent, expected behavior, you are ready to flip TRADING_MODE=live and allocate real capital — starting at 10% of your intended size.

Key Takeaways
  • Railway runs your bot as a persistent long-running process on a managed server
  • IB Gateway runs headless alongside your Python process using the ib-gateway Docker image
  • Environment variables store credentials — never commit them to your repository
  • Health checks and auto-restart protect against crashes and TWS disconnects