Skip to content

alpha-strike Setup Guide (Paper Trading Production)

End-to-end procedure to deploy alpha-strike on Oracle Cloud Always Free + Cloudflare Tunnel, receive TradingView webhooks, and place orders into moomoo SIMULATE / OANDA PRACTICE accounts.

Goal: Establish a zero-cost infrastructure path from TradingView alerts to broker paper-trading accounts that is independent of home IP changes (DHCP), works with TradingView Essential plan or above, and runs on Oracle Cloud Always Free.

Hardware: Verified to run on E2.1.Micro (1/8 OCPU, 1GB RAM). Memory usage stays around 600 MB / 954 MB.


Architecture

TradingView (Pine strategy / Alert)
       │  HTTPS Webhook
Cloudflare WAF Custom Rule (TradingView IP allowlist)
Cloudflare Tunnel (cloudflared)
       │  outbound only (NSG Ingress = 0 rules)
Oracle Cloud VM "alpha-strike-01" (E2.1.Micro)
  ├─ alpha-strike (FastAPI, localhost:8080)
  └─ moomoo OpenD  (localhost:11111)
moomoo SIMULATE account (US / HK stocks, paper trading)

1. Prerequisites

  • [ ] Oracle Cloud Infrastructure account (Always Free tier)
  • [ ] Cloudflare account (Free plan)
  • [ ] A custom domain (this guide uses alforgelabs.com as example)
  • [ ] moomoo securities account with OpenAPI-capable SIMULATE account
  • [ ] TradingView account on Essential plan or above (Webhook URL supported)
  • [ ] 1Password or equivalent secret manager
  • [ ] Local machine (Mac/Linux recommended) with ssh, curl, git

2. VM Provisioning (Oracle Cloud E2.1.Micro)

2-1. Create instance

Item Value
Image Canonical Ubuntu 24.04
Shape VM.Standard.E2.1.Micro (Always Free)
Public IPv4 Ephemeral
SSH Public Key Your local ~/.ssh/id_ed25519.pub

2-2. NSG (Network Security Group)

Target state (after Cloudflare Tunnel and Access for SSH are wired up):

Direction Protocol Source Port Note
Ingress 0 rules
Egress All 0.0.0.0/0 All Egress-only

During initial setup, allow SSH from your home IP temporarily; remove after Cloudflare Access for SSH is verified (§4-3).

2-3. OS initial setup

ssh ubuntu@<VM_PUBLIC_IP>
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl jq build-essential

2-4. Memory hardening (1 GB RAM)

# Swap 4GB
sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# zram 477MB (lz4)
sudo apt install -y zram-tools
echo 'ALGO=lz4
PERCENT=50' | sudo tee /etc/default/zramswap
sudo systemctl restart zramswap

2-5. Install uv

curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.bashrc
uv --version

3. Cloudflare Tunnel Setup

3-1. Move domain to Cloudflare nameservers

Change nameservers at your registrar to Cloudflare-provided values. Propagation can take a few hours.

dig +short NS yourdomain.com
# Expect: xxx.ns.cloudflare.com / yyy.ns.cloudflare.com

3-2. Create the Tunnel

  1. Cloudflare dashboard → Zero Trust → Networks → Tunnels → Create a tunnel
  2. Tunnel type: Cloudflared
  3. Tunnel name: alpha-strike-prod
  4. Save tunnel — a token is issued
  5. On the VM, run the install command shown by Cloudflare:
sudo cloudflared service install <TOKEN>
sudo systemctl status cloudflared

3-3. Public hostname (webhook)

In the Tunnel page → Hostname routes (Beta) tab → Add a hostname route:

Field Value
Subdomain strike
Domain yourdomain.com
Type / Service HTTP
URL / Target localhost:8080

Browse to https://strike.yourdomain.com — a 502 Bad Gateway is expected because alpha-strike is not yet running (the Tunnel itself is connected).

3-4. Route SSH through the same Tunnel

Add another hostname route:

Field Value
Subdomain ssh
Domain yourdomain.com
Type / Service SSH
URL / Target localhost:22

Local ~/.ssh/config:

