VPS for Algorithmic Trading: Running Trading Bots, Data Feeds, and Backtesting 24/7
Algorithmic trading bots need to run 24/7 — markets don’t sleep, and a bot running on a laptop gets interrupted by sleep mode, software updates, and power outages. A VPS provides always-on execution, low-latency connections to exchange APIs, and the compute for running backtests without occupying a local workstation. This guide covers the practical infrastructure for running trading algorithms on a VPS.
Why VPS for Trading
- Always-on execution: Strategies execute 24/7 including overnight, weekends, and when you’re away
- Low latency to exchanges: A USA VPS is typically 10–50ms from major US exchange APIs (Coinbase, Binance US, Kraken); a Japan VPS is 5–20ms from Bybit, OKX Asia, and Binance Japan
- Stable network: Data center connections have redundancy that home internet lacks
- Separation: Trading infrastructure separate from your daily-use machine reduces accident risk
Server Recommendations
- 2 vCPU / 2–4 GB RAM for Python-based bots with real-time data feeds
- NVMe storage for fast TimescaleDB time-series queries during backtests
- USA West/East for US crypto exchanges; Japan for Asian exchanges (Bybit, OKX); Hong Kong for mainland China proximity
Step 1: Install TimescaleDB for Market Data
sudo apt update
sudo apt install -y python3.12 python3.12-venv postgresql postgresql-contrib
# Install TimescaleDB extension
sudo apt install -y timescaledb-2-postgresql-16
sudo timescaledb-tune --quiet --yes
sudo systemctl restart postgresql
sudo -u postgres psql
CREATE DATABASE trading;
\c trading
CREATE EXTENSION IF NOT EXISTS timescaledb;
-- OHLCV (candlestick) data table
CREATE TABLE ohlcv (
time TIMESTAMPTZ NOT NULL,
symbol TEXT NOT NULL,
exchange TEXT NOT NULL,
timeframe TEXT NOT NULL,
open DOUBLE PRECISION,
high DOUBLE PRECISION,
low DOUBLE PRECISION,
close DOUBLE PRECISION,
volume DOUBLE PRECISION
);
-- Convert to hypertable for optimized time-series storage
SELECT create_hypertable('ohlcv', 'time');
-- Fast queries by symbol and timeframe
CREATE INDEX ON ohlcv (symbol, timeframe, time DESC);
-- Individual trade ticks
CREATE TABLE trades (
time TIMESTAMPTZ NOT NULL,
symbol TEXT NOT NULL,
exchange TEXT NOT NULL,
price DOUBLE PRECISION,
amount DOUBLE PRECISION,
side TEXT -- 'buy' or 'sell'
);
SELECT create_hypertable('trades', 'time');
\q
Step 2: Install Dependencies
mkdir -p /opt/trading && cd /opt/trading
python3.12 -m venv .venv
source .venv/bin/activate
pip install ccxt pandas sqlalchemy psycopg2-binary \
python-dotenv schedule asyncio aiohttp \
numpy scipy ta-lib
Step 3: Live Data Feed with CCXT
nano /opt/trading/data_feed.py
"""Collect live OHLCV data and store in TimescaleDB."""
import asyncio
import logging
import os
from datetime import datetime
import ccxt.async_support as ccxt
from sqlalchemy import create_engine, text
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
DB_URL = os.environ['DATABASE_URL']
engine = create_engine(DB_URL)
SYMBOLS = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT']
TIMEFRAMES = ['1m', '5m', '1h']
async def fetch_and_store(exchange, symbol, timeframe):
try:
ohlcv = await exchange.fetch_ohlcv(symbol, timeframe, limit=100)
rows = [
{
'time': datetime.utcfromtimestamp(c[0] / 1000),
'symbol': symbol,
'exchange': exchange.id,
'timeframe': timeframe,
'open': c[1], 'high': c[2], 'low': c[3],
'close': c[4], 'volume': c[5],
}
for c in ohlcv
]
with engine.connect() as conn:
conn.execute(
text("INSERT INTO ohlcv VALUES (:time,:symbol,:exchange,:timeframe,:open,:high,:low,:close,:volume) ON CONFLICT DO NOTHING"),
rows
)
conn.commit()
logger.info(f"Stored {len(rows)} candles for {symbol} {timeframe}")
except Exception as e:
logger.error(f"Error fetching {symbol} {timeframe}: {e}")
async def main():
exchange = ccxt.binance({
'apiKey': os.environ.get('EXCHANGE_API_KEY'),
'secret': os.environ.get('EXCHANGE_SECRET'),
'sandbox': os.environ.get('SANDBOX', 'true').lower() == 'true',
})
await exchange.load_markets()
logger.info(f"Connected to {exchange.id} ({'sandbox' if exchange.sandbox else 'live'})")
while True:
tasks = [
fetch_and_store(exchange, sym, tf)
for sym in SYMBOLS for tf in TIMEFRAMES
]
await asyncio.gather(*tasks, return_exceptions=True)
await asyncio.sleep(60)
await exchange.close()
if __name__ == '__main__':
asyncio.run(main())
Step 4: Strategy Template (EMA Crossover)
nano /opt/trading/strategy.py
"""EMA crossover strategy — educational example only."""
import pandas as pd
import sqlalchemy as sa
import logging
import os
logger = logging.getLogger(__name__)
def get_ohlcv(symbol: str, timeframe: str, limit: int = 200) -> pd.DataFrame:
engine = sa.create_engine(os.environ['DATABASE_URL'])
with engine.connect() as conn:
df = pd.read_sql(
"""SELECT time, open, high, low, close, volume
FROM ohlcv WHERE symbol = %s AND timeframe = %s
ORDER BY time DESC LIMIT %s""",
conn, params=(symbol, timeframe, limit)
)
return df.sort_values('time').reset_index(drop=True)
def compute_signals(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
df['ema_fast'] = df['close'].ewm(span=12, adjust=False).mean()
df['ema_slow'] = df['close'].ewm(span=26, adjust=False).mean()
df['signal'] = 0
df.loc[df['ema_fast'] > df['ema_slow'], 'signal'] = 1 # Long
df.loc[df['ema_fast'] < df['ema_slow'], 'signal'] = -1 # Short
df['crossover'] = df['signal'].diff()
return df
def run_once(symbol='BTC/USDT', timeframe='1h'):
df = get_ohlcv(symbol, timeframe)
df = compute_signals(df)
latest = df.iloc[-1]
price = latest['close']
if latest['crossover'] == 2:
logger.info(f"BUY signal: {symbol} @ {price:.2f}")
# exchange.create_market_buy_order(symbol, amount)
elif latest['crossover'] == -2:
logger.info(f"SELL signal: {symbol} @ {price:.2f}")
# exchange.create_market_sell_order(symbol, amount)
Step 5: Secure API Key Storage
nano /opt/trading/.env
DATABASE_URL=postgresql://trading:password@localhost:5432/trading
EXCHANGE_API_KEY=your_exchange_api_key
EXCHANGE_SECRET=your_exchange_secret
SANDBOX=true # Set to false only for live trading
TELEGRAM_BOT_TOKEN=your_bot_token_for_alerts
TELEGRAM_CHAT_ID=your_chat_id
chmod 600 /opt/trading/.env
# Ensure only the trading user can read it
sudo chown trading:trading /opt/trading/.env
Step 6: systemd Services
sudo nano /etc/systemd/system/trading-feed.service
[Unit]
Description=Trading Data Feed
After=network.target postgresql.service
[Service]
User=trading
WorkingDirectory=/opt/trading
EnvironmentFile=/opt/trading/.env
ExecStart=/opt/trading/.venv/bin/python data_feed.py
Restart=on-failure
RestartSec=30s
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now trading-feed
sudo journalctl -u trading-feed -f
Telegram Alerts for Trades
import requests
def send_alert(message: str):
token = os.environ['TELEGRAM_BOT_TOKEN']
chat_id = os.environ['TELEGRAM_CHAT_ID']
requests.post(
f"https://api.telegram.org/bot{token}/sendMessage",
json={'chat_id': chat_id, 'text': message, 'parse_mode': 'HTML'}
)
# Usage in strategy:
send_alert(f"🟢 <b>BUY</b> {symbol} @ ${price:.2f}\nSignal: EMA crossover")
Getting Started
For US cryptocurrency exchanges, USA VPS plans at VPS.DO minimize API call round-trip time — 10–50ms to Coinbase, Kraken, and Binance US. For Asian exchanges, the Hong Kong VPS provides sub-30ms to most Asian exchange order books. NVMe storage keeps TimescaleDB backtests fast when processing years of tick data.
Conclusion
A VPS provides the always-on infrastructure, low-latency exchange connectivity, and stable network that algorithmic trading requires. The stack — TimescaleDB for time-series data, CCXT for exchange connectivity, systemd for process management, and Telegram for trade alerts — covers the infrastructure needs of most quantitative trading projects. Always start with paper trading (sandbox mode) before committing live capital to any automated strategy.