ApexHelixCortexApex
Notifications
You're all caught up. New activity will show up here.
Preferences
AppearanceChoose how Apex looks. System matches your device.
LightDarkSystem
Account
Hosting apex serve is the engine. It runs on hardware you control, holds your broker credentials, and executes every trade. The cloud dashboard at cloud.apex.fusion.dev watches what it's doing and lets you operate it (start/stop strategies, kill switch, watchlist edits), but the cloud never sees your broker keys and never submits an order on your behalf. This guide walks the three deployment shapes: Hetzner VPS. €5/mo, recommended starting point. Docker on your own host. Same image, any infra you already run. Local laptop / NUC / Mac mini / Pi. Development, paper trading, home-lab setups. For the trust boundary in detail, see Architecture and Security. Before you start You need: A cloud.apex.fusion.dev account. Sign in with magic link; the wizard at /connect mints the registration token. Alpaca paper-trading API keys (APCA_API_KEY_ID + APCA_API_SECRET_KEY) from the Alpaca paper dashboard. Live trading uses the same keys against https://api.alpaca.markets; default to paper until you've verified the engine and your strategies end-to-end. One of: a VPS, a Docker host, or a Mac / Linux box with Docker installed. The engine talks outbound to: wss://relay.apex.fusion.dev/v1/engine (WSS, 443). Apex relay. https://paper-api.alpaca.markets (HTTPS, 443). Alpaca REST. wss://stream.data.alpaca.markets/v2/iex (WSS, 443). Alpaca market data. wss://api.alpaca.markets/stream (WSS, 443). Alpaca trade updates. No inbound ports required. If you're firewalling outbound, allow the four destinations above. Step 1. Mint a registration token Sign into cloud.apex.fusion.dev. The first time you land without a connected engine you're redirected to /connect. Click Generate token. The wizard returns a shell snippet that looks like:
# Paste on the server where you want apex serve to run.
# Replace the APCA_* placeholders with your Alpaca API keys.
export APCA_API_KEY_ID=PK_REPLACE_ME
export APCA_API_SECRET_KEY=REPLACE_ME
# Optional: live trading uses https://api.alpaca.markets
# export APCA_API_BASE_URL=https://paper-api.alpaca.markets
 
docker run --rm \
  -e APCA_API_KEY_ID -e APCA_API_SECRET_KEY -e APCA_API_BASE_URL \
  -p 8080:8080 \
  ghcr.io/thefusionfoundry/apex:latest serve \
    --register wss://relay.apex.fusion.dev/v1/engine \
    --register-token eyJ...
The token is a JWT signed by cloud.apex.fusion.dev, validated by relay.apex.fusion.dev at handshake. It binds the engine to your tenant. Default lifetime is one year; rotate from the engine dashboard if it leaks. Step 2a. Hetzner VPS (€5/mo, recommended) Provision a CX22 (2 vCPU, 4 GB RAM, 40 GB SSD) in nbg1 (Nuremberg) or ash (Ashburn). Proximity to Alpaca matters less than proximity to you, the operator: the engine is I/O-bound on broker round-trips, not CPU-bound, so the CX22 is plenty.
# After cloud-init / ssh root@<ip>:
apt-get update && apt-get install -y docker.io
systemctl enable --now docker
 
mkdir -p /opt/apex && cd /opt/apex
 
# Persist the engine's SQLite chain across container restarts.
docker run -d --name apex --restart=always \
  -e APCA_API_KEY_ID -e APCA_API_SECRET_KEY \
  -v /opt/apex/data:/data \
  -p 8080:8080 \
  ghcr.io/thefusionfoundry/apex:latest serve \
    --register wss://relay.apex.fusion.dev/v1/engine \
    --register-token <YOUR_JWT> \
    --storage /data
 