Host oracle-strike
  HostName ssh.yourdomain.com
  User ubuntu
  ProxyCommand cloudflared access ssh --hostname %h
  IdentityFile ~/.ssh/id_ed25519

Now ssh oracle-strike works regardless of your home IP changes.

3-5. Drop NSG Ingress to zero

Once Cloudflare Access for SSH is verified, delete all OCI NSG Ingress rules.


4. Cloudflare WAF Custom Rule (TradingView IP allowlist)

TradingView publishes 4 official source IPv4 addresses. Only allow /webhook from those IPs.

4-1. Create the rule

  1. Cloudflare dashboard → select your domain → Security → Security rules
  2. In the Custom rules row click Create rule
  3. Configure:
Field Value
Rule name Allow only TradingView IPs to /webhook
Expression (paste below in text mode)
Then take action Block
(http.host eq "strike.yourdomain.com" and http.request.uri.path eq "/webhook" and not ip.src in {52.89.214.238 34.212.75.30 54.218.53.128 52.32.178.7})

Deploy to apply immediately.

TradingView source IPs are published in the official Help Center. Re-check this page if you change the rule.

Defense in depth: even with the WAF IP allowlist, the passphrase check on the request body is mandatory.

4-2. Verify

# A POST from a non-TradingView IP should be blocked
curl -i -X POST https://strike.yourdomain.com/webhook \
  -H "Content-Type: application/json" \
  -d '{"passphrase":"dummy"}'
# → HTTP/2 403 + Cloudflare block page

# /health is unaffected because the rule targets /webhook only
curl https://strike.yourdomain.com/health
# → {"status":"ok"} (after alpha-strike is running)

5. moomoo OpenD Setup

5-1. Install

Download moomoo_OpenD_*_Ubuntu18.04.tar.gz from the moomoo OpenAPI page, then transfer to the VM:

# From Mac
scp ~/Downloads/moomoo_OpenD_*.tar.gz oracle-strike:~/
# On the VM
ssh oracle-strike
mkdir -p ~/moomoo_OpenD && tar xzf moomoo_OpenD_*.tar.gz -C ~/moomoo_OpenD --strip-components=1

5-2. Generate MD5 of trading password

# On Mac (input is hidden)
read -s -p "moomoo trade password: " PW && echo -n "$PW" | md5
unset PW

Use the resulting 32-char hex as MOOMOO_TRADE_PWD_MD5 below.

5-3. Device token (headless VM workaround)

OpenD's initial login requires SMS + CAPTCHA which is not feasible on a headless VM. Authenticate on a Mac first and rsync ~/.com.moomoo.OpenD/F3CNN/ to the VM:

# After SMS-authenticating moomoo OpenD on the Mac
rsync -avz ~/.com.moomoo.OpenD/F3CNN/ oracle-strike:~/.com.moomoo.OpenD/F3CNN/

5-4. Environment file

ssh oracle-strike
sudo tee /etc/moomoo_OpenD.env > /dev/null <<'EOF'
MOOMOO_LOGIN_ACCOUNT=your-moomoo-account
EOF
sudo chmod 600 /etc/moomoo_OpenD.env

5-5. systemd unit

sudo tee /etc/systemd/system/moomoo-opend.service > /dev/null <<'EOF'
[Unit]
Description=moomoo OpenD CLI
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=ubuntu
EnvironmentFile=/etc/moomoo_OpenD.env
WorkingDirectory=/home/ubuntu/moomoo_OpenD
ExecStart=/home/ubuntu/moomoo_OpenD/OpenD -login_account=${MOOMOO_LOGIN_ACCOUNT} -login_by_remember=1 -api_ip=127.0.0.1 -api_port=11111 -console=1 -log_level=info
Restart=always
RestartSec=10
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now moomoo-opend

-login_by_remember=1 lets OpenD log in via the device token transferred in §5-3, avoiding repeated SMS/CAPTCHA prompts.


6. alpha-strike Deployment

6-1. Install from PyPI

alpha-strike is distributed on PyPI (pypi.org/project/alpha-strike/). Install into an isolated venv and use the alpha-strike CLI.

