Hyperliquid trading plugin with background position monitoring and custom automations. Execute market orders, limit orders, manage positions, view funding ra...
---
name: openbroker
description: Hyperliquid trading plugin with background position monitoring and custom automations. Execute market orders, limit orders, manage positions, view funding rates, run trading strategies, and write event-driven automation scripts with automatic alerts for PnL changes and liquidation risk.
license: MIT
compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
homepage: https://www.npmjs.com/package/openbroker-plugin
metadata: {"author": "monemetrics", "version": "1.2.0", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker-plugin", "bins": ["openbroker"], "label": "Install openbroker-plugin (npm)"}]}}
allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_outcomes ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_funding_scan ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_spot_buy ob_spot_sell ob_outcome_buy ob_outcome_sell ob_twap ob_twap_cancel ob_twap_status ob_bracket ob_chase ob_watcher_status ob_auto_run ob_auto_stop ob_auto_list Bash(openbroker:*)
---
# Open Broker - Hyperliquid Trading CLI
Execute trading operations on Hyperliquid DEX with builder fee support.
## Installation
```bash
npm install -g openbroker
```
## Quick Start
```bash
# 1. Setup (generates wallet, creates config, approves builder fee)
openbroker setup
# 2. Fund your wallet with USDC on Arbitrum, then deposit at https://app.hyperliquid.xyz/
# 3. Start trading
openbroker account
openbroker buy --coin ETH --size 0.1
```
## Important: Finding Assets Before Trading
**Always search before trading an unfamiliar asset.** Hyperliquid has main perps (ETH, BTC, SOL...), HIP-3 perps (xyz:CL, xyz:GOLD, km:USOIL...), spot markets, and HIP-4 outcome markets. Use search to discover the correct ticker:
```bash
openbroker search --query GOLD # Find all GOLD markets across all providers
openbroker search --query oil # Find oil-related assets (CL, BRENTOIL, USOIL...)
openbroker search --query BTC --type perp # BTC perps only
openbroker search --query NATGAS --type hip3 # HIP-3 only
openbroker search --query BTC --type outcome # HIP-4 only
openbroker outcomes --query BTC # Outcome-specific details
```
Or with the `ob_search` plugin tool: `{ "query": "gold" }` or `{ "query": "oil", "type": "hip3" }`
**HIP-3 assets use `dex:COIN` format** — e.g., `xyz:CL` not just `CL`. If you get an error like "No market data found", search for the asset to find the correct prefixed ticker. Common HIP-3 dexes: `xyz`, `flx`, `km`, `hyna`, `vntl`, `cash`.
### Asset IDs (disambiguation)
Every info JSON output includes an `assetId` field — the canonical Hyperliquid asset index. Prefer it over the coin name when persisting references, because the same ticker can exist on multiple providers (e.g. `HYPE` perp, `hyna:HYPE` HIP-3, and `HYPE/USDC` spot all coexist).
| Scope | Formula | Example |
|-------|---------|---------|
| Main perps | universe index | `HYPE` → `159` |
| HIP-3 perps | `100000 + dexIdx * 10000 + assetIdx` | `hyna:HYPE` → `140002` |
| Spot | `10000 + pair.index` | `HYPE/USDC` → `10107` |
| HIP-4 outcomes | `100000000 + (10 * outcomeId + side)` | outcome `123` YES → `100001230` |
```bash
openbroker search HYPE --json | jq '.[] | {coin, assetId, type, provider}'
```
Trading commands still take `--coin <name>` for perps/spot (including HIP-3 `dex:COIN`) — `assetId` is for queries, comparisons, and agent state, not order placement. HIP-4 outcome commands take `--outcome <id|#encoding|+encoding>` plus `--outcome-side yes|no` when using a plain id.
## Troubleshooting: CLI Fallback
If an `ob_*` plugin tool returns unexpected errors, empty results, or crashes, **fall back to the equivalent CLI command** via Bash. The CLI and plugin tools share the same core code, but the CLI has more mature error handling and output.
**Every info command supports `--json`** for structured output. The table below covers the commands with dedicated plugin tools; any other info command (e.g. `spot`, `trades`, `fees`, `order-status`, `rate-limit`, `funding-history`, `all-markets`) can be run as `openbroker <command> --json` for the same effect.
| Plugin Tool | CLI Equivalent |
|-------------|---------------|
| `ob_account` | `openbroker account --json` |
| `ob_positions` | `openbroker positions --json` |
| `ob_funding` | `openbroker funding --json --include-hip3` |
| `ob_markets` | `openbroker markets --json --include-hip3` |
| `ob_search` | `openbroker search --query <QUERY> --json` |
| `ob_spot` | `openbroker spot --json` (or `--balances --json`) |
| `ob_outcomes` | `openbroker outcomes --json` (or `--query <QUERY> --json`) |
| `ob_fills` | `openbroker fills --json` |
| `ob_orders` | `openbroker orders --json` |
| `ob_order_status` | `openbroker order-status --oid <OID> --json` |
| `ob_fees` | `openbroker fees --json` |
| `ob_candles` | `openbroker candles --coin <COIN> --json` |
| `ob_funding_history` | `openbroker funding-history --coin <COIN> --json` |
| `ob_trades` | `openbroker trades --coin <COIN> --json` |
| `ob_rate_limit` | `openbroker rate-limit --json` |
| `ob_funding_scan` | `openbroker funding-scan --json` |
| `ob_buy` | `openbroker buy --coin <COIN> --size <SIZE>` |
| `ob_sell` | `openbroker sell --coin <COIN> --size <SIZE>` |
| `ob_limit` | `openbroker limit --coin <COIN> --side <SIDE> --size <SIZE> --price <PRICE>` |
| `ob_tpsl` | `openbroker tpsl --coin <COIN> --tp <PRICE> --sl <PRICE>` |
| `ob_cancel` | `openbroker cancel --all` or `--coin <COIN>` |
| `ob_outcome_buy` | `openbroker outcome-buy --outcome <ID> --outcome-side yes --size <SIZE>` |
| `ob_outcome_sell` | `openbroker outcome-sell --outcome <ID> --outcome-side yes --size <SIZE>` |
| `ob_auto_run` | `openbroker auto run <script> [--dry]` |
| `ob_auto_stop` | `openbroker auto stop <id>` (or SIGINT if run in foreground) |
| `ob_auto_list` | `openbroker auto list` |
**When to use CLI fallback:**
- Plugin tool returns `null`, empty data, or throws an error
- You need data the plugin tool doesn't expose (e.g., `--verbose` debug output)
- Long-running operations (strategies) — the CLI handles timeouts and progress better
Add `--dry` to any trading CLI command to preview without executing. Add `--json` to info commands for structured output.
## Command Reference
### Setup
```bash
openbroker setup # One-command setup (wallet + config + builder approval)
openbroker approve-builder --check # Check builder fee status (for troubleshooting)
```
The `setup` command offers three modes:
1. **Generate fresh wallet** (recommended for agents) — creates a dedicated trading wallet with builder fee auto-approved. No browser steps needed — just fund with USDC and start trading.
2. **Import existing key** — use a private key you already have
3. **Generate API wallet** — creates a restricted wallet that can trade but cannot withdraw. Requires browser approval from a master wallet.
For options 1 and 2, setup saves config and approves the builder fee automatically.
For option 3 (API wallet), see the API Wallet Setup section below.
### Fresh Wallet Setup (Recommended for Agents)
The simplest setup for agents. A fresh wallet is generated, the builder fee is auto-approved, and the agent is ready to trade immediately after funding.
**Flow:**
1. Run `openbroker setup` and choose option 1 ("Generate a fresh wallet")
2. The CLI generates a wallet, saves the config, and approves the builder fee automatically
3. Fund the wallet by sending USDC from your Hyperliquid account to the agent's wallet address using the **Send** feature on https://app.hyperliquid.xyz/. **Funding should be done on Hyperliquid L1 only.**
4. Start trading
### API Wallet Setup (Alternative)
API wallets can place trades on behalf of a master account but **cannot withdraw funds**. Use this if you prefer to keep funds in your existing wallet and only delegate trading access.
**Flow:**
1. Run `openbroker setup` and choose option 3 ("Generate API wallet")
2. The CLI generates a keypair and prints an approval URL (e.g. `https://openbroker.dev/approve?agent=0xABC...`)
3. The agent owner opens the URL in a browser and connects their master wallet (MetaMask etc.)
4. The master wallet signs two transactions: `ApproveAgent` (authorizes the API wallet) and `ApproveBuilderFee` (approves the 1 bps fee)
5. The CLI detects the approval automatically and saves the config
**After setup, the config will contain:**
```
HYPERLIQUID_PRIVATE_KEY=0x... # API wallet private key
HYPERLIQUID_ACCOUNT_ADDRESS=0x... # Master account address
HYPERLIQUID_NETWORK=mainnet
```
**Important for agents:** When using an API wallet, pass the approval URL to the agent owner (the human who controls the master wallet). The owner must approve in a browser before the agent can trade. The CLI waits up to 10 minutes for the approval. If it times out, re-run `openbroker setup`.
### Account Info
```bash
openbroker account # Balance, equity, margin
openbroker account --orders # Include open orders
openbroker account --address 0xabc... # Look up another account
openbroker positions # Open positions with PnL
openbroker positions --coin ETH # Specific coin
openbroker positions --address 0xabc... # Another account's positions
```
### Funding Rates
```bash
openbroker funding --top 20 # Top 20 by funding rate
openbroker funding --coin ETH # Specific coin
openbroker funding --top 20 --json # JSON (includes assetId)
```
### Markets
```bash
openbroker markets --top 30 # Top 30 main perps
openbroker markets --coin BTC # Specific coin
openbroker markets --coin BTC --json # JSON (includes assetId)
```
### All Markets (Perps + Spot + HIP-3 + HIP-4)
```bash
openbroker all-markets # Show all markets
openbroker all-markets --type perp # Main perps only
openbroker all-markets --type hip3 # HIP-3 perps only
openbroker all-markets --type spot # Spot markets only
openbroker all-markets --type outcome # HIP-4 outcome markets only
openbroker all-markets --top 20 # Top 20 by volume
openbroker all-markets --json # JSON (includes assetId)
```
### Search Markets (Find assets across providers)
```bash
openbroker search --query GOLD # Find all GOLD markets
openbroker search --query BTC # Find BTC across all providers
openbroker search --query ETH --type perp # ETH perps only
openbroker search --query BTC --type outcome # HIP-4 outcome markets only
openbroker search HYPE --json # JSON with assetId per result
```
### Spot Markets
```bash
openbroker spot # Show all spot markets
openbroker spot --coin PURR # Show PURR market info
openbroker spot --balances # Show your spot balances
openbroker spot --balances --address 0xabc... # Another account's spot balances
openbroker spot --top 20 # Top 20 by volume
openbroker spot --json # JSON (includes assetId, base, quote)
```
### Trade Fills
```bash
openbroker fills # Recent fills
openbroker fills --coin ETH # ETH fills only
openbroker fills --coin BTC --side buy --top 50
openbroker fills --address 0xabc... # Another account's fills
```
### Order History
```bash
openbroker orders # Recent orders (all statuses)
openbroker orders --open # Currently open orders only
openbroker orders --open --coin ETH # Open orders for a specific coin
openbroker orders --coin ETH --status filled
openbroker orders --top 50
openbroker orders --address 0xabc... --open # Another account's open orders
```
### Order Status
```bash
openbroker order-status --oid 123456789 # Check specific order
openbroker order-status --oid 0x1234... # By client order ID
openbroker order-status --oid 123456789 --address 0xabc... # On another account
openbroker order-status --oid 123456789 --json
```
### Fee Schedule
```bash
openbroker fees # Fee tier, rates, and volume
openbroker fees --address 0xabc... # Another account's fees
openbroker fees --json
```
### Candle Data (OHLCV)
```bash
openbroker candles --coin ETH # 24 hourly candles
openbroker candles --coin BTC --interval 4h --bars 48 # 48 four-hour bars
openbroker candles --coin SOL --interval 1d --bars 30 # 30 daily bars
openbroker candles --coin ETH --json # JSON (coin, assetId, interval, candles)
```
### Funding History
```bash
openbroker funding-history --coin ETH # Last 24h
openbroker funding-history --coin BTC --hours 168 # Last 7 days
openbroker funding-history --coin ETH --json # JSON (coin, assetId, history)
```
### Recent Trades (Tape)
```bash
openbroker trades --coin ETH # Last 30 trades
openbroker trades --coin BTC --top 50 # Last 50 trades
openbroker trades --coin ETH --json # JSON (coin, assetId, trades)
```
### Rate Limit
```bash
openbroker rate-limit # API usage and capacity
openbroker rate-limit --json
```
### Funding Rate Scanner (Cross-Dex)
```bash
openbroker funding-scan # Scan all dexes, >25% threshold
openbroker funding-scan --threshold 50 --pairs # Show opposing funding pairs
openbroker funding-scan --hip3-only --top 20 # HIP-3 only
openbroker funding-scan --watch --interval 120 # Re-scan every 2 minutes
openbroker funding-scan --json # JSON (includes assetId per result)
```
## Trading Commands
### HIP-3 Perp Trading
All trading commands support HIP-3 assets using `dex:COIN` syntax:
```bash
openbroker buy --coin xyz:CL --size 1 # Buy crude oil on xyz dex
openbroker sell --coin xyz:BRENTOIL --size 1 # Sell brent oil
openbroker limit --coin xyz:GOLD --side buy --size 0.1 --price 2500
```
### Market Orders (Quick)
```bash
openbroker buy --coin ETH --size 0.1
openbroker sell --coin BTC --size 0.01
openbroker buy --coin SOL --size 5 --slippage 100 # Custom slippage (bps)
```
### Market Orders (Full)
```bash
openbroker market --coin ETH --side buy --size 0.1
openbroker market --coin BTC --side sell --size 0.01 --slippage 100
```
### Limit Orders
```bash
openbroker limit --coin ETH --side buy --size 1 --price 3000
openbroker limit --coin SOL --side sell --size 10 --price 200 --tif ALO
```
### Set TP/SL on Existing Position
```bash
# Set take profit at $40, stop loss at $30
openbroker tpsl --coin HYPE --tp 40 --sl 30
# Set TP at +10% from entry, SL at entry (breakeven)
openbroker tpsl --coin HYPE --tp +10% --sl entry
# Set only stop loss at -5% from entry
openbroker tpsl --coin ETH --sl -5%
# Partial position TP/SL
openbroker tpsl --coin ETH --tp 4000 --sl 3500 --size 0.5
```
### Trigger Orders (Standalone TP/SL)
```bash
# Take profit: sell when price rises to $40
openbroker trigger --coin HYPE --side sell --size 0.5 --trigger 40 --type tp
# Stop loss: sell when price drops to $30
openbroker trigger --coin HYPE --side sell --size 0.5 --trigger 30 --type sl
```
### Cancel Orders
```bash
openbroker cancel --all # Cancel all orders
openbroker cancel --coin ETH # Cancel ETH orders only
openbroker cancel --oid 123456 # Cancel specific order
```
### Spot Orders
Spot trading uses a separate order path with its own asset indices (see Asset IDs section). Pass the base token symbol as `--coin` — quote is always USDC.
```bash
# Market orders (shortcuts)
openbroker spot-buy --coin PURR --size 1000
openbroker spot-sell --coin HYPE --size 5
# Full form (specify --side)
openbroker spot-order --coin PURR --side buy --size 1000
# Limit orders (add --price)
openbroker spot-order --coin HYPE --side sell --size 50 --price 25.50
openbroker spot-order --coin PURR --side buy --size 500 --price 0.20 --tif Alo
# Preview without executing
openbroker spot-buy --coin PURR --size 500 --dry
```
**Spot flags:** `--coin`, `--side`, `--size`, `--price` (omit → market order), `--tif` (`Gtc`/`Ioc`/`Alo`, default `Gtc`), `--slippage` (bps, market orders only), `--dry`, `--verbose`.
### HIP-4 Outcome Trading
Outcome markets are prediction-style YES/NO tokens. The outcome spot coin is `#<encoding>`, the token name is `+<encoding>`, and `encoding = 10 * outcomeId + side` where side `0` is usually YES and side `1` is usually NO.
```bash
openbroker outcomes --query BTC
openbroker outcomes --outcome 123 --side yes --json
openbroker outcomes --balances
openbroker outcome-buy --outcome 123 --outcome-side yes --size 10 --dry
openbroker outcome-buy --outcome 123 --outcome-side no --size 5 --price 0.42
openbroker outcome-sell --outcome #1230 --size 10
openbroker outcome-order --outcome 123 --outcome-side yes --side buy --size 10
```
**Outcome flags:** `--outcome`, `--outcome-side`, `--side`, `--size`, `--price` (omit → market IOC), `--tif` (`Gtc`/`Ioc`/`Alo`, default `Gtc`), `--slippage`, `--sz-decimals`, `--dry`, `--verbose`.
## Advanced Execution
### TWAP (Native Exchange Order)
```bash
# Buy 1 ETH over 30 minutes (exchange handles slicing)
openbroker twap --coin ETH --side buy --size 1 --duration 30
# Sell 0.5 BTC over 2 hours without randomized timing
openbroker twap --coin BTC --side sell --size 0.5 --duration 120 --randomize false
# Reduce-only (close position with TWAP). Note: TWAP uses `--reduce-only`, not `--reduce`
openbroker twap --coin ETH --side sell --size 1 --duration 30 --reduce-only
# Cancel a running TWAP
openbroker twap-cancel --coin ETH --twap-id 77738308
# Check TWAP status
openbroker twap-status --active
```
### Scale In/Out (Grid Orders)
```bash
# Place 5 buy orders ranging 2% below current price
openbroker scale --coin ETH --side buy --size 1 --levels 5 --range 2
# Scale out with exponential distribution
openbroker scale --coin BTC --side sell --size 0.5 --levels 4 --range 3 --distribution exponential --reduce
```
### Bracket Order (Entry + TP + SL)
```bash
# Long ETH with 3% take profit and 1.5% stop loss
openbroker bracket --coin ETH --side buy --size 0.5 --tp 3 --sl 1.5
# Short with limit entry
openbroker bracket --coin BTC --side sell --size 0.1 --entry limit --price 100000 --tp 5 --sl 2
```
### Chase Order (Follow Price)
```bash
# Chase buy with ALO orders until filled
openbroker chase --coin ETH --side buy --size 0.5 --timeout 300
# Aggressive chase with tight offset
openbroker chase --coin SOL --side buy --size 10 --offset 2 --timeout 60
```
## Order Types
### Limit Orders vs Trigger Orders
**Limit Orders** (`openbroker limit`):
- Execute immediately if price is met
- Rest on the order book until filled or cancelled
- A limit sell BELOW current price fills immediately (taker)
- NOT suitable for stop losses
**Trigger Orders** (`openbroker trigger`, `openbroker tpsl`):
- Stay dormant until trigger price is reached
- Only activate when price hits the trigger level
- Proper way to set stop losses and take profits
- Won't fill prematurely
### When to Use Each
| Scenario | Command |
|----------|---------|
| Buy at specific price below market | `openbroker limit` |
| Sell at specific price above market | `openbroker limit` |
| Stop loss (exit if price drops) | `openbroker trigger --type sl` |
| Take profit (exit at target) | `openbroker trigger --type tp` |
| Add TP/SL to existing position | `openbroker tpsl` |
## Common Arguments
All commands support `--dry` for dry run (preview without executing).
| Argument | Description |
|----------|-------------|
| `--coin` | Asset symbol (ETH, BTC, SOL, HYPE, etc.) |
| `--side` | Order direction: `buy` or `sell` |
| `--size` | Order size in base asset |
| `--price` | Limit price |
| `--dry` | Preview without executing |
| `--help` | Show command help |
### Order Arguments
| Argument | Description |
|----------|-------------|
| `--trigger` | Trigger price (for trigger orders) |
| `--type` | Trigger type: `tp` or `sl` |
| `--slippage` | Slippage tolerance in bps (for market orders) |
| `--tif` | Time in force: GTC, IOC, ALO |
| `--reduce` | Reduce-only order |
### TP/SL Price Formats
| Format | Example | Description |
|--------|---------|-------------|
| Absolute | `--tp 40` | Price of $40 |
| Percentage up | `--tp +10%` | 10% above entry |
| Percentage down | `--sl -5%` | 5% below entry |
| Entry price | `--sl entry` | Breakeven stop |
## Configuration
Config is loaded from (in priority order):
1. Environment variables
2. `.env` in current directory
3. `~/.openbroker/.env` (global config)
Run `openbroker setup` to create the global config interactively.
| Variable | Required | Description |
|----------|----------|-------------|
| `HYPERLIQUID_PRIVATE_KEY` | Yes | Wallet private key (0x...) |
| `HYPERLIQUID_NETWORK` | No | `mainnet` (default) or `testnet` |
| `HYPERLIQUID_ACCOUNT_ADDRESS` | No | Master account address (required for API wallets) |
| `OB_DASHBOARD_URL` | No | Dashboard API URL for forwarding audit notes, metrics, and agent actions (e.g. `http://localhost:3001`) |
The builder fee (1 bps / 0.01%) is hardcoded and not configurable.
## OpenClaw Plugin (Optional)
This skill works standalone via Bash — every command above runs through the `openbroker` CLI. For enhanced features, the same `openbroker` npm package also ships as an **OpenClaw plugin** that you can enable alongside this skill.
### What the plugin adds
- **Structured agent tools** (`ob_account`, `ob_buy`, `ob_limit`, etc.) — typed tool calls with proper input schemas instead of Bash strings. The agent gets structured JSON responses.
- **Background position watcher** — polls your Hyperliquid account every 30s and sends webhook alerts when positions open/close, PnL moves significantly, or margin usage gets dangerous.
- **Automation tools** (`ob_auto_run`, `ob_auto_stop`, `ob_auto_list`) — start, stop, and manage custom trading automations from within the agent.
- **CLI commands** — `openclaw ob status` and `openclaw ob watch` for inspecting the watcher.
### Enable the plugin
The plugin is bundled in the same `openbroker` npm package. To enable it in your OpenClaw config:
```yaml
plugins:
entries:
openbroker:
enabled: true
config:
hooksToken: "your-hooks-secret" # Required for watcher alerts
watcher:
enabled: true
pollIntervalMs: 30000
pnlChangeThresholdPct: 5
marginUsageWarningPct: 80
```
The plugin reads wallet credentials from `~/.openbroker/.env` (set up by `openbroker setup`), so you don't need to duplicate `privateKey` in the plugin config unless you want to override.
### Webhook setup for watcher alerts
For the position watcher and automations to send alerts to the agent, you must enable webhooks in your OpenClaw gateway config and add a hook mapping. This is a manual configuration step — plugins cannot auto-configure gateway settings.
**1. Generate a hook token** — any secure random string:
```bash
openssl rand -hex 32
```
**2. Enable hooks and add a mapping** in your `openclaw.json` (or `openclaw.yaml`) deployment config:
```json
"hooks": {
"enabled": true,
"path": "/hooks",
"token": "<your-generated-token>",
"allowedAgentIds": ["hooks", "main", "openbroker"],
"mappings": [
{
"id": "main",
"match": {
"path": "openbroker"
},
"action": "agent",
"wakeMode": "now",
"name": "Openbroker",
"agentId": "main",
"deliver": true,
"channel": "last",
"model": "anthropic/claude-sonnet-4-6"
}
]
}
```
| Field | Description |
|-------|-------------|
| `token` | Shared secret — must match `hooksToken` in the plugin config |
| `allowedAgentIds` | Agent IDs allowed to receive webhook requests |
| `mappings[].match.path` | Matches the webhook path sent by the plugin (always `"openbroker"`) |
| `mappings[].wakeMode` | `"now"` triggers an immediate agent turn. `"next-heartbeat"` queues for the next scheduled heartbeat |
| `mappings[].deliver` | If `true`, the agent's response is delivered to the user via the configured channel |
| `mappings[].channel` | Delivery channel: `"last"` (most recent), `"slack"`, `"telegram"`, `"discord"`, `"whatsapp"`, etc. |
| `mappings[].model` | Model override for webhook-triggered turns. Optional — uses deployment default if omitted |
**3. Set the same token in your plugin config:**
```yaml
plugins:
entries:
openbroker:
enabled: true
config:
hooksToken: "<your-generated-token>" # Same token as hooks.token
watcher:
enabled: true
```
**4. Restart the gateway** and verify:
```bash
openclaw ob status
```
The watcher sends alerts to `POST /hooks/agent` with `Authorization: Bearer <token>`. The gateway matches the request against the mapping and triggers an agent turn. Without hooks enabled, the watcher still tracks state (accessible via `ob_watcher_status`), but it can't wake the agent.
### Using with or without the plugin
- **Skill only (no plugin):** Use Bash commands (`openbroker buy --coin ETH --size 0.1`). No background monitoring.
- **Skill + plugin:** The agent prefers the `ob_*` tools when available (structured data), falls back to Bash for commands not covered by tools (strategies, scale). Background watcher sends alerts automatically.
## Trading Automations
Automations let you write custom event-driven trading logic as TypeScript scripts. Write exactly the logic you need and OpenBroker handles the polling, event detection, and SDK access.
### IMPORTANT: Bundled Examples Are References, Not Ready-Made Strategies
OpenBroker ships bundled automation examples (`dca`, `grid`, `funding-arb`, `mm-spread`, `mm-maker`) that demonstrate common patterns. **These examples are meant to be read and learned from — not run directly as production strategies.** When the user asks for a strategy (e.g. "run a DCA on HYPE"), you should:
1. Read the relevant bundled example source code to understand the automation API patterns
2. Write a **custom automation** tailored to the user's specific requirements (coin, size, thresholds, risk parameters, TP/SL logic, etc.)
3. Save it to `~/.openbroker/automations/` and run it with `openbroker auto run`
Do NOT simply run `openbroker auto run --example dca --set coin=HYPE`. The examples use generic defaults and lack the nuanced risk management, position sizing, and TP/SL logic that a real strategy needs. Always write a purpose-built automation.
To view bundled examples and their config schemas:
```bash
openbroker auto examples # List examples with config fields
```
Available examples: `dca`, `grid`, `funding-arb`, `mm-spread`, `mm-maker`, `price-alert`
### How Automations Work
An automation is a `.ts` file that exports a default function. The function receives an `AutomationAPI` with the full Hyperliquid client, typed event subscriptions, persistent state, and a logger. The runtime connects a WebSocket for real-time price and order events, with REST polling every 30s as a heartbeat for position/margin data. Use `--no-ws` to disable WebSocket and fall back to pure REST polling (every 10s).
### Writing an Automation
Create a `.ts` file in `~/.openbroker/automations/` (or any path):
```typescript
// ~/.openbroker/automations/funding-scalp.ts
export default function(api) {
const COIN = 'ETH';
api.on('funding_update', async ({ coin, annualized }) => {
if (coin !== COIN) return;
if (annualized > 0.5 && !api.state.get('isShort')) {
api.log.info('High positive funding — going short');
await api.client.marketOrder(COIN, false, 0.1);
api.state.set('isShort', true);
} else if (annualized < -0.1 && api.state.get('isShort')) {
api.log.info('Funding normalized — closing short');
await api.client.marketOrder(COIN, true, 0.1);
api.state.set('isShort', false);
}
});
api.onStop(async () => {
if (api.state.get('isShort')) {
api.log.warn('Closing short on shutdown');
await api.client.marketOrder('ETH', true, 0.1);
api.state.set('isShort', false);
}
});
}
```
### AutomationAPI Reference
| Property / Method | Description |
|-------------------|-------------|
| `api.client` | Full HyperliquidClient — `marketOrder()`, `limitOrder()`, `triggerOrder()`, `cancelAll()`, `getUserStateAll()`, `getAllMids()`, `updateLeverage()`, and 35+ more methods |
| `api.on(event, handler)` | Subscribe to a market/account event (see Events below) |
| `api.every(ms, handler)` | Run a handler on a recurring interval (aligned to poll loop) |
| `api.onStart(handler)` | Called after all handlers are registered, before first poll |
| `api.onStop(handler)` | Called on shutdown (SIGINT). Use for cleanup — close positions, cancel orders |
| `api.onError(handler)` | Called when a handler throws. Error is already logged — use for recovery logic |
| `api.state.get(key)` | Get a persisted value (survives restarts, stored in `~/.openbroker/state/`) |
| `api.state.set(key, value)` | Set a persisted value |
| `api.state.delete(key)` | Delete a persisted value |
| `api.state.clear()` | Clear all state |
| `api.publish(message, options?)` | Send a message to the OpenClaw agent via webhook. Triggers an agent turn — the agent receives the message and can notify the user, take action, etc. Returns `true` if delivered. Options: `{ name?, wakeMode?, deliver?, channel? }` |
| `api.log.info/warn/error/debug(msg)` | Structured logger |
| `api.audit.record(kind, payload?)` | Add a custom audit note to the local SQLite trail for later reporting |
| `api.audit.metric(name, value, tags?)` | Add a numeric metric to the local SQLite trail |
| `api.utils` | `roundPrice`, `roundSize`, `sleep`, `normalizeCoin`, `formatUsd`, `annualizeFundingRate` |
| `api.id` | Automation ID (filename or `--id` flag) |
| `api.dryRun` | `true` if running with `--dry` (write methods are intercepted) |
Automations now write a local audit trail automatically to `~/.openbroker/automation-audit.sqlite`. The runtime records run config, logs, state changes, write actions, order updates, fills, user events, and per-poll account snapshots so you can generate performance reports later.
**Dashboard Forwarding:** When `OB_DASHBOARD_URL` is set (e.g. `http://localhost:3001`), audit notes, metrics, and trade actions are automatically forwarded to the OpenBroker Vaults dashboard API in real time. The vault address is read from `HYPERSTABLE_VAULT_ADDRESS` or `VAULT`. Forwarding is fire-and-forget — it never blocks the automation or causes errors if the dashboard is unreachable.
### Events
| Event | Payload | When |
|-------|---------|------|
| `tick` | `{ timestamp, pollCount }` | Every poll cycle (default: 10s) |
| `price_change` | `{ coin, oldPrice, newPrice, changePct }` | Mid price moved > 0.01% between polls |
| `funding_update` | `{ coin, fundingRate, annualized, premium }` | Every poll for all assets |
| `position_opened` | `{ coin, side, size, entryPrice }` | New position detected |
| `position_closed` | `{ coin, previousSize, entryPrice }` | Position no longer present |
| `position_changed` | `{ coin, oldSize, newSize, entryPrice }` | Position size changed |
| `pnl_threshold` | `{ coin, unrealizedPnl, changePct, positionValue }` | PnL moved > 5% of position value |
| `margin_warning` | `{ marginUsedPct, equity, marginUsed }` | Margin usage > 80% |
### Event Details — Choosing the Right Event
#### `tick` — The universal heartbeat
Fires **every single poll cycle** (default: 10s) regardless of market conditions. Use this when you need to check something on every poll — absolute price thresholds, custom conditions, periodic account checks. This is the most reliable event because it always fires.
**Payload:** `{ timestamp: number, pollCount: number }`
**When to use:**
- Checking if a price is above/below an absolute threshold (e.g. "alert me when ETH < $3000")
- Custom conditions that don't fit other events (e.g. "if I have no positions and funding is high, enter")
- Periodic tasks that need to run every poll (though `api.every()` is better for longer intervals)
**Example — absolute price alert:**
```typescript
api.on('tick', async () => {
const mids = await api.client.getAllMids();
const price = parseFloat(mids['HYPE']);
if (price < 38 && !api.state.get('alerted')) {
api.state.set('alerted', true);
await api.publish(`HYPE dropped below $38 — now at $${price.toFixed(3)}`);
}
});
```
**Note:** `tick` does not include price data in its payload — you must fetch it yourself via `api.client.getAllMids()`. This is because tick fires before any other event processing. If you only care about price movements, use `price_change` instead.
#### `price_change` — Relative price movements
Fires when a coin's mid price moves **≥ 0.01%** compared to the previous poll. This filters out rounding noise while catching virtually any real price movement. The comparison is between consecutive polls (not from a fixed baseline), so it detects incremental changes.
**Payload:** `{ coin: string, oldPrice: number, newPrice: number, changePct: number }`
**When to use:**
- Reacting to price movements (breakouts, momentum, mean reversion)
- Monitoring specific coins for volatility
- Building price-triggered entry/exit logic
**When NOT to use:**
- Checking if price is above/below a fixed threshold — use `tick` instead, because `price_change` only fires on relative movement between polls. During slow drifts (e.g. price slowly declining $0.001/s), the change between any two 10s polls may be < 0.01%, so the event won't fire even though the price has crossed your threshold.
**Example — momentum detector:**
```typescript
api.on('price_change', async ({ coin, changePct, newPrice }) => {
if (coin !== 'ETH') return;
if (changePct > 0.5) {
api.log.info(`ETH surging +${changePct.toFixed(2)}% — price $${newPrice}`);
// Enter long on strong upward momentum
}
});
```
#### `funding_update` — Funding rate data
Fires **every poll** for **every asset** that has funding rate data. This is high-frequency — if there are 150 perp assets, this fires 150 times per poll. Filter by coin in your handler.
**Payload:** `{ coin: string, fundingRate: number, annualized: number, premium: number }`
- `fundingRate` — the raw hourly funding rate (e.g. 0.0001 = 0.01%/hr)
- `annualized` — annualized rate (fundingRate × 8760 × 100, as a percentage)
- `premium` — the premium component
**When to use:**
- Funding rate arbitrage strategies
- Monitoring for extreme funding (entry/exit signals)
- Scanning for highest/lowest funding across all assets
**Example — funding scalp:**
```typescript
api.on('funding_update', async ({ coin, annualized }) => {
if (coin !== 'ETH') return;
if (annualized > 50 && !api.state.get('isShort')) {
api.log.info(`ETH funding at ${annualized.toFixed(1)}% annualized — shorting`);
await api.client.marketOrder('ETH', false, 0.1);
api.state.set('isShort', true);
}
});
```
#### `position_opened` — New position detected
Fires when a position appears that wasn't present in the previous poll. Useful for tracking entries made by other systems or confirming your own orders filled.
**Payload:** `{ coin: string, side: 'long' | 'short', size: number, entryPrice: number }`
**When to use:**
- Setting TP/SL on new positions automatically
- Logging/alerting when positions are opened (by you or another system)
- Starting position-specific monitoring
**Example — auto TP/SL on new positions:**
```typescript
api.on('position_opened', async ({ coin, side, size, entryPrice }) => {
const tpPrice = side === 'long' ? entryPrice * 1.05 : entryPrice * 0.95;
const slPrice = side === 'long' ? entryPrice * 0.97 : entryPrice * 1.03;
await api.client.takeProfit(coin, side !== 'long', size, tpPrice);
await api.client.stopLoss(coin, side !== 'long', size, slPrice);
api.log.info(`Set TP at ${tpPrice} / SL at ${slPrice} for ${coin}`);
});
```
#### `position_closed` — Position gone
Fires when a position that existed in the previous poll is no longer present. The position was either closed by you, liquidated, or filled by TP/SL.
**Payload:** `{ coin: string, previousSize: number, entryPrice: number }`
**When to use:**
- Logging/alerting when positions close
- Cleaning up related orders or state
- Re-entry logic after a position closes
**Example:**
```typescript
api.on('position_closed', async ({ coin, previousSize, entryPrice }) => {
api.log.info(`${coin} position closed (was ${previousSize} @ ${entryPrice})`);
api.state.delete(`${coin}_tp`);
await api.publish(`Position closed: ${coin} (entry: $${entryPrice})`);
});
```
#### `position_changed` — Size or direction changed
Fires when an existing position's size changes (partial close, add to position, or flip direction). Does NOT fire when a new position opens or an existing one fully closes — use `position_opened` and `position_closed` for those.
**Payload:** `{ coin: string, oldSize: number, newSize: number, entryPrice: number }`
- `oldSize`/`newSize` are signed: positive = long, negative = short
**When to use:**
- Detecting partial closes or position scaling
- Adjusting TP/SL when position size changes
- Tracking DCA entries
**Example:**
```typescript
api.on('position_changed', async ({ coin, oldSize, newSize }) => {
if (Math.abs(newSize) > Math.abs(oldSize)) {
api.log.info(`${coin} position increased: ${oldSize} → ${newSize}`);
} else {
api.log.info(`${coin} position reduced: ${oldSize} → ${newSize}`);
}
});
```
#### `pnl_threshold` — Significant PnL movement
Fires when unrealized PnL changes by **≥ 5% of position value** between consecutive polls. This is a large move detector — useful for risk management alerts rather than routine monitoring.
**Payload:** `{ coin: string, unrealizedPnl: number, changePct: number, positionValue: number }`
- `changePct` — the PnL change as a percentage of total position value (not % of PnL itself)
**When to use:**
- Risk alerts for large PnL swings
- Auto-close or reduce positions on sudden adverse moves
- Escalating alerts to the user via `api.publish()`
**Example:**
```typescript
api.on('pnl_threshold', async ({ coin, unrealizedPnl, changePct }) => {
if (unrealizedPnl < 0) {
await api.publish(
`⚠️ ${coin} PnL dropped sharply: $${unrealizedPnl.toFixed(2)} (${changePct.toFixed(1)}% of position)`,
{ name: 'pnl-alert' },
);
}
});
```
#### `margin_warning` — High margin usage
Fires when margin usage exceeds **80%** of equity. After the first trigger, it only fires again if margin usage increases by another 5 percentage points (prevents spam). Resets when margin drops back below 80%.
**Payload:** `{ marginUsedPct: number, equity: number, marginUsed: number }`
**When to use:**
- Automated risk reduction (close smallest position to free margin)
- Alerting the user before liquidation risk
- Pausing new entries when margin is high
**Example:**
```typescript
api.on('margin_warning', async ({ marginUsedPct, equity }) => {
await api.publish(
`🚨 Margin at ${marginUsedPct.toFixed(1)}% — equity: $${equity.toFixed(2)}. Consider reducing exposure.`,
{ name: 'margin-alert' },
);
});
```
#### `order_update` — Real-time order lifecycle (WebSocket)
Fires instantly when any order changes status: `open`, `filled`, `canceled`, `triggered`, `rejected`, `marginCanceled`, `liquidatedCanceled`, `badAloPxRejected`, and 20+ other statuses. Requires WebSocket (enabled by default).
**Payload:** `{ coin: string, oid: number, side: 'buy' | 'sell', size: number, price: number, origSize: number, status: string, statusTimestamp: number }`
**Example:**
```typescript
api.on('order_update', async ({ coin, oid, status, side, size, price }) => {
if (status === 'filled') {
api.log.info(`Order ${oid} filled: ${side} ${size} ${coin} @ $${price}`);
} else if (status === 'canceled' || status.includes('Rejected')) {
api.log.warn(`Order ${oid} ${status}: ${coin}`);
}
});
```
#### `liquidation` — Liquidation alert (WebSocket only)
Fires when the account is liquidated. This event is **only available via WebSocket** — there is no REST polling equivalent.
**Payload:** `{ lid: number, liquidator: string, liquidatedUser: string, liquidatedNtlPos: number, liquidatedAccountValue: number }`
**Example:**
```typescript
api.on('liquidation', async ({ liquidatedNtlPos, liquidatedAccountValue }) => {
await api.publish(
`LIQUIDATED: $${liquidatedNtlPos.toFixed(2)} notional, account value: $${liquidatedAccountValue.toFixed(2)}`,
{ name: 'liquidation-alert' },
);
});
```
### WebSocket Real-Time Data
Automations use **WebSocket by default** for real-time market and account events. The runtime subscribes to:
- **allMids** — price updates for all assets (drives `price_change` events in real-time)
- **orderUpdates** — order lifecycle events (drives `order_update` and `order_filled`)
- **userFills** — trade fill details with PnL and fees
- **userEvents** — liquidation alerts, funding payments, system cancellations
REST polling continues as a **heartbeat** (every 60s by default) for position/margin/funding events that aren't covered by WebSocket. If the WebSocket connection fails, the runtime falls back to full REST polling (every 10s) automatically.
To disable WebSocket (pure REST polling):
```bash
openbroker auto run my-strategy.ts --no-ws
```
### Choosing the Right Event — Quick Guide
| Use case | Best event | Why |
|----------|-----------|-----|
| Alert when price crosses a fixed level | `tick` | Fires every poll — no minimum change threshold |
| React to price momentum/volatility | `price_change` | Real-time via WebSocket, provides relative change data |
| Funding rate strategy | `funding_update` | Gives annualized rate directly |
| Auto TP/SL on new positions | `position_opened` | Fires exactly when a new position appears |
| Log when positions close | `position_closed` | Fires when position disappears |
| Track position scaling | `position_changed` | Fires on size changes only |
| Risk management — PnL spikes | `pnl_threshold` | Only fires on large moves (≥5% of position value) |
| Risk management — margin | `margin_warning` | Fires at 80%+ margin usage |
| React instantly to order fills/rejects | `order_update` | Real-time via WebSocket — sub-second latency |
| Liquidation alerts | `liquidation` | WebSocket only — no REST equivalent |
| Periodic task (DCA, rebalance) | `api.every(ms, fn)` | Better than tick for longer intervals |
### Client Methods Available
The `api.client` object exposes the full `HyperliquidClient`. All `coin` params accept HIP-3 prefixed tickers (e.g. `xyz:CL`). Optional `user` params default to the configured wallet address.
#### Trading
| Method | Description |
|--------|-------------|
| `marketOrder(coin, isBuy, size, slippageBps?, leverage?)` | Market order via IOC limit at mid ± slippage. Returns `OrderResponse` |
| `limitOrder(coin, isBuy, size, price, tif?, reduceOnly?, leverage?)` | Limit order. `tif`: `'Gtc'` (default), `'Ioc'`, `'Alo'`. Returns `OrderResponse` |
| `triggerOrder(coin, isBuy, size, triggerPrice, limitPrice, tpsl, reduceOnly?, leverage?)` | Trigger (conditional) order. `tpsl`: `'tp'` or `'sl'`. Activates when price hits `triggerPrice`, then fills as limit at `limitPrice`. Returns `OrderResponse` |
| `stopLoss(coin, isBuy, size, triggerPrice, slippageBps?)` | Stop loss shortcut. Sets limit price with slippage buffer (default 100 bps / 1%) to ensure fill. `reduceOnly` is always true. Returns `OrderResponse` |
| `takeProfit(coin, isBuy, size, triggerPrice)` | Take profit shortcut. Limit price = trigger price (favorable direction). `reduceOnly` is always true. Returns `OrderResponse` |
| `cancel(coin, oid)` | Cancel a single order by numeric OID. Returns `CancelResponse` |
| `cancelAll(coin?)` | Cancel all open orders. If `coin` is provided, only cancels orders for that asset. Returns `CancelResponse[]` |
| `order(coin, isBuy, size, price, orderType, reduceOnly?, includeBuilder?, leverage?)` | Low-level order placement. `orderType`: `{ limit: { tif: 'Gtc' | 'Ioc' | 'Alo' } }`. Automatically injects builder fee, rounds price/size, and handles HIP-3 margin setup. Returns `OrderResponse` |
| `outcomeMarketOrder(outcomeRef, outcomeSide, isBuy, size, slippageBps?, szDecimalsOverride?)` | HIP-4 outcome market order via IOC limit. `outcomeRef` can be an outcome id, `#encoding`, or `+encoding`. Returns `OrderResponse` |
| `outcomeLimitOrder(outcomeRef, outcomeSide, isBuy, size, price, tif?, szDecimalsOverride?)` | HIP-4 outcome limit order. `outcomeSide`: `yes`/`no` or `0`/`1` for plain ids. Returns `OrderResponse` |
#### Market Data
| Method | Returns |
|--------|---------|
| `getAllMids()` | `Record<string, string>` — mid prices for assets. HIP-4 outcomes use `#<encoding>` keys when available |
| `getMetaAndAssetCtxs()` | `MetaAndAssetCtxs` — market metadata (universe of assets with `szDecimals`, `maxLeverage`) and asset contexts (funding, open interest, volume, mark/oracle prices) |
| `getL2Book(coin)` | `{ bids, asks, bestBid, bestAsk, midPrice, spread, spreadBps }` — L2 order book with computed spread |
| `getRecentTrades(coin)` | `Array<{ coin, side, px, sz, time, hash, tid }>` — recent trade tape. `side`: `'B'` (buy) or `'A'` (sell) |
| `getCandleSnapshot(coin, interval, startTime, endTime?)` | `Array<{ t, T, s, i, o, c, h, l, v, n }>` — OHLCV candles. `interval`: `'1m'`, `'5m'`, `'15m'`, `'1h'`, `'4h'`, `'1d'`. Times are Unix ms |
| `getFundingHistory(coin, startTime, endTime?)` | `Array<{ coin, fundingRate, premium, time }>` — historical hourly funding rates |
| `getPredictedFundings()` | `Array<[coin, Array<[venue, { fundingRate, nextFundingTime }]>]>` — predicted funding rates across all venues |
| `getPerpDexs()` | `Array<{ name, fullName, deployer } | null>` — list of perp DEXs. Index 0 is `null` (main), rest are HIP-3 |
| `getAllPerpMetas()` | `Array<{ dexName, meta, assetCtxs }>` — metadata + contexts for every perp DEX (main + all HIP-3) |
| `getSpotMeta()` | `{ tokens, universe }` — spot market metadata (token info, trading pairs) |
| `getSpotMetaAndAssetCtxs()` | `{ meta, assetCtxs }` — spot metadata + price/volume contexts |
| `getOutcomeMeta()` | HIP-4 raw outcome metadata (`outcomes`, `questions`) |
| `getOutcomeMarkets()` | Normalized HIP-4 markets with parsed descriptions, side encodings, asset ids, token decimals, and price context |
| `resolveOutcomeRef(ref, side?)` | Convert outcome id / `#encoding` / `+encoding` into `{ outcome, side, encoding, coin, tokenName, assetId }` |
| `getTokenDetails(tokenId)` | Token details: supply, deployer, prices. Returns `null` if not found |
#### Account
| Method | Returns |
|--------|---------|
| `getUserStateAll(user?)` | `ClearinghouseState` — full account state across all dexes: `marginSummary` (accountValue, totalMarginUsed, withdrawable), `crossMarginSummary`, and `assetPositions[]` (each with `position.coin`, `.szi`, `.entryPx`, `.unrealizedPnl`, `.positionValue`, `.leverage`, `.marginUsed`, `.liquidationPx`) |
| `getUserState(user?, dex?)` | `ClearinghouseState` — account state for a single dex (omit `dex` for main perps) |
| `getOpenOrders(user?)` | `OpenOrder[]` — all open orders across all dexes. Each: `{ coin, side, limitPx, sz, oid, timestamp, orderType }` |
| `getUserFills(user?, aggregateByTime?)` | `Array<{ coin, px, sz, side, time, closedPnl, fee, oid, tid, crossed, builderFee }>` — trade fill history. `side`: `'B'` (buy) or `'A'` (sell) |
| `getHistoricalOrders(user?)` | `Array<{ order: { coin, side, limitPx, sz, origSz, oid, timestamp, orderType, tif, triggerCondition, triggerPx, isTrigger, isPositionTpsl, reduceOnly }, status, statusTimestamp }>` — all orders (filled, cancelled, etc.) |
| `getOrderStatus(oid, user?)` | `{ status, order? }` — status of a specific order by numeric OID or string CLOID |
| `getUserFunding(user?, startTime?, endTime?)` | `Array<{ time, hash, delta: { coin, usdc, szi, fundingRate } }>` — funding payments received/paid |
| `getUserFees(user?)` | `{ dailyUserVlm, feeSchedule, userCrossRate, userAddRate, activeReferralDiscount, activeStakingDiscount }` — fee tier, rates, and volume |
| `getUserRateLimit(user?)` | `{ cumVlm, nRequestsUsed, nRequestsCap, nRequestsSurplus }` — API rate limit status |
| `getSpotBalances(user?)` | `{ balances: Array<{ coin, token, hold, total, entryNtl }> }` — spot token balances |
| `getSubAccounts(user?)` | `Array<{ subAccountUser, name }>` — sub-accounts for a master wallet |
| `getAccountMode(user?)` | `string` — account abstraction mode: `'standard'`, `'unified'`, `'portfolio'`, or `'dexAbstraction'` |
| `isUnifiedAccount(user?)` | `boolean` — `true` if unified or portfolio margin (shared USDC across dexes) |
#### Leverage & Config
| Method | Description |
|--------|-------------|
| `updateLeverage(coin, leverage, isCross?)` | Set leverage. `isCross` defaults to `true` (cross margin). HIP-3 assets are forced to isolated and clamped to their max leverage |
| `approveBuilderFee(maxFeeRate?, builder?)` | Approve builder fee (must be called from main wallet, not API wallet). Default rate: `'0.1%'` |
| `getMaxBuilderFee(user?, builder?)` | Check approved builder fee. Returns fee string (e.g. `'0.01%'`) or `null` if not approved |
#### Utility Properties
| Property / Method | Description |
|-------------------|-------------|
| `getAssetIndex(coin)` | Get numeric asset index for a coin (used internally for order wire) |
| `getSzDecimals(coin)` | Get size decimal precision for a coin |
| `isHip3(coin)` | Check if a coin is a HIP-3 asset |
| `getCoinDex(coin)` | Get dex name for a coin (`null` for main perps) |
| `getAllAssetNames()` | Get all known asset names (main + HIP-3) |
| `getHip3AssetNames()` | Get only HIP-3 asset names |
| `invalidateMetaCache()` | Force refresh of market metadata on next call |
#### Utility Functions (`api.utils`)
| Function | Description |
|----------|-------------|
| `roundPrice(price, szDecimals, isSpot?)` | Round price to 5 significant figures (max 6 decimals perp, 8 spot) |
| `roundSize(size, szDecimals)` | Round size to asset-specific decimal precision |
| `sleep(ms)` | Promise-based delay |
| `normalizeCoin(coin)` | Normalize coin name (uppercase, trim whitespace) |
| `formatUsd(amount)` | Format number as USD string (e.g. `$1,234.56`) |
| `annualizeFundingRate(hourlyRate)` | Convert hourly funding rate to annualized percentage |
### Example: Price Breakout
```typescript
// ~/.openbroker/automations/breakout.ts
export default function(api) {
const COIN = 'ETH';
const BREAKOUT_PCT = 2; // 2% move triggers entry
const SIZE = 0.5;
let basePrice = null;
api.onStart(async () => {
const mids = await api.client.getAllMids();
basePrice = parseFloat(mids[COIN]);
api.log.info(`Watching ${COIN} from $${basePrice} for ${BREAKOUT_PCT}% breakout`);
});
api.on('price_change', async ({ coin, newPrice }) => {
if (coin !== COIN || !basePrice) return;
const totalChange = ((newPrice - basePrice) / basePrice) * 100;
if (Math.abs(totalChange) >= BREAKOUT_PCT && !api.state.get('inPosition')) {
const side = totalChange > 0; // true = long, false = short
api.log.info(`Breakout! ${totalChange.toFixed(2)}% — entering ${side ? 'long' : 'short'}`);
await api.client.marketOrder(COIN, side, SIZE);
api.state.set('inPosition', true);
}
});
}
```
### Example: Scheduled DCA
```typescript
// ~/.openbroker/automations/hourly-dca.ts
export default function(api) {
const COIN = 'ETH';
const USD_PER_BUY = 100;
// Buy $100 of ETH every hour
api.every(60 * 60 * 1000, async () => {
const mids = await api.client.getAllMids();
const price = parseFloat(mids[COIN]);
const size = parseFloat(api.utils.roundSize(USD_PER_BUY / price, 4));
await api.client.marketOrder(COIN, true, size);
const count = (api.state.get('buyCount') || 0) + 1;
api.state.set('buyCount', count);
api.log.info(`DCA #${count}: bought ${size} ${COIN} at $${price}`);
});
}
```
### Example: Margin Guardian
```typescript
// ~/.openbroker/automations/margin-guard.ts
export default function(api) {
api.on('margin_warning', async ({ marginUsedPct, equity }) => {
api.log.warn(`Margin at ${marginUsedPct.toFixed(1)}% — reducing positions`);
// Close the smallest position to free margin
const state = await api.client.getUserStateAll();
const positions = state.assetPositions
.filter(p => parseFloat(p.position.szi) !== 0)
.sort((a, b) => Math.abs(parseFloat(a.position.positionValue)) - Math.abs(parseFloat(b.position.positionValue)));
if (positions.length > 0) {
const pos = positions[0].position;
const size = Math.abs(parseFloat(pos.szi));
const isBuy = parseFloat(pos.szi) < 0; // Close short = buy, close long = sell
api.log.info(`Closing smallest position: ${pos.coin} (${pos.szi})`);
await api.client.marketOrder(pos.coin, isBuy, size);
}
});
}
```
### Publishing to the Agent (Webhooks)
Use `api.publish()` to send messages back to the OpenClaw agent. This triggers an agent turn — the agent receives the message and can notify the user via their preferred channel, take trading actions, or log the event.
```typescript
// Simple notification
await api.publish(`ETH broke above $4000 — current price: $${price}`);
// With options
await api.publish(`Margin at ${pct}% — positions at risk`, {
name: 'margin-alert', // appears in logs
wakeMode: 'now', // 'now' (default) or 'next-heartbeat'
channel: 'slack', // target channel (optional)
});
```
`api.publish()` returns `true` if delivered, `false` if webhooks are not configured (no hooks token). It requires `OPENCLAW_HOOKS_TOKEN` to be set (automatically configured when running as an OpenClaw plugin).
**Example: Price alert automation with publish**
```typescript
// ~/.openbroker/automations/price-alert.ts
export default function(api) {
const COIN = 'ETH';
const THRESHOLD = 4000;
api.on('price_change', async ({ coin, newPrice, changePct }) => {
if (coin !== COIN) return;
const crossed = api.state.get<boolean>('crossed', false);
if (!crossed && newPrice >= THRESHOLD) {
api.state.set('crossed', true);
await api.publish(
`${COIN} crossed above $${THRESHOLD}! Price: $${newPrice.toFixed(2)} (+${changePct.toFixed(2)}%)`,
);
} else if (crossed && newPrice < THRESHOLD) {
api.state.set('crossed', false);
}
});
}
```
### Running Automations
**CLI:**
```bash
openbroker auto run my-strategy --dry # Test without trading
openbroker auto run ./funding-scalp.ts # Run from path
openbroker auto run my-strategy --poll 5000 # Poll every 5s
openbroker auto run my-strategy --no-ws # Disable WebSocket, pure REST polling
openbroker auto run --example dca --set coin=HYPE --set amount=50 --dry # Run bundled example
openbroker auto examples # List bundled examples with config
openbroker auto list # Show available scripts
openbroker auto status # Show running automations
openbroker auto stop <id> # Unregister an automation (won't auto-restart)
openbroker auto report <id> # Read the local audit report (logs, trades, metrics) for an automation
openbroker auto clean # Remove stale entries from the registry
```
**Plugin tools (for OpenClaw agents):**
- `ob_auto_run` — `{ "script": "funding-scalp", "dry": true }` — start an automation
- `ob_auto_run` — `{ "example": "dca", "config": { "coin": "HYPE", "amount": 50 }, "dry": true }` — run a bundled example
- `ob_auto_stop` — `{ "id": "funding-scalp" }` — stop a running automation
- `ob_auto_list` — `{}` — list available automations, bundled examples with config schemas, and running automations
**Options:**
| Flag | Description | Default |
|------|-------------|---------|
| `--dry` | Intercept write methods — no real trades | false |
| `--verbose` | Show debug output | false |
| `--id <name>` | Custom automation ID | filename |
| `--poll <ms>` | Poll interval in milliseconds | 10000 |
| `--no-ws` | Disable WebSocket; fall back to REST-only polling | WebSocket on |
| `--example <name>` | Run a bundled example automation | - |
| `--set key=value` | Set config values (repeatable) | - |
**Inspecting automations after they run:**
- `openbroker auto report <id>` — reads the local SQLite audit trail at `~/.openbroker/automation-audit.sqlite` and prints a summary of logs, write actions, fills, PnL, and custom metrics recorded via `api.audit.record()` / `api.audit.metric()`. Use this to review what a strategy actually did.
- `openbroker auto clean` — prunes registry entries for automations that are no longer running or whose script file is gone. Safe to run anytime.
**Guidelines for agents writing automations:**
**Risk & Safety (mandatory):**
- Always attach a liquidation monitoring automation to every open position. Subscribe to `margin_warning` and `pnl_threshold` events so the user is never blindsided by liquidation risk. If no margin/liquidation automation is already running, create one before placing trades.
- Use `api.publish()` to notify the user of important events — position opens/closes, TP/SL triggers, large PnL swings, margin warnings, errors, and any situation that requires human attention. Do NOT silently handle critical events.
- Always register an `api.onStop()` handler to clean up — cancel open orders and close positions (or at minimum alert the user) on shutdown. Never leave orphaned orders or unmanaged positions.
- Do NOT use `--dry` unless the user explicitly asks for it. Automations should run live by default.
- Never place trades without validating that sufficient margin is available. Check account state before sizing orders.
- Cap position sizes relative to account equity. Do not risk more than a reasonable percentage of equity on a single trade unless the user explicitly specifies the size.
- Always set TP/SL on new positions — either within the automation or by confirming the user has them set. Unprotected positions are a liability.
**State & Reliability:**
- Use `api.state` to track position state, entry prices, and flags across restarts. Never rely on in-memory variables alone — automations persist across gateway restarts and are automatically restarted.
- Use idempotency guards (`api.state.get`/`set`) to prevent duplicate orders. Events can fire multiple times for the same condition across polls — always check state before placing orders.
- The runtime catches errors per handler — one failing handler won't crash others, but always handle expected errors (e.g. order rejection, insufficient margin) gracefully within handlers.
**Communication:**
- Use `api.publish()` to send alerts/events back to the OpenClaw agent — do NOT manually construct webhook requests.
- Publish on: position opened/closed, TP/SL triggered, PnL threshold exceeded, margin warning, automation errors, and any automated trade execution. The user should always know what the automation did and why.
- Include actionable context in publish messages — coin, price, size, PnL, and what happened — so the user can make informed decisions without checking the terminal.
**General:**
- Scripts are loaded from `~/.openbroker/automations/` by name, or from any absolute path.
- All trading commands support HIP-3 assets (`api.client.marketOrder('xyz:CL', true, 1)`).
- Automations persist across gateway restarts — they are automatically restarted when the gateway comes back up.
- Prefer `api.every(ms, fn)` over `tick` for periodic tasks with intervals longer than the poll cycle.
## Risk Warning
- Always use `--dry` first to preview orders
- Start with small sizes on testnet (`HYPERLIQUID_NETWORK=testnet`)
- Monitor positions and liquidation prices
- Use `--reduce` for closing positions only
don't have the plugin yet? install it then click "run inline in claude" again.