docker logs -f apex
You should see relay handshake accepted within a second. Tab back to /connect in the cloud dashboard; the wizard advances to the Engine connected step as soon as the relay sees the first Snapshot frame. Step 2b. Docker on your own host Same docker run invocation as above. Two opinionated knobs: --restart=always (or --restart=unless-stopped) so the engine recovers from host reboots without you babysitting it. -v <path>:/data so the engine's SQLite chain (the provenance graph for every signal, gate, order, fill, audit event) survives container replacement. Without this, docker rm apex deletes your trade history. For a Compose file:
# /opt/apex/docker-compose.yml
services:
  apex:
    image: ghcr.io/thefusionfoundry/apex:latest
    restart: always
    command: >
      serve
      --register wss://relay.apex.fusion.dev/v1/engine
      --register-token ${APEX_REGISTRATION_TOKEN}
      --storage /data
    environment:
      APCA_API_KEY_ID: ${APCA_API_KEY_ID}
      APCA_API_SECRET_KEY: ${APCA_API_SECRET_KEY}
    volumes:
      - ./data:/data
    ports:
      - "8080:8080"
docker compose up -d and you're live. Copy-paste templates for Compose, systemd, Hetzner cloud-init, and Fly live under Templates. Step 2c. Local laptop / NUC Same image, same invocation, with one wrinkle: laptops sleep. The engine reconnects to the relay automatically on wake (the relay client uses exponential backoff to 60 seconds), but a sleeping machine isn't trading. For paper accounts this is fine; for live trading prefer a real always-on host. On a NUC or home server, the systemd unit form is convenient:
# /etc/systemd/system/apex.service
[Unit]
Description=Apex trading engine
After=docker.service
Requires=docker.service
 
[Service]
Restart=always
EnvironmentFile=/etc/apex/env
ExecStart=/usr/bin/docker run --rm --name apex \
  -e APCA_API_KEY_ID -e APCA_API_SECRET_KEY \
  -v /var/lib/apex:/data \
  -p 8080:8080 \
  ghcr.io/thefusionfoundry/apex:latest serve \
    --register wss://relay.apex.fusion.dev/v1/engine \
    --register-token ${APEX_REGISTRATION_TOKEN} \
    --storage /data
ExecStop=/usr/bin/docker stop apex
 
[Install]
WantedBy=multi-user.target
systemctl enable --now apex. Mac mini (launchd) For a Mac mini you want to leave running, wrap the same docker run in a launchd plist so macOS restarts the engine after reboot and keeps it alive if it crashes:
<!-- ~/Library/LaunchAgents/dev.fusion.apex.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>dev.fusion.apex</string>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>EnvironmentVariables</key>
  <dict>
    <key>APCA_API_KEY_ID</key><string>PK_REPLACE_ME</string>
    <key>APCA_API_SECRET_KEY</key><string>REPLACE_ME</string>
    <key>APEX_REGISTRATION_TOKEN</key><string>eyJ...</string>
  </dict>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/docker</string>
    <string>run</string><string>--rm</string><string>--name</string><string>apex</string>
    <string>-e</string><string>APCA_API_KEY_ID</string>
    <string>-e</string><string>APCA_API_SECRET_KEY</string>
    <string>-v</string><string>/Users/Shared/apex:/data</string>
    <string>-p</string><string>8080:8080</string>
    <string>ghcr.io/thefusionfoundry/apex:latest</string>
    <string>serve</string>
    <string>--register</string><string>wss://relay.apex.fusion.dev/v1/engine</string>
    <string>--register-token</string><string>$(APEX_REGISTRATION_TOKEN)</string>
    <string>--storage</string><string>/data</string>
  </array>