ssh oracle-strike
sudo apt install -y python3.12 python3.12-venv  # if not installed yet

# Create a dedicated venv under /opt
sudo mkdir -p /opt/alpha-strike
sudo chown ubuntu:ubuntu /opt/alpha-strike
cd /opt/alpha-strike

uv venv --python 3.12
uv pip install alpha-strike

# Verify
.venv/bin/alpha-strike --version
# → alpha-strike 0.3.0 or later

6-2. Create .env

sudo mkdir -p /etc/alpha-strike
sudo tee /etc/alpha-strike/.env > /dev/null <<'EOF'
# Required
WEBHOOK_PASSPHRASE=<random string, 32+ chars>

# moomoo
MOOMOO_HOST=127.0.0.1
MOOMOO_PORT=11111
MOOMOO_TRADE_PWD_MD5=<32-char MD5 hex from §5-2>
MOOMOO_TRADE_ENV=SIMULATE
# Over-sell guard (default ON): clamps a SELL to the actual position,
# skips it when no position is held — prevents moomoo "Not enough positions".
# Set MOOMOO_SELL_POSITION_GUARD=0 only to disable.
# Time-in-force for REAL US-market MARKET orders (default GTC). Daily alerts
# arrive after the market close, so GTC carries them over to the next session's
# open (with DAY they would expire unfilled).
# Set MOOMOO_TIME_IN_FORCE=DAY only to restore the legacy behavior.
# Note: SIMULATE (paper) always uses DAY regardless, as moomoo 10.7 rejects GTC for paper.
# Note: SIMULATE after-close signals (which expire as DAY) are re-submitted at the next
#   market open by carry-over (#89, default ON). Disable with CARRYOVER_ENABLED=0; tune
#   CARRYOVER_RESUBMIT_INTERVAL_SECONDS=300 / CARRYOVER_LOOKBACK_HOURS=48.
# Closed-loop quantity resolution via target_qty (default ON, v0.7.0+).
# Re-resolves the order side/quantity from the difference between the
# payload's target_qty (absolute target holding) and the actual position.
# Set MOOMOO_TARGET_QTY_RECONCILE=0 only to restore the legacy delta mode.
# Delayed re-reconciliation that reflects next-day GTC fills into the event
# log (default ON, v0.7.1+). Tune with PENDING_RECONCILE_INTERVAL_SECONDS=600.
# Set PENDING_RECONCILE_ENABLED=0 only to disable.

# OANDA (optional)
# OANDA_API_KEY=<your-token>
# OANDA_ACCOUNT_ID=<your-account-id>
# OANDA_ENV=PRACTICE
EOF
sudo chmod 600 /etc/alpha-strike/.env

Also store the WEBHOOK_PASSPHRASE in 1Password — you will paste the same value into TradingView's alert Message JSON later.

6-3. systemd unit

sudo tee /etc/systemd/system/alpha-strike.service > /dev/null <<'EOF'
[Unit]
Description=alpha-strike webhook server
After=moomoo-opend.service network-online.target
Wants=network-online.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/alpha-strike
EnvironmentFile=/etc/alpha-strike/.env
ExecStart=/opt/alpha-strike/.venv/bin/alpha-strike --host 0.0.0.0 --port 8080
Restart=always
RestartSec=5
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now alpha-strike

6-4. Smoke test (internal)

curl http://127.0.0.1:8080/health
# → {"status":"ok"}

6-5. Memory / service monitoring

Fetch scripts/check_memory.sh from the alpha-strike repository (not part of the PyPI distribution) and register it in cron:

sudo curl -fsSL \
  https://raw.githubusercontent.com/alforge-labs/alpha-strike/main/scripts/check_memory.sh \
  -o /usr/local/bin/check_alpha_strike_memory.sh
sudo chmod +x /usr/local/bin/check_alpha_strike_memory.sh

crontab -e
# Append:
*/5 * * * * /usr/local/bin/check_alpha_strike_memory.sh

Configure ~/.ntfy.env with NTFY_TOPIC=... to receive iPhone push notifications when memory exceeds thresholds (85% / 95%) via ntfy.sh.


