HyperTrade is a lightweight server that processes TradingView long/short alerts to execute orders on Hyperliquid, for automated algorithmic trading.
It validates webhook payloads, enforces secret auth and IP whitelisting, and emits audit logs. Use it as a reliable layer between TradingView strategies and your Hyperliquid sub-accounts.
- TradingView‑compatible payloads with validation.
- IP whitelisting.
- Payload secret.
- Environment secrets.
- Specify a different leverage per asset.
- Dry-run / demo mode (
HYPERTRADE_DRY_RUN=true): validate webhooks without trading. - Health check at
GET /health. - Simple config via env vars or
.env(no external dotenv dependency).
-
Define a Leverage Policy.
ALWAYS trade with a maximum leverage of 3x–5x in cross-margin mode to improve risk control. NEVER gamble with a 10x–20x leverage. -
Deep Defensive Capital.
Keep a portion of idle funds as defensive capital. This reserve extends the liquidation range and protects the position during periods of volatility. -
Never forget a Stop Loss.
Never be greedy. Always include a stop loss in your strategy, no matter what.
- Python 3.10+
- Pip or your preferred package manager
- A TradingView subscription.
- A Hyperliquid sub-account.
Using pip:
python -m venv .venv && source .venv/bin/activate
pip install --upgrade pip
pip install fastapi uvicorn[standard] pydantic pydantic-settings python-dotenv jsonschemaRun via Uvicorn directly:
uvicorn hypertrade.daemon:app --host 0.0.0.0 --port 6487Or via the module entrypoint, which serves the same app on the host/port from
HYPERTRADE_LISTEN_HOST / HYPERTRADE_LISTEN_PORT (default 0.0.0.0:6487):
python -m hypertradeOr use the bundled launchers, which load secrets from pass,
select the Hyperliquid endpoint, and run the guided setup on first use:
./hypertrade-test.sh # testnet, port 6488
./hypertrade-prod.sh # mainnet, port 6487Run an interactive wizard that collects the minimum config needed to start and stores it for you:
python -m hypertrade.setupIt prompts only for what is missing — environment (prod/test), master
address, API wallet private key (hidden), an optional sub-account, and one
authentication method (a webhook secret or the IP whitelist). Secrets are
saved to pass when it is installed;
otherwise the wizard recommends installing pass and falls back to writing a
.env file (mode 0600). The hypertrade-{prod,test}.{sh,fish} launchers run
this automatically when required configuration is not yet present.
Set HYPERTRADE_DRY_RUN=true to validate incoming webhooks without trading.
The request runs the full pipeline — content-type, JSON-schema, secret/auth,
signal parsing, leverage and reduce_only mapping — and returns a dry_run
response describing the order that would have been placed, while performing
no side effect: no Hyperliquid order, no database write, and no idempotency
reservation. On startup the daemon logs a clear
⚠️ DRY-RUN MODE ENABLED warning so a demo instance is never mistaken for a
live one.
export HYPERTRADE_DRY_RUN=true
uvicorn hypertrade.daemon:app --host 127.0.0.1 --port 6499A successful call returns the mapped order it would have sent:
{
"status": "dry_run",
"signal": "REDUCE_LONG",
"side": "sell",
"symbol": "SOL",
"action": "sell",
"contracts": "10.5",
"price": "183.81",
"leverage": 3,
"reduce_only": true,
"subaccount": "0xYourSubaccount",
"received_at": "2026-01-01T00:00:00+00:00"
}Dry-run does not relax authentication. The shared secret, the IP whitelist,
and the nonce requirement (while idempotency is enabled) are all still enforced
— that is the point: you exercise the real request path. When testing from
localhost, the default IP whitelist (TradingView's published IPs) rejects
127.0.0.1 with 403. For local testing, either disable the whitelist and
authenticate with the secret, or add your own address:
# Option A: disable the IP whitelist, authenticate with the shared secret
export HYPERTRADE_IP_WHITELIST_ENABLED=false
export HYPERTRADE_WEBHOOK_SECRET='testsecret'
# Option B: keep the whitelist but allow your own address
export 'HYPERTRADE_TV_WEBHOOK_IPS=["127.0.0.1"]'Then send a test order. The payload must carry general.secret matching
HYPERTRADE_WEBHOOK_SECRET, plus a unique general.nonce while idempotency is
enabled:
curl -s -X POST http://127.0.0.1:6499/webhook \
-H 'Content-Type: application/json' \
-d '{
"general": {"strategy":"Demo","ticker":"SOLUSD","interval":"60",
"time":"2025-10-21T06:00:00Z","timenow":"2025-10-21T06:00:45Z",
"secret":"testsecret","leverage":"3X","nonce":"demo-1"},
"currency": {"quote":"USD","base":"SOL"},
"order": {"action":"sell","contracts":"10.5","price":"183.81",
"id":"demo","comment":"","alert_message":""},
"market": {"position":"long","position_size":"10.5",
"previous_position":"long","previous_position_size":"21"}
}'Because idempotency is skipped in dry-run, re-sending the same nonce returns
dry_run again rather than duplicate — repeated test webhooks are never
deduplicated.
Hypertrade won't start unless these variables are set:
HYPERTRADE_ENVIRONMENT– The Hyperliquid environment to connect to. Must beprodortest.HYPERTRADE_MASTER_ADDR– Your master wallet address.HYPERTRADE_API_WALLET_PRIV– Your private key for API access.HYPERTRADE_SUBACCOUNT_ADDR– (Optional) Your Hyperliquid sub-account address.
The HYPERTRADE_ENVIRONMENT variable controls which Hyperliquid endpoint is used:
HYPERTRADE_ENVIRONMENT=prod→https://api.hyperliquid.xyz(mainnet)HYPERTRADE_ENVIRONMENT=test→https://api.hyperliquid-testnet.xyz(testnet)
Any other value will cause the application to fail at startup with a clear error message.
export HYPERTRADE_ENVIRONMENT=prod
export HYPERTRADE_MASTER_ADDR=0xYourMasterAddress
export HYPERTRADE_API_WALLET_PRIV='your-private-key'
export HYPERTRADE_SUBACCOUNT_ADDR=0xYourSubaccountAddress
uvicorn hypertrade.daemon:app --host 0.0.0.0 --port 6487Personally I prefer storing secrets with Password Store (pass) instead of a .env file:
- Project: https://www.passwordstore.org/
- Example exports pulling from pass:
export HYPERTRADE_ENVIRONMENT=prod
export HYPERTRADE_MASTER_ADDR="$(pass show hypertrade/master_addr)"
export HYPERTRADE_API_WALLET_PRIV="$(pass show hypertrade/api_wallet_priv)"
export HYPERTRADE_SUBACCOUNT_ADDR="$(pass show hypertrade/subaccount_addr)"GET /health– health checkPOST /webhook– TradingView webhook (supports IP whitelist)
Enable IP whitelisting for the TradingView webhook endpoint and set allowed IPs:
export HYPERTRADE_IP_WHITELIST_ENABLED=true
export 'HYPERTRADE_TV_WEBHOOK_IPS=["52.89.214.238","34.212.75.30","54.218.53.128","52.32.178.7"]'
# Enable ONLY when behind a trusted reverse proxy. Default is false.
export HYPERTRADE_TRUST_FORWARDED_FOR=true
⚠️ Security / breaking change:HYPERTRADE_TRUST_FORWARDED_FORnow defaults tofalse(previouslytrue).X-Forwarded-Foris client-supplied and therefore spoofable — a request could otherwise claim a whitelisted IP and bypass the whitelist. When enabled, only the right-mostX-Forwarded-Forentry (the address added by your immediate trusted proxy) is used; this assumes a single proxy hop. If you run behind a reverse proxy and rely on the IP whitelist, you must now setHYPERTRADE_TRUST_FORWARDED_FOR=trueexplicitly, or every request will be seen as coming from the proxy and rejected.
You can apply the whitelist dependency to other routes using require_ip_whitelisted() from hypertrade/security.py.
Note:
- For pydantic-settings v2, complex types like lists must be provided as JSON strings in env vars. Use a JSON list for
HYPERTRADE_TV_WEBHOOK_IPS(as shown above). Comma-separated values are not supported by the loader.
For an extra authentication layer, set a shared secret and include it in the payload under general.secret.
Env:
export HYPERTRADE_WEBHOOK_SECRET='your-shared-secret'The /history/* endpoints require authentication with the webhook secret:
curl -H "Authorization: Bearer $HYPERTRADE_WEBHOOK_SECRET" http://localhost:6487/history/statsRequests without a valid Authorization: Bearer <secret> get 401; if no
HYPERTRADE_WEBHOOK_SECRET is configured, /history returns 403.
Payload (TradingView template) with all the placeholders, including secret and leverage. Copy and paste it on as TradingView Alert.
Don't forget to set up first the strategy name, the "secret": "your-shared-secret" and the preferred leverage.
{
"general": {
"strategy" : "your-strategy-name",
"ticker": "{{ticker}}",
"interval": "{{interval}}",
"time": "{{time}}",
"timenow": "{{timenow}}",
"secret": "your-shared-secret",
"leverage": "3X"
},
"currency": {
"quote": "{{syminfo.currency}}",
"base": "{{syminfo.basecurrency}}"
},
"order": {
"action": "{{strategy.order.action}}",
"contracts": "{{strategy.order.contracts}}",
"price": "{{strategy.order.price}}",
"id": "{{strategy.order.id}}",
"comment": "{{strategy.order.comment}}",
"alert_message": "{{strategy.order.alert_message}}"
},
"market": {
"position": "{{strategy.market_position}}",
"position_size": "{{strategy.market_position_size}}",
"previous_position": "{{strategy.prev_market_position}}",
"previous_position_size": "{{strategy.prev_market_position_size}}"
}
}Validation:
- Incoming JSON is validated against a JSON Schema and then parsed into a Pydantic model.
- Schema enforces required sections and basic constraints (action enum, date-time fields, numeric fields).
Behavior:
- If
HYPERTRADE_WEBHOOK_SECRETis set, incoming requests must includegeneral.secretmatching it, or the request is rejected with 401. - If not set, the secret check is skipped.
📖 For field-by-field semantics, how the trading signal is derived from the position transition, and ready-to-use example payloads for every signal (
OPEN_LONG,CLOSE_LONG,ADD_LONG,REVERSE_TO_SHORT, …), seedocs/tradingview-webhook.md.
HYPERTRADE_MAX_PAYLOAD_BYTES(default65536): reject requests larger than this size with 413.HYPERTRADE_ENABLE_TRUSTED_HOSTS(defaultfalse): enable Trusted Host middleware.HYPERTRADE_TRUSTED_HOSTS(default*): allowed hosts when Trusted Host is enabled. Provide as a JSON list, e.g.'["example.com","api.example.com"]'(comma-separated values are not supported by the loader — see the note under IP Whitelisting).- Webhook requires
Content-Type: application/jsonand returns 415 otherwise. HYPERTRADE_IDEMPOTENCY_ENABLED(defaulttrue): require a uniquegeneral.nonceper order and place each at most once. Requires the order DB (the daemon refuses to start ifHYPERTRADE_DB_ENABLED=false).HYPERTRADE_IDEMPOTENCY_INFLIGHT_TIMEOUT(default60): seconds before an in-progress reservation is considered stale and reclaimable.
sequenceDiagram
autonumber
participant TV as TradingView
participant WH as Webhook (HTTP POST)
box HyperTrader Deaemon
participant HT as HyperTrade Service
participant RL as Risk Logic
participant OR as Order Executor
end
participant HL as Hyperliquid SDK/API
participant SA as HL Sub-Account
TV->>TV: Trading Strategy Logic
TV->>WH: POST /webhook (JSON payload)
WH->>HT: Forward payload (signal event)
HT->>RL: Validate signal (IP whitelist, secrets, etc.)
RL-->>HT: Approved / Rejected
alt Approved
HT->>OR: Build order {coin, side, size, leverage, reduceOnly, etc}
OR->>HL: exchange.order(...)
HL-->>OR: OrderAck {status,id,price,filledSz}
OR-->>HT: Execution result
HT-->>TV: (optional) 200 OK
HT->>SA: Position updated on fill
HT->>HT: Log (trades, PnL, metrics)
else Rejected
HT-->>TV: 200 OK (ignored by policy)
HT->>HT: Log rejection reason
end