When your mean-reversion strategy crashes at 2:47 AM on a Saturday, the market does not wait. The bid-ask spread widens. Your unfilled orders sit orphaned in the exchange queue. Your competitors' algorithms harvest the liquidity dislocation you were supposed to capture.
This is not a hypothetical. In institutional quant shops, a crashed strategy is a quantifiable P&L event — measured in basis points lost per minute of downtime. The difference between a strategy that survives a segfault silently and one that alerts the on-call engineer at 3 AM lies entirely in process supervision architecture.
This article covers two production-grade solutions for keeping quantitative trading systems alive: supervisor, a user-space process manager ideal for development and small-scale deployments, and systemd, the init system that dominates production Linux servers. You will learn how to configure both, write health check scripts that actually catch failure modes specific to trading systems, and understand which tool belongs where in your infrastructure stack.
Why Trading Systems Need Dedicated Process Supervision
A Python script run with nohup python strategy.py & is not supervised. When that process dies — whether from an uncaught exception, a broken WebSocket connection, a memory leak, or a SIGKILL from the OOM killer — nobody knows. The process simply stops. Your strategy exits silently. Your capital sits idle.
Standard daemonization techniques (daemon = True in Python, fork() calls in C) solve the "run in background" problem but not the "survive crashes" problem. A properly supervised process enjoys three guarantees that no manual daemonization can provide:
- Automatic restart on crash: The supervisor detects process death and re-spawns the child within seconds.
- Restart limit enforcement: After N consecutive failures in a time window, the supervisor stops restarting and alerts you — preventing infinite crash loops that saturate CPU.
- Log capture: All stdout and stderr from the supervised process are captured to disk, giving you a forensic record of what happened before the crash.
For quantitative systems specifically, process supervision also provides a natural integration point for health check scripts that verify not just "is the process alive" but "is the strategy actually trading."
Part 1: supervisor — The User-Space Process Manager
When to Use supervisor
supervisor is a Python-written process manager that runs entirely in user space. It does not require root access, it works on any Linux system, and its configuration is dead simple. Use supervisor when:
- You are running on a shared VPS or a personal trading server without root.
- You want quick iteration during development —
supervisorctl rereadandsupervisorctl updatetake effect immediately. - You need to manage multiple unrelated processes (your trading strategy, a data collection daemon, a notification service) under one tool.
supervisor is not the right choice for a production server that must start your strategies automatically after a server reboot. For that, you need systemd (covered in Part 2).
Installing supervisor
# Debian / Ubuntu
sudo apt-get update
sudo apt-get install -y supervisor
# RHEL / CentOS / Amazon Linux
sudo yum install -y supervisor
# Verify installation
supervisord --version
supervisor consists of two components: supervisord, the daemon that manages child processes, and supervisorctl, the command-line client for controlling them.
Configuration Structure
supervisor reads configuration from /etc/supervisor/conf.d/*.conf on most systems. Each supervised program gets its own [program:x] section.
Minimal Production Configuration
; /etc/supervisor/conf.d/mean-reversion-strategy.conf
[program:mean-reversion-strategy]
; The command to run. Using an absolute path and virtual environment.
command=/home/trader/venv/bin/python /home/trader/strategies/mean_reversion.py
directory=/home/trader/strategies
user=trader
autostart=true
autorestart=true
; Startretries: number of restart attempts within 60 seconds before giving up.
startretries=5
; Exit codes in exitcodes are treated as "expected exits" — no restart.
exitcodes=0
; Stopsignal: signal used to stop the process gracefully.
stopsignal=TERM
stopwaitsecs=30
; Redirect stdout/stderr to log files
stdout_logfile=/var/log/supervisor/mean-reversion-stdout.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10
stderr_logfile=/var/log/supervisor/mean-reversion-stderr.log
stderr_logfile_maxbytes=50MB
stderr_logfile_backups=10
; Environment variables
environment=PYTHONUNBUFFERED="1",TICKDB_API_KEY="%(ENV_TICKDB_API_KEY)s"
; Priority: lower numbers start first, higher numbers stop first.
priority=999
Key configuration notes:
- The
%(ENV_VAR)ssyntax lets you reference environment variables set elsewhere — never hardcode API keys. autorestart=truemeans supervisor restarts the process after any non-zero exit code (unless the exit code is inexitcodes).stopwaitsecs=30gives your strategy 30 seconds to shut down gracefully when you issuesupervisorctl stop. If it does not exit, supervisor sends SIGKILL.
Managing supervisor
# Reload configuration after changes
sudo supervisorctl reread
sudo supervisorctl update
# Start, stop, restart a specific program
sudo supervisorctl start mean-reversion-strategy
sudo supervisorctl stop mean-reversion-strategy
sudo supervisorctl restart mean-reversion-strategy
# View status of all programs
sudo supervisorctl status
# View real-time tail of stdout log
sudo supervisorctl tail -f mean-reversion-strategy
Processing Trading-Specific Exit Codes
For quantitative strategies, some exit codes are expected and should not trigger a restart. For example, if your strategy exits deliberately when market conditions are unsuitable:
# mean_reversion.py — strategic exit codes
import sys
EXIT_MARKET_CLOSED = 10
EXIT_RISK_LIMIT_HIT = 11
EXIT_CONFIG_INVALID = 12
def check_market_conditions():
if not is_trading_hours():
print("Market closed — strategic exit.")
sys.exit(EXIT_MARKET_CLOSED)
if portfolio.risk_limit_exceeded():
print("Risk limit exceeded — halting strategy.")
sys.exit(EXIT_RISK_LIMIT_HIT)
Update your supervisor configuration to treat these as non-restart exits:
; Treat strategic exits as normal — do NOT restart
exitcodes=0;10;11;12
Log Rotation for Trading Logs
supervisor logs grow unbounded by default. In production, configure logrotate to manage them:
# /etc/logrotate.d/supervisor-trading
/var/log/supervisor/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 root root
sharedscripts
postrotate
/usr/bin/supervisorctl signal HUP supervisor > /dev/null 2>&1 || true
endscript
}
Part 2: systemd — The Production Standard
When to Use systemd
systemd is the init system on virtually every major Linux distribution (Ubuntu 20.04+, Debian 10+, RHEL 8+, Amazon Linux 2). If you have a production trading server, systemd is almost certainly already running. Use systemd when:
- You need your strategy to start automatically after a server reboot (this is systemd's core job).
- You need fine-grained control over resource limits (memory caps, CPU affinity, I/O priority).
- You are managing a multi-service trading infrastructure (data feed, strategy engine, risk manager, notification service).
- You need integration with system-level alerting (journald,
sd_notify, alerting viasystemdtimers).
Why systemd Is Different from supervisor
supervisor runs alongside your application in user space. systemd replaces the old init system — it is the first process the kernel starts (PID 1), and it owns the lifecycle of everything on the machine.
This means systemd knows things supervisor never will:
- When the server boots (
multi-user.target). - When the power button is pressed.
- The state of the entire machine — not just one process.
For trading systems, this matters because your strategy should start after the network is up and before market open — systemd's dependency ordering makes this explicit and reliable.
Writing a systemd Unit File
Unit files live in /etc/systemd/system/ for system-wide services or ~/.config/systemd/user/ for user-space services. Here is a production-grade unit file for a trading strategy:
; /etc/systemd/system/mean-reversion-strategy.service
[Unit]
; Human-readable description
Description=Mean Reversion Trading Strategy
; Start AFTER networking is ready, file systems are mounted
After=network-online.target local-fs.target
; Stop BEFORE network goes down — allows graceful shutdown
Before=network-pre.target
; If this crashes repeatedly, alert via standard monitoring
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
; Run as the trading user, not root
User=trader
Group=trader
; Working directory
WorkingDirectory=/home/trader/strategies
; The executable — absolute path to venv Python
ExecStart=/home/trader/venv/bin/python /home/trader/strategies/mean_reversion.py
; Environment variables loaded from a secure source
EnvironmentFile=/etc/environment.trading
Environment=PYTHONUNBUFFERED=1
; Graceful shutdown: systemd sends SIGTERM, waits 60 seconds, then SIGKILL
TimeoutStopSec=60
KillMode=process
; Restart policy: always restart on non-zero exit (unless in exitcodes list)
Restart=on-failure
RestartSec=5
; Resource limits — critical for preventing runaway processes
; Memory limit prevents OOM killer from affecting other services
MemoryMax=2G
MemoryHigh=1.5G
; CPU time limit
CPUQuota=200%
; Logging: systemd's journald captures stdout/stderr automatically
; No need for separate log files — query with journalctl
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mean-reversion
; Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/trader/strategies/logs /home/trader/strategies/state
PrivateTmp=true
; Deny network access if the strategy does not need it directly
; (If it connects via WebSocket/REST, set this appropriately)
RestrictNamespaces=true
[Install]
; Start automatically on boot after multi-user target
WantedBy=multi-user.target
Resource Limits for Trading Strategies
The MemoryMax and MemoryHigh directives in systemd deserve special attention for quantitative systems. A memory leak in a long-running strategy can consume gigabytes of RAM — without a hard limit, the Linux OOM killer will terminate your process at an unpredictable moment, potentially mid-trade. With MemoryHigh, systemd starts throttling memory allocation before hitting the hard MemoryMax limit, giving you time to investigate.
# Check memory consumption of your strategy
systemctl status mean-reversion-strategy
# View real-time logs
journalctl -u mean-reversion-strategy -f
# View logs since last restart
journalctl -u mean-reversion-strategy --since "1 hour ago"
Enabling and Starting the Service
# Reload systemd to pick up new unit files
sudo systemctl daemon-reload
# Enable: start automatically on boot
sudo systemctl enable mean-reversion-strategy
# Start now
sudo systemctl start mean-reversion-strategy
# Check status
sudo systemctl status mean-reversion-strategy
# Stop, restart
sudo systemctl stop mean-reversion-strategy
sudo systemctl restart mean-reversion-strategy
# View recent log entries
journalctl -u mean-reversion-strategy -n 50 --no-pager
Health Checks with systemd
systemd supports native health checks via the ExecStartPre and ExecStartPost directives. You can also use a watchdog mechanism for deeper liveness verification.
Basic Pre-Start Health Check
# Create a pre-start health check script
# /home/trader/scripts/pre-start-check.sh
#!/bin/bash
set -euo pipefail
# Check that the market data API is reachable
curl -sf --max-time 5 https://api.tickdb.ai/v1/symbols/available > /dev/null
if [ $? -ne 0 ]; then
echo "ERROR: Cannot reach TickDB API — aborting strategy start"
exit 1
fi
# Check that log directory is writable
if [ ! -w /home/trader/strategies/logs ]; then
echo "ERROR: Log directory not writable"
exit 1
fi
# Check available disk space (require at least 5GB)
AVAILABLE=$(df -BG /home/trader/strategies | awk 'NR==2 {print $4}' | tr -d 'G')
if [ "$AVAILABLE" -lt 5 ]; then
echo "ERROR: Less than 5GB disk space available"
exit 1
fi
echo "Health checks passed — starting strategy"
exit 0
Add this to your unit file:
ExecStartPre=/home/trader/scripts/pre-start-check.sh
Watchdog-Based Liveness Monitoring
systemd's built-in watchdog (WatchdogSec) sends a ping to your process and expects a pong back via sd_notify. If your process does not respond, systemd restarts it.
# mean_reversion.py — watchdog support
import socket
import os
import time
# Use sd_notify to communicate with systemd
def sd_notify(state, message=""):
notify_socket = os.environ.get("NOTIFY_SOCKET")
if not notify_socket:
return
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
msg = f"STATUS={message}\n{state}"
sock.sendto(msg.encode(), notify_socket)
sock.close()
# Tell systemd we are ready
sd_notify("READY=1", "Strategy initialized, connecting to market data")
# Send watchdog ping every 30 seconds (must be less than WatchdogSec in unit file)
last_ping = time.time()
while True:
try:
run_strategy_iteration()
# Check position health
if not check_positions_healthy():
sd_notify("STATUS=Critical: Position check failed", "WATCHDOG=1")
raise RuntimeError("Position health check failed")
# Send watchdog ping every 30 seconds
if time.time() - last_ping > 25:
sd_notify("STATUS=Running", "WATCHDOG=1")
last_ping = time.time()
time.sleep(1)
except Exception as e:
sd_notify("STATUS=Exception: " + str(e), "ERRNO=1")
raise
Update the unit file to enable the watchdog:
; Send watchdog pings every 30 seconds; restart if no pong within 60 seconds
WatchdogSec=60
Part 3: Writing Effective Health Check Scripts
Health check scripts for trading systems must answer a deeper question than "is the process alive?" They must answer "is the strategy actually functioning?"
A process can be alive but broken — connected to the WebSocket, receiving ticks, but failing silently because a calculation error causes it to place orders with zero quantity.
Multi-Layer Health Check Architecture
#!/bin/bash
# /home/trader/scripts/health-check.sh
# Exit 0 = healthy, Exit 1 = unhealthy, Exit 2 = degraded (restart not needed)
set -o pipefail
STRATEGY_NAME="mean-reversion-strategy"
LOG_DIR="/home/trader/strategies/logs"
STATE_FILE="$LOG_DIR/strategy_state.json"
# Layer 1: Is the process running?
if ! systemctl is-active --quiet "$STRATEGY_NAME"; then
echo "CRITICAL: Process not running"
exit 1
fi
# Layer 2: Did it crash recently? Check journal for restarts
CRASH_COUNT=$(journalctl -u "$STRATEGY_NAME" --since "1 hour ago" \
| grep -c "Strategy terminated" || true)
if [ "$CRASH_COUNT" -gt 3 ]; then
echo "CRITICAL: $CRASH_COUNT restarts in the last hour"
exit 1
fi
# Layer 3: Is the state file being updated?
if [ -f "$STATE_FILE" ]; then
LAST_UPDATE=$(stat -c %Y "$STATE_FILE" 2>/dev/null)
NOW=$(date +%s)
AGE=$((NOW - LAST_UPDATE))
if [ "$AGE" -gt 300 ]; then # 5 minutes
echo "WARNING: State file not updated in $AGE seconds"
exit 2 # Degraded — do not restart, but alert
fi
fi
# Layer 4: Check position consistency
if [ -f "$STATE_FILE" ]; then
ORDERS_COUNT=$(jq -r '.open_orders // 0' "$STATE_FILE" 2>/dev/null)
if [ "$ORDERS_COUNT" -gt 50 ]; then
echo "WARNING: Unusually high open order count: $ORDERS_COUNT"
exit 2
fi
fi
# Layer 5: Log file anomaly detection
STDERR_LOG="/var/log/supervisor/mean-reversion-stderr.log"
if [ -f "$STDERR_LOG" ]; then
# Count ERROR entries in the last 5 minutes
ERROR_COUNT=$(tail -n 1000 "$STDERR_LOG" | \
awk -v since="$(date -d '5 minutes ago' +%s)" \
'/ERROR/ && $1 > since {count++} END {print count+0}' 2>/dev/null)
if [ "$ERROR_COUNT" -gt 20 ]; then
echo "WARNING: $ERROR_COUNT errors in recent logs"
exit 2
fi
fi
echo "OK: All health checks passed"
exit 0
Integrating Health Checks into Monitoring
For production systems, wire the health check into your alerting pipeline:
# check_health.py — called by cron or monitoring system
import subprocess
import requests
import os
def run_health_check():
result = subprocess.run(
["/home/trader/scripts/health-check.sh"],
capture_output=True, text=True
)
status = "HEALTHY" if result.returncode == 0 else \
"DEGRADED" if result.returncode == 2 else "UNHEALTHY"
# Send to monitoring (example: generic webhook)
webhook_url = os.environ.get("ALERT_WEBHOOK_URL")
if webhook_url and result.returncode != 0:
message = f"[{status}] Mean Reversion Strategy\n{result.stdout}"
requests.post(
webhook_url,
json={"text": message},
timeout=5
)
# Auto-restart only for truly dead processes
if result.returncode == 1:
print("CRITICAL: Attempting automatic restart")
subprocess.run(["sudo", "systemctl", "restart", "mean-reversion-strategy"])
if __name__ == "__main__":
run_health_check()
Add to crontab for 5-minute health checks:
# /etc/cron.d/trading-health-checks
*/5 * * * * root /home/trader/venv/bin/python /home/trader/scripts/check_health.py >> /var/log/trading-health.log 2>&1
supervisor vs. systemd: Decision Guide
| Criteria | supervisor | systemd |
|---|---|---|
| Auto-start on boot | No (by itself) | Yes (native) |
| User-space only (no root) | Yes | No (system services need root) |
| Configuration complexity | Low | Medium |
| Log management | Manual (redirect to files) | Native journald |
| Resource limits | Basic (ulimit in config) | Extensive (MemoryMax, CPUQuota, I/O priority) |
| Watchdog support | No (add-on only) | Native sd_notify |
| Dependency ordering | Manual (priority field) | Native After= / Requires= |
| Best for | Dev/staging, shared hosting | Production servers |
The pragmatic answer: Use supervisor during development on your local machine or a cheap VPS. Use systemd on every production server. Many teams run both — supervisor for quick local iteration, systemd as the authoritative production supervisor.
A Note on Strategy-Specific Considerations
The configurations above assume a single-process strategy. If your trading infrastructure spans multiple processes — a data ingestion daemon, a risk engine, a strategy executor, and a GUI dashboard — systemd's native support for template units (services ending in @.service) handles dynamic instance management elegantly:
; Scale N strategy instances automatically
; Start with: systemctl start strategy@1 strategy@2 strategy@3
; Each instance gets its own config via %i
ExecStart=/home/trader/venv/bin/python /home/trader/strategies/strategy_runner.py %i
For multi-process architectures, also consider process groups in supervisor or systemd's Slice= directives to isolate resource consumption and apply security boundaries between components.
Closing
The difference between a quant strategy that crashes once and never recovers and one that bounces back within seconds is not a more robust programming language or a better exception handler. It is supervision architecture.
supervisor gives you a quick, portable way to keep your strategy alive during development. systemd gives you a production-grade solution that survives server reboots, enforces resource limits, and integrates with your system's alerting infrastructure.
Write the health check scripts first. A strategy that is monitored but not supervised can at least alert you before the damage compounds. A strategy that is supervised but not monitored is better — it fixes itself — but you still want to know when it is struggling.
This article does not constitute investment advice. Automated trading systems involve significant risk, including the risk of total capital loss. Ensure adequate testing and risk controls are in place before deploying any strategy to production.
Next Steps
If you are setting up a development environment, install supervisor, write your first unit file, and run your strategy with autorestart=true. Validate that it actually restarts after a kill -9.
If you are moving to production, migrate to systemd. Write the health check scripts in parallel. Configure WatchdogSec and sd_notify. Set MemoryMax to a value that gives you headroom but prevents runaway memory growth.
If you want to go deeper on monitoring, explore Prometheus node_exporter metrics for system-level visibility, Grafana dashboards for strategy-level health, and alert routing via PagerDuty or OpsGenie for 3 AM coverage.
If you use AI coding tools, search for the tickdb-market-data SKILL in your tool's marketplace to get production-ready code templates for connecting your supervised strategies to TickDB's market data infrastructure.