</dict>
</plist>
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/dev.fusion.apex.plist loads it. A MacBook will still pause the engine when it sleeps; for live trading prefer an always-on Mac mini or a VPS. Raspberry Pi (or any arm64 SBC) The published Docker image is multi-arch (linux/amd64, linux/arm64) so the same docker run invocation works on a Pi 4 or Pi 5 without any change. Two practical notes specific to a Pi: Use a USB SSD for /data, not the SD card. The Cortex chain is append-write; SD cards wear out fast under that workload. The Pi 4 / 5 are plenty for paper trading and modest live watchlists. If you start subscribing to dense market-data streams (50+ symbols at minute bars) and the engine logs bar channel full; dropping warnings, move to a VPS or a NUC. Step 3. Verify the round-trip In the cloud dashboard: Portfolio tab: equity, realized P&L, position count should match your Alpaca paper account. Orders tab: empty (no strategy is armed yet; that's step 4). Events tab: you should see apex.relay.connected Info events streaming in via SSE. On the engine host:
docker logs apex 2>&1 | tail -20
Look for relay handshake accepted, snapshot sent, periodic heartbeat lines. No disconnected / deadline_expired warnings. Step 4. Arm a strategy In the cloud dashboard: Watchlist tab. Add the symbols you want to trade. Strategies tab. Pick a strategy, configure its params, assign it to a watchlist row. The engine receives a start_strategy command over the relay; the strategy starts evaluating bars; signals flow through gates; gated signals submit orders. The kill-switch buttons (Cancel all orders, Flatten) on the Portfolio tab dispatch over the same command channel; both require a non-empty reason that lands in the provenance graph as a Critical event. Environment variables Beyond the broker credentials and registration token, a few env vars tune engine behavior: VarDefaultPurposeAPCA_API_KEY_ID(none)Alpaca public key (paper or live)APCA_API_SECRET_KEY(none)Alpaca secretAPCA_API_BASE_URLhttps://paper-api.alpaca.marketsFlip to https://api.alpaca.markets for live moneyAPEX_MARKET_DATA_FEEDiexiex (free) or sip (paid Algo Trader Plus). Crypto symbols (BTC/USD shape) auto-route to Alpaca's v1beta3/crypto/us endpoint; setting this to crypto is no longer needed and falls back to iex.APEX_RELAY_URL(none)wss://relay.apex.fusion.dev/v1/engine when registering with the hosted cloudAPEX_RELAY_TOKEN(none)Registration JWT from /connect; equivalent to --register-tokenAPEX_EQUITY_POLL_SECS60How often the engine refreshes account equity from the brokerAPEX_WS_PING_SECS60App-level WS heartbeat ping cadenceAPEX_WS_DEADLINE_SECS120Reconnect if no inbound frame for this longAPEX_RELAY_SNAPSHOT_SECS10Snapshot publish cadence to the relayAPEX_IDEMPOTENCY_TTL_SECS86400Idempotency cache TTLAPEX_IDEMPOTENCY_MAX_ENTRIES10000Per-tenant LRU cap Operating tips Stay on paper until you've watched the engine for at least a session. The Portfolio header's mode chip reads paper / live straight from the snapshot. The Cortex chain on the engine is the source of truth. Cloud snapshots are summary-level. For "why did this signal fire?" or "why did this gate reject?" investigations, the chain on the engine host (/data/cortex.db) has the full causal graph. Backups: /data directory. Snapshot it on whatever cadence matches your tolerance for losing audit history. The engine doesn't depend on backups; losing /data resets the chain but doesn't affect broker state (Alpaca is the source of truth for fills and positions). Upgrading the engine. Pull a new image, docker stop apex && docker rm apex, re-run the same docker run command. The registration token and storage volume carry forward; the engine reconnects to the relay on boot. Token rotation. From the engine dashboard, click Revoke on the engine row, then Generate new token on /connect. The revoked token is rejected at the next handshake; the engine reconnects with the new one when you redeploy. What stays on your hardware vs. what Fusion sees Quick summary. Full version in Security. Stays local: Broker API keys (APCA_*). Per-decision detail: which bar triggered which signal, which gate let it through, order parameters, fill price. Tenant configuration: gate thresholds, market timezone, per-strategy params. Visible to Fusion (via the relay): Snapshot summary every 10s: equity, position count, today's realized + unrealized P&L, strategy count, subscribed symbol list, paper / live mode flag. Audit events as they happen: connection lifecycle, kill-switch invocations, gate rejections (the event but not the full decision context, which stays in the chain on your host). The fact that your engine is reachable. If this split isn't acceptable for your use case (corporate policy, regulatory, plain preference), the dashboard is optional: the engine runs standalone and exposes the same API surface on localhost:8080. The hosted dashboard is the default but not the only way.