7. TradingView Alert

7-1. Create the alert

  1. Open the target chart (e.g., BINANCE:BTCUSDT for testing, or your actual instrument)
  2. Click the bell icon → Create Alert
  3. Condition: pick your strategy / indicator
  4. Notifications tab:
  5. Webhook URL with https://strike.yourdomain.com/webhook
  6. Message field — paste the JSON below (replace <WEBHOOK_PASSPHRASE> with the actual .env value):
{
  "passphrase": "<WEBHOOK_PASSPHRASE>",
  "broker": "moomoo",
  "asset_class": "US",
  "action": "buy",
  "ticker": "US.AAPL",
  "quantity": 1,
  "run_mode": "paper",
  "strategy_id": "tv_smoke_test",
  "alert_name": "{{strategy.order.alert_message}}"
}
  1. Create

7-2. Always-fires test condition (works during market close)

Use a 24/7 crypto chart with an always-true condition to fire within seconds:

Field Value
Symbol BINANCE:BTCUSDT
Condition Type Greater Than
Source Price (Close)
Value 1
Frequency Once Per Bar

Why not Crossing Up 1? BTC is always above 1, so there is no "crossing" event to trigger on. Greater Than 1 evaluates true on every bar close, guaranteeing a fire.

The ticker:"US.AAPL" in the JSON is independent of the chart symbol that fires the alert.

7-3. Running CRYPTO strategies

In addition to US / HK equities, moomoo offers crypto (BTC / ETH / XRP, etc.) as a destination. alpha-strike accepts asset_class="CRYPTO" and routes through OpenSecTradeContext(filter_trdmarket=TrdMarket.CRYPTO) internally.

moomoo crypto is live (REAL) only — SIMULATE is rejected

moomoo's crypto trading API is live-environment only. Sending a crypto order with MOOMOO_TRD_ENV=SIMULATE makes the moomoo SDK return the type of environment param is wrong. alpha-strike detects this combination before connecting to OpenD and raises ValueError.

For paper validation, choose one of:

  • Order a BTC ETF (US.IBIT / US.FBTC / US.BITO) with asset_class=US (recommended)
  • Start with a small REAL position (MOOMOO_TRD_ENV=REAL, e.g. 0.001 BTC ≈ $80)
  • Use TradingView's built-in Paper Trading (bypass alpha-strike)

Sample JSON (REAL-buy 0.01 BTC, requires MOOMOO_TRD_ENV=REAL):

{
  "passphrase": "<WEBHOOK_PASSPHRASE>",
  "broker": "moomoo",
  "asset_class": "CRYPTO",
  "action": "buy",
  "ticker": "CC.BTC",
  "quantity": 0.01,
  "run_mode": "live",
  "strategy_id": "btc_ema_sma_trail40_v1"
}

Notes: - moomoo crypto trades 24/7 (no market close) - US-residency restriction applies (FinCEN MSB). The broker-side crypto account must be enabled - Symbols are CC.BTC / CC.ETH / CC.XRP etc. (CC. prefix + uppercase symbol)

See TradingView × alpha-strike Integration §3-1b for the full payload specification.


8. End-to-end Verification

8-1. External reachability

# From Mac (expect 401 if WAF is off, 403 if WAF is on)
curl -i -X POST https://strike.yourdomain.com/webhook \
  -H "Content-Type: application/json" \
  -d '{"passphrase":"WRONG","broker":"moomoo","asset_class":"US","action":"buy","ticker":"US.AAPL","quantity":1,"run_mode":"paper"}'

8-2. SIMULATE order arrival

After the TradingView alert fires:

ssh oracle-strike
sudo journalctl -u alpha-strike -n 50 --no-pager | grep -E "Webhook受信|order|34\.212\.75\.30|52\.32\.178\.7"

# Fetch the helper scripts (not part of the PyPI distribution)
curl -fsSL https://raw.githubusercontent.com/alforge-labs/alpha-strike/main/scripts/show_simulate_status.py \
  -o /tmp/show_simulate_status.py
/opt/alpha-strike/.venv/bin/python /tmp/show_simulate_status.py
# → pending_orders should list the test order

