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.comas 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¶
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.
3-2. Create the Tunnel¶
- Cloudflare dashboard → Zero Trust → Networks → Tunnels → Create a tunnel
- Tunnel type: Cloudflared
- Tunnel name:
alpha-strike-prod - Save tunnel — a token is issued
- On the VM, run the install command shown by Cloudflare:
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¶
- Cloudflare dashboard → select your domain → Security → Security rules
- In the Custom rules row click Create rule
- 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
passphrasecheck 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¶
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=1lets 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)¶
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¶
- Open the target chart (e.g.,
BINANCE:BTCUSDTfor testing, or your actual instrument) - Click the bell icon → Create Alert
- Condition: pick your strategy / indicator
- Notifications tab:
- ✅ Webhook URL with
https://strike.yourdomain.com/webhook - Message field — paste the JSON below (replace
<WEBHOOK_PASSPHRASE>with the actual.envvalue):
{
"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}}"
}
- 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 1evaluates 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) withasset_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.XRPetc. (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-strike → sudo sed -i "s\|^WEBHOOK_PASSPHRASE=.*\|WEBHOOK_PASSPHRASE=<new>\|" /etc/alpha-strike/.env → sudo systemctl restart alpha-strike |
| TradingView Message JSON | Edit the alert → update passphrase → Save → Restart |
Common pitfall: forgetting to update the VM
.envwill produce401 Unauthorizedon every alert..envis read only at systemd start, sorestartis 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_loggerJSONL for later pnl reconciliation against alpha-forge
10-2. Weekly (Sunday night)¶
- [ ] Review
scripts/run_apt_maintenance.shresults - [ ] 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-opend → systemctl 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:
/healthstill 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_orderslinger beyond 24 hourstotal_assetsdrops 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