openbroker 1.0.89 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md DELETED
@@ -1,1182 +0,0 @@
1
- ---
2
- name: openbroker
3
- 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.
4
- license: MIT
5
- compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
6
- homepage: https://www.npmjs.com/package/openbroker
7
- metadata: {"author": "monemetrics", "version": "1.0.89", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
8
- allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot 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_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:*)
9
- ---
10
-
11
- # Open Broker - Hyperliquid Trading CLI
12
-
13
- Execute trading operations on Hyperliquid DEX with builder fee support.
14
-
15
- ## Installation
16
-
17
- ```bash
18
- npm install -g openbroker
19
- ```
20
-
21
- ## Quick Start
22
-
23
- ```bash
24
- # 1. Setup (generates wallet, creates config, approves builder fee)
25
- openbroker setup
26
-
27
- # 2. Fund your wallet with USDC on Arbitrum, then deposit at https://app.hyperliquid.xyz/
28
-
29
- # 3. Start trading
30
- openbroker account
31
- openbroker buy --coin ETH --size 0.1
32
- ```
33
-
34
- ## Important: Finding Assets Before Trading
35
-
36
- **Always search before trading an unfamiliar asset.** Hyperliquid has main perps (ETH, BTC, SOL...), HIP-3 perps (xyz:CL, xyz:GOLD, km:USOIL...), and spot markets. Use search to discover the correct ticker:
37
-
38
- ```bash
39
- openbroker search --query GOLD # Find all GOLD markets across all providers
40
- openbroker search --query oil # Find oil-related assets (CL, BRENTOIL, USOIL...)
41
- openbroker search --query BTC --type perp # BTC perps only
42
- openbroker search --query NATGAS --type hip3 # HIP-3 only
43
- ```
44
-
45
- Or with the `ob_search` plugin tool: `{ "query": "gold" }` or `{ "query": "oil", "type": "hip3" }`
46
-
47
- **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`.
48
-
49
- ### Asset IDs (disambiguation)
50
-
51
- 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).
52
-
53
- | Scope | Formula | Example |
54
- |-------|---------|---------|
55
- | Main perps | universe index | `HYPE` → `159` |
56
- | HIP-3 perps | `100000 + dexIdx * 10000 + assetIdx` | `hyna:HYPE` → `140002` |
57
- | Spot | `10000 + pair.index` | `HYPE/USDC` → `10107` |
58
-
59
- ```bash
60
- openbroker search HYPE --json | jq '.[] | {coin, assetId, type, provider}'
61
- ```
62
-
63
- Trading commands still take `--coin <name>` (including HIP-3 `dex:COIN`) — `assetId` is for queries, comparisons, and agent state, not order placement.
64
-
65
- ## Troubleshooting: CLI Fallback
66
-
67
- 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.
68
-
69
- **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.
70
-
71
- | Plugin Tool | CLI Equivalent |
72
- |-------------|---------------|
73
- | `ob_account` | `openbroker account --json` |
74
- | `ob_positions` | `openbroker positions --json` |
75
- | `ob_funding` | `openbroker funding --json --include-hip3` |
76
- | `ob_markets` | `openbroker markets --json --include-hip3` |
77
- | `ob_search` | `openbroker search --query <QUERY> --json` |
78
- | `ob_spot` | `openbroker spot --json` (or `--balances --json`) |
79
- | `ob_fills` | `openbroker fills --json` |
80
- | `ob_orders` | `openbroker orders --json` |
81
- | `ob_order_status` | `openbroker order-status --oid <OID> --json` |
82
- | `ob_fees` | `openbroker fees --json` |
83
- | `ob_candles` | `openbroker candles --coin <COIN> --json` |
84
- | `ob_funding_history` | `openbroker funding-history --coin <COIN> --json` |
85
- | `ob_trades` | `openbroker trades --coin <COIN> --json` |
86
- | `ob_rate_limit` | `openbroker rate-limit --json` |
87
- | `ob_funding_scan` | `openbroker funding-scan --json` |
88
- | `ob_buy` | `openbroker buy --coin <COIN> --size <SIZE>` |
89
- | `ob_sell` | `openbroker sell --coin <COIN> --size <SIZE>` |
90
- | `ob_limit` | `openbroker limit --coin <COIN> --side <SIDE> --size <SIZE> --price <PRICE>` |
91
- | `ob_tpsl` | `openbroker tpsl --coin <COIN> --tp <PRICE> --sl <PRICE>` |
92
- | `ob_cancel` | `openbroker cancel --all` or `--coin <COIN>` |
93
- | `ob_auto_run` | `openbroker auto run <script> [--dry]` |
94
- | `ob_auto_stop` | `openbroker auto stop <id>` (or SIGINT if run in foreground) |
95
- | `ob_auto_list` | `openbroker auto list` |
96
-
97
- **When to use CLI fallback:**
98
- - Plugin tool returns `null`, empty data, or throws an error
99
- - You need data the plugin tool doesn't expose (e.g., `--verbose` debug output)
100
- - Long-running operations (strategies) — the CLI handles timeouts and progress better
101
-
102
- Add `--dry` to any trading CLI command to preview without executing. Add `--json` to info commands for structured output.
103
-
104
- ## Command Reference
105
-
106
- ### Setup
107
- ```bash
108
- openbroker setup # One-command setup (wallet + config + builder approval)
109
- openbroker approve-builder --check # Check builder fee status (for troubleshooting)
110
- ```
111
-
112
- The `setup` command offers three modes:
113
- 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.
114
- 2. **Import existing key** — use a private key you already have
115
- 3. **Generate API wallet** — creates a restricted wallet that can trade but cannot withdraw. Requires browser approval from a master wallet.
116
-
117
- For options 1 and 2, setup saves config and approves the builder fee automatically.
118
- For option 3 (API wallet), see the API Wallet Setup section below.
119
-
120
- ### Fresh Wallet Setup (Recommended for Agents)
121
-
122
- 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.
123
-
124
- **Flow:**
125
- 1. Run `openbroker setup` and choose option 1 ("Generate a fresh wallet")
126
- 2. The CLI generates a wallet, saves the config, and approves the builder fee automatically
127
- 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.**
128
- 4. Start trading
129
-
130
- ### API Wallet Setup (Alternative)
131
-
132
- 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.
133
-
134
- **Flow:**
135
- 1. Run `openbroker setup` and choose option 3 ("Generate API wallet")
136
- 2. The CLI generates a keypair and prints an approval URL (e.g. `https://openbroker.dev/approve?agent=0xABC...`)
137
- 3. The agent owner opens the URL in a browser and connects their master wallet (MetaMask etc.)
138
- 4. The master wallet signs two transactions: `ApproveAgent` (authorizes the API wallet) and `ApproveBuilderFee` (approves the 1 bps fee)
139
- 5. The CLI detects the approval automatically and saves the config
140
-
141
- **After setup, the config will contain:**
142
- ```
143
- HYPERLIQUID_PRIVATE_KEY=0x... # API wallet private key
144
- HYPERLIQUID_ACCOUNT_ADDRESS=0x... # Master account address
145
- HYPERLIQUID_NETWORK=mainnet
146
- ```
147
-
148
- **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`.
149
-
150
- ### Account Info
151
- ```bash
152
- openbroker account # Balance, equity, margin
153
- openbroker account --orders # Include open orders
154
- openbroker account --address 0xabc... # Look up another account
155
- openbroker positions # Open positions with PnL
156
- openbroker positions --coin ETH # Specific coin
157
- openbroker positions --address 0xabc... # Another account's positions
158
- ```
159
-
160
- ### Funding Rates
161
- ```bash
162
- openbroker funding --top 20 # Top 20 by funding rate
163
- openbroker funding --coin ETH # Specific coin
164
- openbroker funding --top 20 --json # JSON (includes assetId)
165
- ```
166
-
167
- ### Markets
168
- ```bash
169
- openbroker markets --top 30 # Top 30 main perps
170
- openbroker markets --coin BTC # Specific coin
171
- openbroker markets --coin BTC --json # JSON (includes assetId)
172
- ```
173
-
174
- ### All Markets (Perps + Spot + HIP-3)
175
- ```bash
176
- openbroker all-markets # Show all markets
177
- openbroker all-markets --type perp # Main perps only
178
- openbroker all-markets --type hip3 # HIP-3 perps only
179
- openbroker all-markets --type spot # Spot markets only
180
- openbroker all-markets --top 20 # Top 20 by volume
181
- openbroker all-markets --json # JSON (includes assetId)
182
- ```
183
-
184
- ### Search Markets (Find assets across providers)
185
- ```bash
186
- openbroker search --query GOLD # Find all GOLD markets
187
- openbroker search --query BTC # Find BTC across all providers
188
- openbroker search --query ETH --type perp # ETH perps only
189
- openbroker search HYPE --json # JSON with assetId per result
190
- ```
191
-
192
- ### Spot Markets
193
- ```bash
194
- openbroker spot # Show all spot markets
195
- openbroker spot --coin PURR # Show PURR market info
196
- openbroker spot --balances # Show your spot balances
197
- openbroker spot --balances --address 0xabc... # Another account's spot balances
198
- openbroker spot --top 20 # Top 20 by volume
199
- openbroker spot --json # JSON (includes assetId, base, quote)
200
- ```
201
-
202
- ### Trade Fills
203
- ```bash
204
- openbroker fills # Recent fills
205
- openbroker fills --coin ETH # ETH fills only
206
- openbroker fills --coin BTC --side buy --top 50
207
- openbroker fills --address 0xabc... # Another account's fills
208
- ```
209
-
210
- ### Order History
211
- ```bash
212
- openbroker orders # Recent orders (all statuses)
213
- openbroker orders --open # Currently open orders only
214
- openbroker orders --open --coin ETH # Open orders for a specific coin
215
- openbroker orders --coin ETH --status filled
216
- openbroker orders --top 50
217
- openbroker orders --address 0xabc... --open # Another account's open orders
218
- ```
219
-
220
- ### Order Status
221
- ```bash
222
- openbroker order-status --oid 123456789 # Check specific order
223
- openbroker order-status --oid 0x1234... # By client order ID
224
- openbroker order-status --oid 123456789 --address 0xabc... # On another account
225
- openbroker order-status --oid 123456789 --json
226
- ```
227
-
228
- ### Fee Schedule
229
- ```bash
230
- openbroker fees # Fee tier, rates, and volume
231
- openbroker fees --address 0xabc... # Another account's fees
232
- openbroker fees --json
233
- ```
234
-
235
- ### Candle Data (OHLCV)
236
- ```bash
237
- openbroker candles --coin ETH # 24 hourly candles
238
- openbroker candles --coin BTC --interval 4h --bars 48 # 48 four-hour bars
239
- openbroker candles --coin SOL --interval 1d --bars 30 # 30 daily bars
240
- openbroker candles --coin ETH --json # JSON (coin, assetId, interval, candles)
241
- ```
242
-
243
- ### Funding History
244
- ```bash
245
- openbroker funding-history --coin ETH # Last 24h
246
- openbroker funding-history --coin BTC --hours 168 # Last 7 days
247
- openbroker funding-history --coin ETH --json # JSON (coin, assetId, history)
248
- ```
249
-
250
- ### Recent Trades (Tape)
251
- ```bash
252
- openbroker trades --coin ETH # Last 30 trades
253
- openbroker trades --coin BTC --top 50 # Last 50 trades
254
- openbroker trades --coin ETH --json # JSON (coin, assetId, trades)
255
- ```
256
-
257
- ### Rate Limit
258
- ```bash
259
- openbroker rate-limit # API usage and capacity
260
- openbroker rate-limit --json
261
- ```
262
-
263
- ### Funding Rate Scanner (Cross-Dex)
264
- ```bash
265
- openbroker funding-scan # Scan all dexes, >25% threshold
266
- openbroker funding-scan --threshold 50 --pairs # Show opposing funding pairs
267
- openbroker funding-scan --hip3-only --top 20 # HIP-3 only
268
- openbroker funding-scan --watch --interval 120 # Re-scan every 2 minutes
269
- openbroker funding-scan --json # JSON (includes assetId per result)
270
- ```
271
-
272
- ## Trading Commands
273
-
274
- ### HIP-3 Perp Trading
275
- All trading commands support HIP-3 assets using `dex:COIN` syntax:
276
- ```bash
277
- openbroker buy --coin xyz:CL --size 1 # Buy crude oil on xyz dex
278
- openbroker sell --coin xyz:BRENTOIL --size 1 # Sell brent oil
279
- openbroker limit --coin xyz:GOLD --side buy --size 0.1 --price 2500
280
- ```
281
-
282
- ### Market Orders (Quick)
283
- ```bash
284
- openbroker buy --coin ETH --size 0.1
285
- openbroker sell --coin BTC --size 0.01
286
- openbroker buy --coin SOL --size 5 --slippage 100 # Custom slippage (bps)
287
- ```
288
-
289
- ### Market Orders (Full)
290
- ```bash
291
- openbroker market --coin ETH --side buy --size 0.1
292
- openbroker market --coin BTC --side sell --size 0.01 --slippage 100
293
- ```
294
-
295
- ### Limit Orders
296
- ```bash
297
- openbroker limit --coin ETH --side buy --size 1 --price 3000
298
- openbroker limit --coin SOL --side sell --size 10 --price 200 --tif ALO
299
- ```
300
-
301
- ### Set TP/SL on Existing Position
302
- ```bash
303
- # Set take profit at $40, stop loss at $30
304
- openbroker tpsl --coin HYPE --tp 40 --sl 30
305
-
306
- # Set TP at +10% from entry, SL at entry (breakeven)
307
- openbroker tpsl --coin HYPE --tp +10% --sl entry
308
-
309
- # Set only stop loss at -5% from entry
310
- openbroker tpsl --coin ETH --sl -5%
311
-
312
- # Partial position TP/SL
313
- openbroker tpsl --coin ETH --tp 4000 --sl 3500 --size 0.5
314
- ```
315
-
316
- ### Trigger Orders (Standalone TP/SL)
317
- ```bash
318
- # Take profit: sell when price rises to $40
319
- openbroker trigger --coin HYPE --side sell --size 0.5 --trigger 40 --type tp
320
-
321
- # Stop loss: sell when price drops to $30
322
- openbroker trigger --coin HYPE --side sell --size 0.5 --trigger 30 --type sl
323
- ```
324
-
325
- ### Cancel Orders
326
- ```bash
327
- openbroker cancel --all # Cancel all orders
328
- openbroker cancel --coin ETH # Cancel ETH orders only
329
- openbroker cancel --oid 123456 # Cancel specific order
330
- ```
331
-
332
- ### Spot Orders
333
- 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.
334
-
335
- ```bash
336
- # Market orders (shortcuts)
337
- openbroker spot-buy --coin PURR --size 1000
338
- openbroker spot-sell --coin HYPE --size 5
339
-
340
- # Full form (specify --side)
341
- openbroker spot-order --coin PURR --side buy --size 1000
342
-
343
- # Limit orders (add --price)
344
- openbroker spot-order --coin HYPE --side sell --size 50 --price 25.50
345
- openbroker spot-order --coin PURR --side buy --size 500 --price 0.20 --tif Alo
346
-
347
- # Preview without executing
348
- openbroker spot-buy --coin PURR --size 500 --dry
349
- ```
350
-
351
- **Spot flags:** `--coin`, `--side`, `--size`, `--price` (omit → market order), `--tif` (`Gtc`/`Ioc`/`Alo`, default `Gtc`), `--slippage` (bps, market orders only), `--dry`, `--verbose`.
352
-
353
- ## Advanced Execution
354
-
355
- ### TWAP (Native Exchange Order)
356
- ```bash
357
- # Buy 1 ETH over 30 minutes (exchange handles slicing)
358
- openbroker twap --coin ETH --side buy --size 1 --duration 30
359
-
360
- # Sell 0.5 BTC over 2 hours without randomized timing
361
- openbroker twap --coin BTC --side sell --size 0.5 --duration 120 --randomize false
362
-
363
- # Reduce-only (close position with TWAP). Note: TWAP uses `--reduce-only`, not `--reduce`
364
- openbroker twap --coin ETH --side sell --size 1 --duration 30 --reduce-only
365
-
366
- # Cancel a running TWAP
367
- openbroker twap-cancel --coin ETH --twap-id 77738308
368
-
369
- # Check TWAP status
370
- openbroker twap-status --active
371
- ```
372
-
373
- ### Scale In/Out (Grid Orders)
374
- ```bash
375
- # Place 5 buy orders ranging 2% below current price
376
- openbroker scale --coin ETH --side buy --size 1 --levels 5 --range 2
377
-
378
- # Scale out with exponential distribution
379
- openbroker scale --coin BTC --side sell --size 0.5 --levels 4 --range 3 --distribution exponential --reduce
380
- ```
381
-
382
- ### Bracket Order (Entry + TP + SL)
383
- ```bash
384
- # Long ETH with 3% take profit and 1.5% stop loss
385
- openbroker bracket --coin ETH --side buy --size 0.5 --tp 3 --sl 1.5
386
-
387
- # Short with limit entry
388
- openbroker bracket --coin BTC --side sell --size 0.1 --entry limit --price 100000 --tp 5 --sl 2
389
- ```
390
-
391
- ### Chase Order (Follow Price)
392
- ```bash
393
- # Chase buy with ALO orders until filled
394
- openbroker chase --coin ETH --side buy --size 0.5 --timeout 300
395
-
396
- # Aggressive chase with tight offset
397
- openbroker chase --coin SOL --side buy --size 10 --offset 2 --timeout 60
398
- ```
399
-
400
- ## Order Types
401
-
402
- ### Limit Orders vs Trigger Orders
403
-
404
- **Limit Orders** (`openbroker limit`):
405
- - Execute immediately if price is met
406
- - Rest on the order book until filled or cancelled
407
- - A limit sell BELOW current price fills immediately (taker)
408
- - NOT suitable for stop losses
409
-
410
- **Trigger Orders** (`openbroker trigger`, `openbroker tpsl`):
411
- - Stay dormant until trigger price is reached
412
- - Only activate when price hits the trigger level
413
- - Proper way to set stop losses and take profits
414
- - Won't fill prematurely
415
-
416
- ### When to Use Each
417
-
418
- | Scenario | Command |
419
- |----------|---------|
420
- | Buy at specific price below market | `openbroker limit` |
421
- | Sell at specific price above market | `openbroker limit` |
422
- | Stop loss (exit if price drops) | `openbroker trigger --type sl` |
423
- | Take profit (exit at target) | `openbroker trigger --type tp` |
424
- | Add TP/SL to existing position | `openbroker tpsl` |
425
-
426
- ## Common Arguments
427
-
428
- All commands support `--dry` for dry run (preview without executing).
429
-
430
- | Argument | Description |
431
- |----------|-------------|
432
- | `--coin` | Asset symbol (ETH, BTC, SOL, HYPE, etc.) |
433
- | `--side` | Order direction: `buy` or `sell` |
434
- | `--size` | Order size in base asset |
435
- | `--price` | Limit price |
436
- | `--dry` | Preview without executing |
437
- | `--help` | Show command help |
438
-
439
- ### Order Arguments
440
- | Argument | Description |
441
- |----------|-------------|
442
- | `--trigger` | Trigger price (for trigger orders) |
443
- | `--type` | Trigger type: `tp` or `sl` |
444
- | `--slippage` | Slippage tolerance in bps (for market orders) |
445
- | `--tif` | Time in force: GTC, IOC, ALO |
446
- | `--reduce` | Reduce-only order |
447
-
448
- ### TP/SL Price Formats
449
- | Format | Example | Description |
450
- |--------|---------|-------------|
451
- | Absolute | `--tp 40` | Price of $40 |
452
- | Percentage up | `--tp +10%` | 10% above entry |
453
- | Percentage down | `--sl -5%` | 5% below entry |
454
- | Entry price | `--sl entry` | Breakeven stop |
455
-
456
- ## Configuration
457
-
458
- Config is loaded from (in priority order):
459
- 1. Environment variables
460
- 2. `.env` in current directory
461
- 3. `~/.openbroker/.env` (global config)
462
-
463
- Run `openbroker setup` to create the global config interactively.
464
-
465
- | Variable | Required | Description |
466
- |----------|----------|-------------|
467
- | `HYPERLIQUID_PRIVATE_KEY` | Yes | Wallet private key (0x...) |
468
- | `HYPERLIQUID_NETWORK` | No | `mainnet` (default) or `testnet` |
469
- | `HYPERLIQUID_ACCOUNT_ADDRESS` | No | Master account address (required for API wallets) |
470
- | `OB_DASHBOARD_URL` | No | Dashboard API URL for forwarding audit notes, metrics, and agent actions (e.g. `http://localhost:3001`) |
471
-
472
- The builder fee (1 bps / 0.01%) is hardcoded and not configurable.
473
-
474
- ## OpenClaw Plugin (Optional)
475
-
476
- 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.
477
-
478
- ### What the plugin adds
479
-
480
- - **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.
481
- - **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.
482
- - **Automation tools** (`ob_auto_run`, `ob_auto_stop`, `ob_auto_list`) — start, stop, and manage custom trading automations from within the agent.
483
- - **CLI commands** — `openclaw ob status` and `openclaw ob watch` for inspecting the watcher.
484
-
485
- ### Enable the plugin
486
-
487
- The plugin is bundled in the same `openbroker` npm package. To enable it in your OpenClaw config:
488
-
489
- ```yaml
490
- plugins:
491
- entries:
492
- openbroker:
493
- enabled: true
494
- config:
495
- hooksToken: "your-hooks-secret" # Required for watcher alerts
496
- watcher:
497
- enabled: true
498
- pollIntervalMs: 30000
499
- pnlChangeThresholdPct: 5
500
- marginUsageWarningPct: 80
501
- ```
502
-
503
- 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.
504
-
505
- ### Webhook setup for watcher alerts
506
-
507
- 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.
508
-
509
- **1. Generate a hook token** — any secure random string:
510
- ```bash
511
- openssl rand -hex 32
512
- ```
513
-
514
- **2. Enable hooks and add a mapping** in your `openclaw.json` (or `openclaw.yaml`) deployment config:
515
- ```json
516
- "hooks": {
517
- "enabled": true,
518
- "path": "/hooks",
519
- "token": "<your-generated-token>",
520
- "allowedAgentIds": ["hooks", "main", "openbroker"],
521
- "mappings": [
522
- {
523
- "id": "main",
524
- "match": {
525
- "path": "openbroker"
526
- },
527
- "action": "agent",
528
- "wakeMode": "now",
529
- "name": "Openbroker",
530
- "agentId": "main",
531
- "deliver": true,
532
- "channel": "last",
533
- "model": "anthropic/claude-sonnet-4-6"
534
- }
535
- ]
536
- }
537
- ```
538
-
539
- | Field | Description |
540
- |-------|-------------|
541
- | `token` | Shared secret — must match `hooksToken` in the plugin config |
542
- | `allowedAgentIds` | Agent IDs allowed to receive webhook requests |
543
- | `mappings[].match.path` | Matches the webhook path sent by the plugin (always `"openbroker"`) |
544
- | `mappings[].wakeMode` | `"now"` triggers an immediate agent turn. `"next-heartbeat"` queues for the next scheduled heartbeat |
545
- | `mappings[].deliver` | If `true`, the agent's response is delivered to the user via the configured channel |
546
- | `mappings[].channel` | Delivery channel: `"last"` (most recent), `"slack"`, `"telegram"`, `"discord"`, `"whatsapp"`, etc. |
547
- | `mappings[].model` | Model override for webhook-triggered turns. Optional — uses deployment default if omitted |
548
-
549
- **3. Set the same token in your plugin config:**
550
- ```yaml
551
- plugins:
552
- entries:
553
- openbroker:
554
- enabled: true
555
- config:
556
- hooksToken: "<your-generated-token>" # Same token as hooks.token
557
- watcher:
558
- enabled: true
559
- ```
560
-
561
- **4. Restart the gateway** and verify:
562
- ```bash
563
- openclaw ob status
564
- ```
565
-
566
- 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.
567
-
568
- ### Using with or without the plugin
569
-
570
- - **Skill only (no plugin):** Use Bash commands (`openbroker buy --coin ETH --size 0.1`). No background monitoring.
571
- - **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.
572
-
573
- ## Trading Automations
574
-
575
- 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.
576
-
577
- ### IMPORTANT: Bundled Examples Are References, Not Ready-Made Strategies
578
-
579
- 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:
580
-
581
- 1. Read the relevant bundled example source code to understand the automation API patterns
582
- 2. Write a **custom automation** tailored to the user's specific requirements (coin, size, thresholds, risk parameters, TP/SL logic, etc.)
583
- 3. Save it to `~/.openbroker/automations/` and run it with `openbroker auto run`
584
-
585
- 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.
586
-
587
- To view bundled examples and their config schemas:
588
- ```bash
589
- openbroker auto examples # List examples with config fields
590
- ```
591
-
592
- Available examples: `dca`, `grid`, `funding-arb`, `mm-spread`, `mm-maker`, `price-alert`
593
-
594
- ### How Automations Work
595
-
596
- 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).
597
-
598
- ### Writing an Automation
599
-
600
- Create a `.ts` file in `~/.openbroker/automations/` (or any path):
601
-
602
- ```typescript
603
- // ~/.openbroker/automations/funding-scalp.ts
604
- export default function(api) {
605
- const COIN = 'ETH';
606
-
607
- api.on('funding_update', async ({ coin, annualized }) => {
608
- if (coin !== COIN) return;
609
-
610
- if (annualized > 0.5 && !api.state.get('isShort')) {
611
- api.log.info('High positive funding — going short');
612
- await api.client.marketOrder(COIN, false, 0.1);
613
- api.state.set('isShort', true);
614
- } else if (annualized < -0.1 && api.state.get('isShort')) {
615
- api.log.info('Funding normalized — closing short');
616
- await api.client.marketOrder(COIN, true, 0.1);
617
- api.state.set('isShort', false);
618
- }
619
- });
620
-
621
- api.onStop(async () => {
622
- if (api.state.get('isShort')) {
623
- api.log.warn('Closing short on shutdown');
624
- await api.client.marketOrder('ETH', true, 0.1);
625
- api.state.set('isShort', false);
626
- }
627
- });
628
- }
629
- ```
630
-
631
- ### AutomationAPI Reference
632
-
633
- | Property / Method | Description |
634
- |-------------------|-------------|
635
- | `api.client` | Full HyperliquidClient — `marketOrder()`, `limitOrder()`, `triggerOrder()`, `cancelAll()`, `getUserStateAll()`, `getAllMids()`, `updateLeverage()`, and 35+ more methods |
636
- | `api.on(event, handler)` | Subscribe to a market/account event (see Events below) |
637
- | `api.every(ms, handler)` | Run a handler on a recurring interval (aligned to poll loop) |
638
- | `api.onStart(handler)` | Called after all handlers are registered, before first poll |
639
- | `api.onStop(handler)` | Called on shutdown (SIGINT). Use for cleanup — close positions, cancel orders |
640
- | `api.onError(handler)` | Called when a handler throws. Error is already logged — use for recovery logic |
641
- | `api.state.get(key)` | Get a persisted value (survives restarts, stored in `~/.openbroker/state/`) |
642
- | `api.state.set(key, value)` | Set a persisted value |
643
- | `api.state.delete(key)` | Delete a persisted value |
644
- | `api.state.clear()` | Clear all state |
645
- | `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? }` |
646
- | `api.log.info/warn/error/debug(msg)` | Structured logger |
647
- | `api.audit.record(kind, payload?)` | Add a custom audit note to the local SQLite trail for later reporting |
648
- | `api.audit.metric(name, value, tags?)` | Add a numeric metric to the local SQLite trail |
649
- | `api.utils` | `roundPrice`, `roundSize`, `sleep`, `normalizeCoin`, `formatUsd`, `annualizeFundingRate` |
650
- | `api.id` | Automation ID (filename or `--id` flag) |
651
- | `api.dryRun` | `true` if running with `--dry` (write methods are intercepted) |
652
-
653
- 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.
654
-
655
- **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.
656
-
657
- ### Events
658
-
659
- | Event | Payload | When |
660
- |-------|---------|------|
661
- | `tick` | `{ timestamp, pollCount }` | Every poll cycle (default: 10s) |
662
- | `price_change` | `{ coin, oldPrice, newPrice, changePct }` | Mid price moved > 0.01% between polls |
663
- | `funding_update` | `{ coin, fundingRate, annualized, premium }` | Every poll for all assets |
664
- | `position_opened` | `{ coin, side, size, entryPrice }` | New position detected |
665
- | `position_closed` | `{ coin, previousSize, entryPrice }` | Position no longer present |
666
- | `position_changed` | `{ coin, oldSize, newSize, entryPrice }` | Position size changed |
667
- | `pnl_threshold` | `{ coin, unrealizedPnl, changePct, positionValue }` | PnL moved > 5% of position value |
668
- | `margin_warning` | `{ marginUsedPct, equity, marginUsed }` | Margin usage > 80% |
669
-
670
- ### Event Details — Choosing the Right Event
671
-
672
- #### `tick` — The universal heartbeat
673
- 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.
674
-
675
- **Payload:** `{ timestamp: number, pollCount: number }`
676
-
677
- **When to use:**
678
- - Checking if a price is above/below an absolute threshold (e.g. "alert me when ETH < $3000")
679
- - Custom conditions that don't fit other events (e.g. "if I have no positions and funding is high, enter")
680
- - Periodic tasks that need to run every poll (though `api.every()` is better for longer intervals)
681
-
682
- **Example — absolute price alert:**
683
- ```typescript
684
- api.on('tick', async () => {
685
- const mids = await api.client.getAllMids();
686
- const price = parseFloat(mids['HYPE']);
687
- if (price < 38 && !api.state.get('alerted')) {
688
- api.state.set('alerted', true);
689
- await api.publish(`HYPE dropped below $38 — now at $${price.toFixed(3)}`);
690
- }
691
- });
692
- ```
693
-
694
- **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.
695
-
696
- #### `price_change` — Relative price movements
697
- 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.
698
-
699
- **Payload:** `{ coin: string, oldPrice: number, newPrice: number, changePct: number }`
700
-
701
- **When to use:**
702
- - Reacting to price movements (breakouts, momentum, mean reversion)
703
- - Monitoring specific coins for volatility
704
- - Building price-triggered entry/exit logic
705
-
706
- **When NOT to use:**
707
- - 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.
708
-
709
- **Example — momentum detector:**
710
- ```typescript
711
- api.on('price_change', async ({ coin, changePct, newPrice }) => {
712
- if (coin !== 'ETH') return;
713
- if (changePct > 0.5) {
714
- api.log.info(`ETH surging +${changePct.toFixed(2)}% — price $${newPrice}`);
715
- // Enter long on strong upward momentum
716
- }
717
- });
718
- ```
719
-
720
- #### `funding_update` — Funding rate data
721
- 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.
722
-
723
- **Payload:** `{ coin: string, fundingRate: number, annualized: number, premium: number }`
724
- - `fundingRate` — the raw hourly funding rate (e.g. 0.0001 = 0.01%/hr)
725
- - `annualized` — annualized rate (fundingRate × 8760 × 100, as a percentage)
726
- - `premium` — the premium component
727
-
728
- **When to use:**
729
- - Funding rate arbitrage strategies
730
- - Monitoring for extreme funding (entry/exit signals)
731
- - Scanning for highest/lowest funding across all assets
732
-
733
- **Example — funding scalp:**
734
- ```typescript
735
- api.on('funding_update', async ({ coin, annualized }) => {
736
- if (coin !== 'ETH') return;
737
- if (annualized > 50 && !api.state.get('isShort')) {
738
- api.log.info(`ETH funding at ${annualized.toFixed(1)}% annualized — shorting`);
739
- await api.client.marketOrder('ETH', false, 0.1);
740
- api.state.set('isShort', true);
741
- }
742
- });
743
- ```
744
-
745
- #### `position_opened` — New position detected
746
- 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.
747
-
748
- **Payload:** `{ coin: string, side: 'long' | 'short', size: number, entryPrice: number }`
749
-
750
- **When to use:**
751
- - Setting TP/SL on new positions automatically
752
- - Logging/alerting when positions are opened (by you or another system)
753
- - Starting position-specific monitoring
754
-
755
- **Example — auto TP/SL on new positions:**
756
- ```typescript
757
- api.on('position_opened', async ({ coin, side, size, entryPrice }) => {
758
- const tpPrice = side === 'long' ? entryPrice * 1.05 : entryPrice * 0.95;
759
- const slPrice = side === 'long' ? entryPrice * 0.97 : entryPrice * 1.03;
760
- await api.client.takeProfit(coin, side !== 'long', size, tpPrice);
761
- await api.client.stopLoss(coin, side !== 'long', size, slPrice);
762
- api.log.info(`Set TP at ${tpPrice} / SL at ${slPrice} for ${coin}`);
763
- });
764
- ```
765
-
766
- #### `position_closed` — Position gone
767
- 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.
768
-
769
- **Payload:** `{ coin: string, previousSize: number, entryPrice: number }`
770
-
771
- **When to use:**
772
- - Logging/alerting when positions close
773
- - Cleaning up related orders or state
774
- - Re-entry logic after a position closes
775
-
776
- **Example:**
777
- ```typescript
778
- api.on('position_closed', async ({ coin, previousSize, entryPrice }) => {
779
- api.log.info(`${coin} position closed (was ${previousSize} @ ${entryPrice})`);
780
- api.state.delete(`${coin}_tp`);
781
- await api.publish(`Position closed: ${coin} (entry: $${entryPrice})`);
782
- });
783
- ```
784
-
785
- #### `position_changed` — Size or direction changed
786
- 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.
787
-
788
- **Payload:** `{ coin: string, oldSize: number, newSize: number, entryPrice: number }`
789
- - `oldSize`/`newSize` are signed: positive = long, negative = short
790
-
791
- **When to use:**
792
- - Detecting partial closes or position scaling
793
- - Adjusting TP/SL when position size changes
794
- - Tracking DCA entries
795
-
796
- **Example:**
797
- ```typescript
798
- api.on('position_changed', async ({ coin, oldSize, newSize }) => {
799
- if (Math.abs(newSize) > Math.abs(oldSize)) {
800
- api.log.info(`${coin} position increased: ${oldSize} → ${newSize}`);
801
- } else {
802
- api.log.info(`${coin} position reduced: ${oldSize} → ${newSize}`);
803
- }
804
- });
805
- ```
806
-
807
- #### `pnl_threshold` — Significant PnL movement
808
- 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.
809
-
810
- **Payload:** `{ coin: string, unrealizedPnl: number, changePct: number, positionValue: number }`
811
- - `changePct` — the PnL change as a percentage of total position value (not % of PnL itself)
812
-
813
- **When to use:**
814
- - Risk alerts for large PnL swings
815
- - Auto-close or reduce positions on sudden adverse moves
816
- - Escalating alerts to the user via `api.publish()`
817
-
818
- **Example:**
819
- ```typescript
820
- api.on('pnl_threshold', async ({ coin, unrealizedPnl, changePct }) => {
821
- if (unrealizedPnl < 0) {
822
- await api.publish(
823
- `⚠️ ${coin} PnL dropped sharply: $${unrealizedPnl.toFixed(2)} (${changePct.toFixed(1)}% of position)`,
824
- { name: 'pnl-alert' },
825
- );
826
- }
827
- });
828
- ```
829
-
830
- #### `margin_warning` — High margin usage
831
- 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%.
832
-
833
- **Payload:** `{ marginUsedPct: number, equity: number, marginUsed: number }`
834
-
835
- **When to use:**
836
- - Automated risk reduction (close smallest position to free margin)
837
- - Alerting the user before liquidation risk
838
- - Pausing new entries when margin is high
839
-
840
- **Example:**
841
- ```typescript
842
- api.on('margin_warning', async ({ marginUsedPct, equity }) => {
843
- await api.publish(
844
- `🚨 Margin at ${marginUsedPct.toFixed(1)}% — equity: $${equity.toFixed(2)}. Consider reducing exposure.`,
845
- { name: 'margin-alert' },
846
- );
847
- });
848
- ```
849
-
850
- #### `order_update` — Real-time order lifecycle (WebSocket)
851
- Fires instantly when any order changes status: `open`, `filled`, `canceled`, `triggered`, `rejected`, `marginCanceled`, `liquidatedCanceled`, `badAloPxRejected`, and 20+ other statuses. Requires WebSocket (enabled by default).
852
-
853
- **Payload:** `{ coin: string, oid: number, side: 'buy' | 'sell', size: number, price: number, origSize: number, status: string, statusTimestamp: number }`
854
-
855
- **Example:**
856
- ```typescript
857
- api.on('order_update', async ({ coin, oid, status, side, size, price }) => {
858
- if (status === 'filled') {
859
- api.log.info(`Order ${oid} filled: ${side} ${size} ${coin} @ $${price}`);
860
- } else if (status === 'canceled' || status.includes('Rejected')) {
861
- api.log.warn(`Order ${oid} ${status}: ${coin}`);
862
- }
863
- });
864
- ```
865
-
866
- #### `liquidation` — Liquidation alert (WebSocket only)
867
- Fires when the account is liquidated. This event is **only available via WebSocket** — there is no REST polling equivalent.
868
-
869
- **Payload:** `{ lid: number, liquidator: string, liquidatedUser: string, liquidatedNtlPos: number, liquidatedAccountValue: number }`
870
-
871
- **Example:**
872
- ```typescript
873
- api.on('liquidation', async ({ liquidatedNtlPos, liquidatedAccountValue }) => {
874
- await api.publish(
875
- `LIQUIDATED: $${liquidatedNtlPos.toFixed(2)} notional, account value: $${liquidatedAccountValue.toFixed(2)}`,
876
- { name: 'liquidation-alert' },
877
- );
878
- });
879
- ```
880
-
881
- ### WebSocket Real-Time Data
882
-
883
- Automations use **WebSocket by default** for real-time market and account events. The runtime subscribes to:
884
- - **allMids** — price updates for all assets (drives `price_change` events in real-time)
885
- - **orderUpdates** — order lifecycle events (drives `order_update` and `order_filled`)
886
- - **userFills** — trade fill details with PnL and fees
887
- - **userEvents** — liquidation alerts, funding payments, system cancellations
888
-
889
- 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.
890
-
891
- To disable WebSocket (pure REST polling):
892
- ```bash
893
- openbroker auto run my-strategy.ts --no-ws
894
- ```
895
-
896
- ### Choosing the Right Event — Quick Guide
897
-
898
- | Use case | Best event | Why |
899
- |----------|-----------|-----|
900
- | Alert when price crosses a fixed level | `tick` | Fires every poll — no minimum change threshold |
901
- | React to price momentum/volatility | `price_change` | Real-time via WebSocket, provides relative change data |
902
- | Funding rate strategy | `funding_update` | Gives annualized rate directly |
903
- | Auto TP/SL on new positions | `position_opened` | Fires exactly when a new position appears |
904
- | Log when positions close | `position_closed` | Fires when position disappears |
905
- | Track position scaling | `position_changed` | Fires on size changes only |
906
- | Risk management — PnL spikes | `pnl_threshold` | Only fires on large moves (≥5% of position value) |
907
- | Risk management — margin | `margin_warning` | Fires at 80%+ margin usage |
908
- | React instantly to order fills/rejects | `order_update` | Real-time via WebSocket — sub-second latency |
909
- | Liquidation alerts | `liquidation` | WebSocket only — no REST equivalent |
910
- | Periodic task (DCA, rebalance) | `api.every(ms, fn)` | Better than tick for longer intervals |
911
-
912
- ### Client Methods Available
913
-
914
- 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.
915
-
916
- #### Trading
917
-
918
- | Method | Description |
919
- |--------|-------------|
920
- | `marketOrder(coin, isBuy, size, slippageBps?, leverage?)` | Market order via IOC limit at mid ± slippage. Returns `OrderResponse` |
921
- | `limitOrder(coin, isBuy, size, price, tif?, reduceOnly?, leverage?)` | Limit order. `tif`: `'Gtc'` (default), `'Ioc'`, `'Alo'`. Returns `OrderResponse` |
922
- | `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` |
923
- | `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` |
924
- | `takeProfit(coin, isBuy, size, triggerPrice)` | Take profit shortcut. Limit price = trigger price (favorable direction). `reduceOnly` is always true. Returns `OrderResponse` |
925
- | `cancel(coin, oid)` | Cancel a single order by numeric OID. Returns `CancelResponse` |
926
- | `cancelAll(coin?)` | Cancel all open orders. If `coin` is provided, only cancels orders for that asset. Returns `CancelResponse[]` |
927
- | `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` |
928
-
929
- #### Market Data
930
-
931
- | Method | Returns |
932
- |--------|---------|
933
- | `getAllMids()` | `Record<string, string>` — mid prices for all assets (main + HIP-3). Key = coin name, value = price string |
934
- | `getMetaAndAssetCtxs()` | `MetaAndAssetCtxs` — market metadata (universe of assets with `szDecimals`, `maxLeverage`) and asset contexts (funding, open interest, volume, mark/oracle prices) |
935
- | `getL2Book(coin)` | `{ bids, asks, bestBid, bestAsk, midPrice, spread, spreadBps }` — L2 order book with computed spread |
936
- | `getRecentTrades(coin)` | `Array<{ coin, side, px, sz, time, hash, tid }>` — recent trade tape. `side`: `'B'` (buy) or `'A'` (sell) |
937
- | `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 |
938
- | `getFundingHistory(coin, startTime, endTime?)` | `Array<{ coin, fundingRate, premium, time }>` — historical hourly funding rates |
939
- | `getPredictedFundings()` | `Array<[coin, Array<[venue, { fundingRate, nextFundingTime }]>]>` — predicted funding rates across all venues |
940
- | `getPerpDexs()` | `Array<{ name, fullName, deployer } | null>` — list of perp DEXs. Index 0 is `null` (main), rest are HIP-3 |
941
- | `getAllPerpMetas()` | `Array<{ dexName, meta, assetCtxs }>` — metadata + contexts for every perp DEX (main + all HIP-3) |
942
- | `getSpotMeta()` | `{ tokens, universe }` — spot market metadata (token info, trading pairs) |
943
- | `getSpotMetaAndAssetCtxs()` | `{ meta, assetCtxs }` — spot metadata + price/volume contexts |
944
- | `getTokenDetails(tokenId)` | Token details: supply, deployer, prices. Returns `null` if not found |
945
-
946
- #### Account
947
-
948
- | Method | Returns |
949
- |--------|---------|
950
- | `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`) |
951
- | `getUserState(user?, dex?)` | `ClearinghouseState` — account state for a single dex (omit `dex` for main perps) |
952
- | `getOpenOrders(user?)` | `OpenOrder[]` — all open orders across all dexes. Each: `{ coin, side, limitPx, sz, oid, timestamp, orderType }` |
953
- | `getUserFills(user?, aggregateByTime?)` | `Array<{ coin, px, sz, side, time, closedPnl, fee, oid, tid, crossed, builderFee }>` — trade fill history. `side`: `'B'` (buy) or `'A'` (sell) |
954
- | `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.) |
955
- | `getOrderStatus(oid, user?)` | `{ status, order? }` — status of a specific order by numeric OID or string CLOID |
956
- | `getUserFunding(user?, startTime?, endTime?)` | `Array<{ time, hash, delta: { coin, usdc, szi, fundingRate } }>` — funding payments received/paid |
957
- | `getUserFees(user?)` | `{ dailyUserVlm, feeSchedule, userCrossRate, userAddRate, activeReferralDiscount, activeStakingDiscount }` — fee tier, rates, and volume |
958
- | `getUserRateLimit(user?)` | `{ cumVlm, nRequestsUsed, nRequestsCap, nRequestsSurplus }` — API rate limit status |
959
- | `getSpotBalances(user?)` | `{ balances: Array<{ coin, token, hold, total, entryNtl }> }` — spot token balances |
960
- | `getSubAccounts(user?)` | `Array<{ subAccountUser, name }>` — sub-accounts for a master wallet |
961
- | `getAccountMode(user?)` | `string` — account abstraction mode: `'standard'`, `'unified'`, `'portfolio'`, or `'dexAbstraction'` |
962
- | `isUnifiedAccount(user?)` | `boolean` — `true` if unified or portfolio margin (shared USDC across dexes) |
963
-
964
- #### Leverage & Config
965
-
966
- | Method | Description |
967
- |--------|-------------|
968
- | `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 |
969
- | `approveBuilderFee(maxFeeRate?, builder?)` | Approve builder fee (must be called from main wallet, not API wallet). Default rate: `'0.1%'` |
970
- | `getMaxBuilderFee(user?, builder?)` | Check approved builder fee. Returns fee string (e.g. `'0.01%'`) or `null` if not approved |
971
-
972
- #### Utility Properties
973
-
974
- | Property / Method | Description |
975
- |-------------------|-------------|
976
- | `getAssetIndex(coin)` | Get numeric asset index for a coin (used internally for order wire) |
977
- | `getSzDecimals(coin)` | Get size decimal precision for a coin |
978
- | `isHip3(coin)` | Check if a coin is a HIP-3 asset |
979
- | `getCoinDex(coin)` | Get dex name for a coin (`null` for main perps) |
980
- | `getAllAssetNames()` | Get all known asset names (main + HIP-3) |
981
- | `getHip3AssetNames()` | Get only HIP-3 asset names |
982
- | `invalidateMetaCache()` | Force refresh of market metadata on next call |
983
-
984
- #### Utility Functions (`api.utils`)
985
-
986
- | Function | Description |
987
- |----------|-------------|
988
- | `roundPrice(price, szDecimals, isSpot?)` | Round price to 5 significant figures (max 6 decimals perp, 8 spot) |
989
- | `roundSize(size, szDecimals)` | Round size to asset-specific decimal precision |
990
- | `sleep(ms)` | Promise-based delay |
991
- | `normalizeCoin(coin)` | Normalize coin name (uppercase, trim whitespace) |
992
- | `formatUsd(amount)` | Format number as USD string (e.g. `$1,234.56`) |
993
- | `annualizeFundingRate(hourlyRate)` | Convert hourly funding rate to annualized percentage |
994
-
995
- ### Example: Price Breakout
996
-
997
- ```typescript
998
- // ~/.openbroker/automations/breakout.ts
999
- export default function(api) {
1000
- const COIN = 'ETH';
1001
- const BREAKOUT_PCT = 2; // 2% move triggers entry
1002
- const SIZE = 0.5;
1003
- let basePrice = null;
1004
-
1005
- api.onStart(async () => {
1006
- const mids = await api.client.getAllMids();
1007
- basePrice = parseFloat(mids[COIN]);
1008
- api.log.info(`Watching ${COIN} from $${basePrice} for ${BREAKOUT_PCT}% breakout`);
1009
- });
1010
-
1011
- api.on('price_change', async ({ coin, newPrice }) => {
1012
- if (coin !== COIN || !basePrice) return;
1013
- const totalChange = ((newPrice - basePrice) / basePrice) * 100;
1014
-
1015
- if (Math.abs(totalChange) >= BREAKOUT_PCT && !api.state.get('inPosition')) {
1016
- const side = totalChange > 0; // true = long, false = short
1017
- api.log.info(`Breakout! ${totalChange.toFixed(2)}% — entering ${side ? 'long' : 'short'}`);
1018
- await api.client.marketOrder(COIN, side, SIZE);
1019
- api.state.set('inPosition', true);
1020
- }
1021
- });
1022
- }
1023
- ```
1024
-
1025
- ### Example: Scheduled DCA
1026
-
1027
- ```typescript
1028
- // ~/.openbroker/automations/hourly-dca.ts
1029
- export default function(api) {
1030
- const COIN = 'ETH';
1031
- const USD_PER_BUY = 100;
1032
-
1033
- // Buy $100 of ETH every hour
1034
- api.every(60 * 60 * 1000, async () => {
1035
- const mids = await api.client.getAllMids();
1036
- const price = parseFloat(mids[COIN]);
1037
- const size = parseFloat(api.utils.roundSize(USD_PER_BUY / price, 4));
1038
- await api.client.marketOrder(COIN, true, size);
1039
- const count = (api.state.get('buyCount') || 0) + 1;
1040
- api.state.set('buyCount', count);
1041
- api.log.info(`DCA #${count}: bought ${size} ${COIN} at $${price}`);
1042
- });
1043
- }
1044
- ```
1045
-
1046
- ### Example: Margin Guardian
1047
-
1048
- ```typescript
1049
- // ~/.openbroker/automations/margin-guard.ts
1050
- export default function(api) {
1051
- api.on('margin_warning', async ({ marginUsedPct, equity }) => {
1052
- api.log.warn(`Margin at ${marginUsedPct.toFixed(1)}% — reducing positions`);
1053
-
1054
- // Close the smallest position to free margin
1055
- const state = await api.client.getUserStateAll();
1056
- const positions = state.assetPositions
1057
- .filter(p => parseFloat(p.position.szi) !== 0)
1058
- .sort((a, b) => Math.abs(parseFloat(a.position.positionValue)) - Math.abs(parseFloat(b.position.positionValue)));
1059
-
1060
- if (positions.length > 0) {
1061
- const pos = positions[0].position;
1062
- const size = Math.abs(parseFloat(pos.szi));
1063
- const isBuy = parseFloat(pos.szi) < 0; // Close short = buy, close long = sell
1064
- api.log.info(`Closing smallest position: ${pos.coin} (${pos.szi})`);
1065
- await api.client.marketOrder(pos.coin, isBuy, size);
1066
- }
1067
- });
1068
- }
1069
- ```
1070
-
1071
- ### Publishing to the Agent (Webhooks)
1072
-
1073
- 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.
1074
-
1075
- ```typescript
1076
- // Simple notification
1077
- await api.publish(`ETH broke above $4000 — current price: $${price}`);
1078
-
1079
- // With options
1080
- await api.publish(`Margin at ${pct}% — positions at risk`, {
1081
- name: 'margin-alert', // appears in logs
1082
- wakeMode: 'now', // 'now' (default) or 'next-heartbeat'
1083
- channel: 'slack', // target channel (optional)
1084
- });
1085
- ```
1086
-
1087
- `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).
1088
-
1089
- **Example: Price alert automation with publish**
1090
- ```typescript
1091
- // ~/.openbroker/automations/price-alert.ts
1092
- export default function(api) {
1093
- const COIN = 'ETH';
1094
- const THRESHOLD = 4000;
1095
-
1096
- api.on('price_change', async ({ coin, newPrice, changePct }) => {
1097
- if (coin !== COIN) return;
1098
-
1099
- const crossed = api.state.get<boolean>('crossed', false);
1100
- if (!crossed && newPrice >= THRESHOLD) {
1101
- api.state.set('crossed', true);
1102
- await api.publish(
1103
- `${COIN} crossed above $${THRESHOLD}! Price: $${newPrice.toFixed(2)} (+${changePct.toFixed(2)}%)`,
1104
- );
1105
- } else if (crossed && newPrice < THRESHOLD) {
1106
- api.state.set('crossed', false);
1107
- }
1108
- });
1109
- }
1110
- ```
1111
-
1112
- ### Running Automations
1113
-
1114
- **CLI:**
1115
- ```bash
1116
- openbroker auto run my-strategy --dry # Test without trading
1117
- openbroker auto run ./funding-scalp.ts # Run from path
1118
- openbroker auto run my-strategy --poll 5000 # Poll every 5s
1119
- openbroker auto run my-strategy --no-ws # Disable WebSocket, pure REST polling
1120
- openbroker auto run --example dca --set coin=HYPE --set amount=50 --dry # Run bundled example
1121
- openbroker auto examples # List bundled examples with config
1122
- openbroker auto list # Show available scripts
1123
- openbroker auto status # Show running automations
1124
- openbroker auto stop <id> # Unregister an automation (won't auto-restart)
1125
- openbroker auto report <id> # Read the local audit report (logs, trades, metrics) for an automation
1126
- openbroker auto clean # Remove stale entries from the registry
1127
- ```
1128
-
1129
- **Plugin tools (for OpenClaw agents):**
1130
- - `ob_auto_run` — `{ "script": "funding-scalp", "dry": true }` — start an automation
1131
- - `ob_auto_run` — `{ "example": "dca", "config": { "coin": "HYPE", "amount": 50 }, "dry": true }` — run a bundled example
1132
- - `ob_auto_stop` — `{ "id": "funding-scalp" }` — stop a running automation
1133
- - `ob_auto_list` — `{}` — list available automations, bundled examples with config schemas, and running automations
1134
-
1135
- **Options:**
1136
- | Flag | Description | Default |
1137
- |------|-------------|---------|
1138
- | `--dry` | Intercept write methods — no real trades | false |
1139
- | `--verbose` | Show debug output | false |
1140
- | `--id <name>` | Custom automation ID | filename |
1141
- | `--poll <ms>` | Poll interval in milliseconds | 10000 |
1142
- | `--no-ws` | Disable WebSocket; fall back to REST-only polling | WebSocket on |
1143
- | `--example <name>` | Run a bundled example automation | - |
1144
- | `--set key=value` | Set config values (repeatable) | - |
1145
-
1146
- **Inspecting automations after they run:**
1147
- - `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.
1148
- - `openbroker auto clean` — prunes registry entries for automations that are no longer running or whose script file is gone. Safe to run anytime.
1149
-
1150
- **Guidelines for agents writing automations:**
1151
-
1152
- **Risk & Safety (mandatory):**
1153
- - 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.
1154
- - 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.
1155
- - 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.
1156
- - Do NOT use `--dry` unless the user explicitly asks for it. Automations should run live by default.
1157
- - Never place trades without validating that sufficient margin is available. Check account state before sizing orders.
1158
- - 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.
1159
- - Always set TP/SL on new positions — either within the automation or by confirming the user has them set. Unprotected positions are a liability.
1160
-
1161
- **State & Reliability:**
1162
- - 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.
1163
- - 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.
1164
- - 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.
1165
-
1166
- **Communication:**
1167
- - Use `api.publish()` to send alerts/events back to the OpenClaw agent — do NOT manually construct webhook requests.
1168
- - 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.
1169
- - Include actionable context in publish messages — coin, price, size, PnL, and what happened — so the user can make informed decisions without checking the terminal.
1170
-
1171
- **General:**
1172
- - Scripts are loaded from `~/.openbroker/automations/` by name, or from any absolute path.
1173
- - All trading commands support HIP-3 assets (`api.client.marketOrder('xyz:CL', true, 1)`).
1174
- - Automations persist across gateway restarts — they are automatically restarted when the gateway comes back up.
1175
- - Prefer `api.every(ms, fn)` over `tick` for periodic tasks with intervals longer than the poll cycle.
1176
-
1177
- ## Risk Warning
1178
-
1179
- - Always use `--dry` first to preview orders
1180
- - Start with small sizes on testnet (`HYPERLIQUID_NETWORK=testnet`)
1181
- - Monitor positions and liquidation prices
1182
- - Use `--reduce` for closing positions only