8-3. Cancel the test order

curl -fsSL https://raw.githubusercontent.com/alforge-labs/alpha-strike/main/scripts/cleanup_simulate_orders.py \
  -o /tmp/cleanup_simulate_orders.py
/opt/alpha-strike/.venv/bin/python /tmp/cleanup_simulate_orders.py
# → pending_orders=0

9. Secret Rotation

When rotating WEBHOOK_PASSPHRASE, all three locations must be synced:

Location How
1Password Update the field in the alpha-strike item
VM .env ssh oracle-strikesudo sed -i "s\|^WEBHOOK_PASSPHRASE=.*\|WEBHOOK_PASSPHRASE=<new>\|" /etc/alpha-strike/.envsudo systemctl restart alpha-strike
TradingView Message JSON Edit the alert → update passphrase → Save → Restart

Common pitfall: forgetting to update the VM .env will produce 401 Unauthorized on every alert. .env is read only at systemd start, so restart is required.


10. Operational Routine

10-1. Daily (after US market close)

  • [ ] scripts/show_simulate_status.py --json --days 1 | jq '.recent_orders' — verify the day's fills
  • [ ] Backup event_logger JSONL for later pnl reconciliation against alpha-forge

10-2. Weekly (Sunday night)

  • [ ] Review scripts/run_apt_maintenance.sh results
  • [ ] Check OpenD memory (systemctl status moomoo-opend | grep Memory)
  • [ ] Confirm there are no long-lived pending_orders

10-3. Incident response

Symptom First action
Unexpected order pattern (highest urgency) kill switch: echo "reason" \| sudo tee /etc/alpha-strike/MAINTENANCE to return 503 instantly (no restart) → cleanup_simulate_orders.py → investigate → sudo rm /etc/alpha-strike/MAINTENANCE to restore
Webhook 502 floods systemctl status alpha-strike moomoo-opendsystemctl restart alpha-strike
OpenD disconnect systemctl restart moomoo-opend → if device token expired, re-auth on Mac and rsync

Kill switch (maintenance mode) details

The first action when an incident is detected. The service stays up but /webhook returns 503 instantly (alpha-strike v0.4.0+). TradingView will also auto-disable the alert after consecutive 5xx responses.

Method Switching Use case
File flag /etc/alpha-strike/MAINTENANCE No restart, instant Primary tool for emergencies. The file body is echoed in the 503 detail.
Env var MAINTENANCE_MODE=1 .env + restart Planned stops, locked-in via systemd Environment=
# Activate
echo "ticker AAPL runaway: investigating" | sudo tee /etc/alpha-strike/MAINTENANCE

# Verify (temporarily disable the WAF rule to test from your home IP)
# Note: if required body fields (broker/asset_class/action/ticker, etc.) are missing,
#   request-body validation (422) runs before the kill switch, so use a complete payload.
curl -i https://strike.yourdomain.com/webhook \
  -X POST -H "Content-Type: application/json" \
  -d '{"passphrase":"x","broker":"moomoo","asset_class":"US","action":"buy","ticker":"US.AAPL","quantity":1,"run_mode":"paper"}'
# → 503 maintenance: ticker AAPL runaway: investigating

# Deactivate
sudo rm /etc/alpha-strike/MAINTENANCE

Note: /health still returns 200 during maintenance (so external health checks / the Cloudflare Tunnel stay alive). The kill switch is checked before the passphrase, so failed passphrase attempts during maintenance are not logged.

10-4. Abort criteria

Stop the integration immediately if:

  • Same ticker fires more than 5 times in 5 minutes
  • pending_orders linger beyond 24 hours
  • total_assets drops beyond your strategy's expected max drawdown
  • Cloudflare WAF / Access returns unexplained 403s

11. Toward LIVE Migration (reference)

Consider LIVE migration only after 3+ months of stable paper trading with pnl divergence from alpha-forge backtest below 5%. LIVE additionally requires:

  • moomoo production account with MOOMOO_TRADE_ENV=REAL
  • 2FA workflow documentation
  • Kill switch for unexpected orders
  • Monthly P&L automation on alpha-forge journal side