openbroker 1.0.82 → 1.0.87
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 +79 -7
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/auto/cli.ts +1 -0
- package/scripts/auto/dashboard-forwarder.ts +77 -0
- package/scripts/auto/loader.ts +4 -2
- package/scripts/auto/report.ts +2 -2
- package/scripts/auto/runtime.ts +42 -14
- package/scripts/auto/types.ts +17 -1
- package/scripts/core/client.ts +167 -23
- package/scripts/core/config.ts +1 -1
- package/scripts/core/types.ts +13 -4
- package/scripts/info/all-markets.ts +51 -24
- package/scripts/info/candles.ts +13 -2
- package/scripts/info/fees.ts +16 -5
- package/scripts/info/funding-history.ts +13 -2
- package/scripts/info/funding-scan.ts +6 -0
- package/scripts/info/funding.ts +7 -1
- package/scripts/info/markets.ts +5 -0
- package/scripts/info/order-status.ts +11 -2
- package/scripts/info/rate-limit.ts +11 -2
- package/scripts/info/search-markets.ts +30 -10
- package/scripts/info/spot.ts +25 -5
- package/scripts/info/trades.ts +13 -2
- package/scripts/operations/limit-order.ts +1 -1
- package/scripts/operations/twap-cancel.ts +4 -2
- package/scripts/operations/twap.ts +8 -10
- package/scripts/plugin/tools.ts +15 -19
- package/scripts/setup/onboard.ts +8 -2
package/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Hyperliquid trading plugin with background position monitoring and
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
|
|
6
6
|
homepage: https://www.npmjs.com/package/openbroker
|
|
7
|
-
metadata: {"author": "monemetrics", "version": "1.0.
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "1.0.87", "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
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
9
|
---
|
|
10
10
|
|
|
@@ -46,28 +46,52 @@ Or with the `ob_search` plugin tool: `{ "query": "gold" }` or `{ "query": "oil",
|
|
|
46
46
|
|
|
47
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
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
|
+
|
|
49
65
|
## Troubleshooting: CLI Fallback
|
|
50
66
|
|
|
51
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.
|
|
52
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
|
+
|
|
53
71
|
| Plugin Tool | CLI Equivalent |
|
|
54
72
|
|-------------|---------------|
|
|
55
73
|
| `ob_account` | `openbroker account --json` |
|
|
56
74
|
| `ob_positions` | `openbroker positions --json` |
|
|
57
75
|
| `ob_funding` | `openbroker funding --json --include-hip3` |
|
|
58
76
|
| `ob_markets` | `openbroker markets --json --include-hip3` |
|
|
59
|
-
| `ob_search` | `openbroker search --query <QUERY
|
|
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` |
|
|
60
88
|
| `ob_buy` | `openbroker buy --coin <COIN> --size <SIZE>` |
|
|
61
89
|
| `ob_sell` | `openbroker sell --coin <COIN> --size <SIZE>` |
|
|
62
90
|
| `ob_limit` | `openbroker limit --coin <COIN> --side <SIDE> --size <SIZE> --price <PRICE>` |
|
|
63
91
|
| `ob_tpsl` | `openbroker tpsl --coin <COIN> --tp <PRICE> --sl <PRICE>` |
|
|
64
92
|
| `ob_cancel` | `openbroker cancel --all` or `--coin <COIN>` |
|
|
65
|
-
| `ob_fills` | `openbroker fills --json` |
|
|
66
|
-
| `ob_orders` | `openbroker orders --json` |
|
|
67
|
-
| `ob_funding_scan` | `openbroker funding-scan --json` |
|
|
68
|
-
| `ob_candles` | `openbroker candles --coin <COIN> --json` |
|
|
69
93
|
| `ob_auto_run` | `openbroker auto run <script> [--dry]` |
|
|
70
|
-
| `ob_auto_stop` |
|
|
94
|
+
| `ob_auto_stop` | `openbroker auto stop <id>` (or SIGINT if run in foreground) |
|
|
71
95
|
| `ob_auto_list` | `openbroker auto list` |
|
|
72
96
|
|
|
73
97
|
**When to use CLI fallback:**
|
|
@@ -137,12 +161,14 @@ openbroker positions --address 0xabc... # Another account's positions
|
|
|
137
161
|
```bash
|
|
138
162
|
openbroker funding --top 20 # Top 20 by funding rate
|
|
139
163
|
openbroker funding --coin ETH # Specific coin
|
|
164
|
+
openbroker funding --top 20 --json # JSON (includes assetId)
|
|
140
165
|
```
|
|
141
166
|
|
|
142
167
|
### Markets
|
|
143
168
|
```bash
|
|
144
169
|
openbroker markets --top 30 # Top 30 main perps
|
|
145
170
|
openbroker markets --coin BTC # Specific coin
|
|
171
|
+
openbroker markets --coin BTC --json # JSON (includes assetId)
|
|
146
172
|
```
|
|
147
173
|
|
|
148
174
|
### All Markets (Perps + Spot + HIP-3)
|
|
@@ -152,6 +178,7 @@ openbroker all-markets --type perp # Main perps only
|
|
|
152
178
|
openbroker all-markets --type hip3 # HIP-3 perps only
|
|
153
179
|
openbroker all-markets --type spot # Spot markets only
|
|
154
180
|
openbroker all-markets --top 20 # Top 20 by volume
|
|
181
|
+
openbroker all-markets --json # JSON (includes assetId)
|
|
155
182
|
```
|
|
156
183
|
|
|
157
184
|
### Search Markets (Find assets across providers)
|
|
@@ -159,6 +186,7 @@ openbroker all-markets --top 20 # Top 20 by volume
|
|
|
159
186
|
openbroker search --query GOLD # Find all GOLD markets
|
|
160
187
|
openbroker search --query BTC # Find BTC across all providers
|
|
161
188
|
openbroker search --query ETH --type perp # ETH perps only
|
|
189
|
+
openbroker search HYPE --json # JSON with assetId per result
|
|
162
190
|
```
|
|
163
191
|
|
|
164
192
|
### Spot Markets
|
|
@@ -168,6 +196,7 @@ openbroker spot --coin PURR # Show PURR market info
|
|
|
168
196
|
openbroker spot --balances # Show your spot balances
|
|
169
197
|
openbroker spot --balances --address 0xabc... # Another account's spot balances
|
|
170
198
|
openbroker spot --top 20 # Top 20 by volume
|
|
199
|
+
openbroker spot --json # JSON (includes assetId, base, quote)
|
|
171
200
|
```
|
|
172
201
|
|
|
173
202
|
### Trade Fills
|
|
@@ -193,12 +222,14 @@ openbroker orders --address 0xabc... --open # Another account's open orders
|
|
|
193
222
|
openbroker order-status --oid 123456789 # Check specific order
|
|
194
223
|
openbroker order-status --oid 0x1234... # By client order ID
|
|
195
224
|
openbroker order-status --oid 123456789 --address 0xabc... # On another account
|
|
225
|
+
openbroker order-status --oid 123456789 --json
|
|
196
226
|
```
|
|
197
227
|
|
|
198
228
|
### Fee Schedule
|
|
199
229
|
```bash
|
|
200
230
|
openbroker fees # Fee tier, rates, and volume
|
|
201
231
|
openbroker fees --address 0xabc... # Another account's fees
|
|
232
|
+
openbroker fees --json
|
|
202
233
|
```
|
|
203
234
|
|
|
204
235
|
### Candle Data (OHLCV)
|
|
@@ -206,23 +237,27 @@ openbroker fees --address 0xabc... # Another account's fees
|
|
|
206
237
|
openbroker candles --coin ETH # 24 hourly candles
|
|
207
238
|
openbroker candles --coin BTC --interval 4h --bars 48 # 48 four-hour bars
|
|
208
239
|
openbroker candles --coin SOL --interval 1d --bars 30 # 30 daily bars
|
|
240
|
+
openbroker candles --coin ETH --json # JSON (coin, assetId, interval, candles)
|
|
209
241
|
```
|
|
210
242
|
|
|
211
243
|
### Funding History
|
|
212
244
|
```bash
|
|
213
245
|
openbroker funding-history --coin ETH # Last 24h
|
|
214
246
|
openbroker funding-history --coin BTC --hours 168 # Last 7 days
|
|
247
|
+
openbroker funding-history --coin ETH --json # JSON (coin, assetId, history)
|
|
215
248
|
```
|
|
216
249
|
|
|
217
250
|
### Recent Trades (Tape)
|
|
218
251
|
```bash
|
|
219
252
|
openbroker trades --coin ETH # Last 30 trades
|
|
220
253
|
openbroker trades --coin BTC --top 50 # Last 50 trades
|
|
254
|
+
openbroker trades --coin ETH --json # JSON (coin, assetId, trades)
|
|
221
255
|
```
|
|
222
256
|
|
|
223
257
|
### Rate Limit
|
|
224
258
|
```bash
|
|
225
259
|
openbroker rate-limit # API usage and capacity
|
|
260
|
+
openbroker rate-limit --json
|
|
226
261
|
```
|
|
227
262
|
|
|
228
263
|
### Funding Rate Scanner (Cross-Dex)
|
|
@@ -231,6 +266,7 @@ openbroker funding-scan # Scan all dexes, >25% threshol
|
|
|
231
266
|
openbroker funding-scan --threshold 50 --pairs # Show opposing funding pairs
|
|
232
267
|
openbroker funding-scan --hip3-only --top 20 # HIP-3 only
|
|
233
268
|
openbroker funding-scan --watch --interval 120 # Re-scan every 2 minutes
|
|
269
|
+
openbroker funding-scan --json # JSON (includes assetId per result)
|
|
234
270
|
```
|
|
235
271
|
|
|
236
272
|
## Trading Commands
|
|
@@ -293,6 +329,27 @@ openbroker cancel --coin ETH # Cancel ETH orders only
|
|
|
293
329
|
openbroker cancel --oid 123456 # Cancel specific order
|
|
294
330
|
```
|
|
295
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
|
+
|
|
296
353
|
## Advanced Execution
|
|
297
354
|
|
|
298
355
|
### TWAP (Native Exchange Order)
|
|
@@ -303,6 +360,9 @@ openbroker twap --coin ETH --side buy --size 1 --duration 30
|
|
|
303
360
|
# Sell 0.5 BTC over 2 hours without randomized timing
|
|
304
361
|
openbroker twap --coin BTC --side sell --size 0.5 --duration 120 --randomize false
|
|
305
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
|
+
|
|
306
366
|
# Cancel a running TWAP
|
|
307
367
|
openbroker twap-cancel --coin ETH --twap-id 77738308
|
|
308
368
|
|
|
@@ -407,6 +467,7 @@ Run `openbroker setup` to create the global config interactively.
|
|
|
407
467
|
| `HYPERLIQUID_PRIVATE_KEY` | Yes | Wallet private key (0x...) |
|
|
408
468
|
| `HYPERLIQUID_NETWORK` | No | `mainnet` (default) or `testnet` |
|
|
409
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`) |
|
|
410
471
|
|
|
411
472
|
The builder fee (1 bps / 0.01%) is hardcoded and not configurable.
|
|
412
473
|
|
|
@@ -591,6 +652,8 @@ export default function(api) {
|
|
|
591
652
|
|
|
592
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.
|
|
593
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
|
+
|
|
594
657
|
### Events
|
|
595
658
|
|
|
596
659
|
| Event | Payload | When |
|
|
@@ -1053,10 +1116,14 @@ export default function(api) {
|
|
|
1053
1116
|
openbroker auto run my-strategy --dry # Test without trading
|
|
1054
1117
|
openbroker auto run ./funding-scalp.ts # Run from path
|
|
1055
1118
|
openbroker auto run my-strategy --poll 5000 # Poll every 5s
|
|
1119
|
+
openbroker auto run my-strategy --no-ws # Disable WebSocket, pure REST polling
|
|
1056
1120
|
openbroker auto run --example dca --set coin=HYPE --set amount=50 --dry # Run bundled example
|
|
1057
1121
|
openbroker auto examples # List bundled examples with config
|
|
1058
1122
|
openbroker auto list # Show available scripts
|
|
1059
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
|
|
1060
1127
|
```
|
|
1061
1128
|
|
|
1062
1129
|
**Plugin tools (for OpenClaw agents):**
|
|
@@ -1072,9 +1139,14 @@ openbroker auto status # Show running automations
|
|
|
1072
1139
|
| `--verbose` | Show debug output | false |
|
|
1073
1140
|
| `--id <name>` | Custom automation ID | filename |
|
|
1074
1141
|
| `--poll <ms>` | Poll interval in milliseconds | 10000 |
|
|
1142
|
+
| `--no-ws` | Disable WebSocket; fall back to REST-only polling | WebSocket on |
|
|
1075
1143
|
| `--example <name>` | Run a bundled example automation | - |
|
|
1076
1144
|
| `--set key=value` | Set config values (repeatable) | - |
|
|
1077
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
|
+
|
|
1078
1150
|
**Guidelines for agents writing automations:**
|
|
1079
1151
|
|
|
1080
1152
|
**Risk & Safety (mandatory):**
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/scripts/auto/cli.ts
CHANGED
|
@@ -32,6 +32,7 @@ Options (for run):
|
|
|
32
32
|
--verbose Show debug output
|
|
33
33
|
--id <name> Custom automation ID (default: filename)
|
|
34
34
|
--poll <ms> Poll interval in milliseconds (default: 10000)
|
|
35
|
+
--no-ws Disable WebSocket; fall back to REST-only polling
|
|
35
36
|
|
|
36
37
|
Scripts are loaded from:
|
|
37
38
|
1. Absolute or relative path
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard audit forwarder.
|
|
3
|
+
*
|
|
4
|
+
* When OB_DASHBOARD_URL is set, wraps the AutomationAudit API to also POST
|
|
5
|
+
* audit notes, metrics, and agent action logs to the ob-app backend.
|
|
6
|
+
*
|
|
7
|
+
* Fires HTTP requests in the background — never blocks the automation loop.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { AutomationAudit } from './types.js';
|
|
11
|
+
|
|
12
|
+
const DASHBOARD_URL = process.env.OB_DASHBOARD_URL; // e.g. "http://localhost:3001"
|
|
13
|
+
const DASHBOARD_API_KEY = process.env.OB_DASHBOARD_API_KEY || '';
|
|
14
|
+
const VAULT_ADDRESS = process.env.HYPERSTABLE_VAULT_ADDRESS || process.env.VAULT || '';
|
|
15
|
+
|
|
16
|
+
function postJSON(path: string, body: unknown): void {
|
|
17
|
+
if (!DASHBOARD_URL || !VAULT_ADDRESS) return;
|
|
18
|
+
|
|
19
|
+
const url = `${DASHBOARD_URL}/api/vaults/${VAULT_ADDRESS.toLowerCase()}${path}`;
|
|
20
|
+
|
|
21
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
22
|
+
if (DASHBOARD_API_KEY) {
|
|
23
|
+
headers['Authorization'] = `Bearer ${DASHBOARD_API_KEY}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers,
|
|
29
|
+
body: JSON.stringify(body),
|
|
30
|
+
signal: AbortSignal.timeout(5_000),
|
|
31
|
+
}).catch(() => {
|
|
32
|
+
// Silently ignore — dashboard may be down, automation must not be affected.
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Wrap an existing AutomationAudit to forward calls to the dashboard API.
|
|
38
|
+
* If OB_DASHBOARD_URL is not set, returns the original audit object unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export function withDashboardForwarder(audit: AutomationAudit): AutomationAudit {
|
|
41
|
+
if (!DASHBOARD_URL || !VAULT_ADDRESS) return audit;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
record(kind: string, payload?: unknown): void {
|
|
45
|
+
audit.record(kind, payload);
|
|
46
|
+
postJSON('/audit/notes', {
|
|
47
|
+
category: kind,
|
|
48
|
+
label: typeof payload === 'object' && payload !== null && 'reason' in payload
|
|
49
|
+
? String((payload as Record<string, unknown>).reason)
|
|
50
|
+
: kind,
|
|
51
|
+
data: payload ?? {},
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
metric(name: string, value: number, tags?: Record<string, unknown>): void {
|
|
56
|
+
audit.metric(name, value, tags);
|
|
57
|
+
postJSON('/audit/metrics', {
|
|
58
|
+
name,
|
|
59
|
+
value,
|
|
60
|
+
tags: tags ?? {},
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Forward an agent action log to the dashboard.
|
|
68
|
+
* Call this from audited client wrappers or directly from automation code.
|
|
69
|
+
*/
|
|
70
|
+
export function forwardAgentAction(
|
|
71
|
+
action: string,
|
|
72
|
+
status: 'success' | 'error' | 'pending',
|
|
73
|
+
details: Record<string, unknown>,
|
|
74
|
+
txHash?: string,
|
|
75
|
+
): void {
|
|
76
|
+
postJSON('/agent/logs', { action, status, details, txHash });
|
|
77
|
+
}
|
package/scripts/auto/loader.ts
CHANGED
|
@@ -22,8 +22,10 @@ export function resolveScriptPath(nameOrPath: string): string {
|
|
|
22
22
|
return nameOrPath;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// Relative to cwd
|
|
26
|
-
|
|
25
|
+
// Relative to the user's original cwd (not the openbroker package root that
|
|
26
|
+
// `bin/openbroker.js` chdirs to before spawning tsx).
|
|
27
|
+
const userCwd = process.env.OPENBROKER_CWD || process.cwd();
|
|
28
|
+
const cwdPath = path.resolve(userCwd, nameOrPath);
|
|
27
29
|
if (existsSync(cwdPath)) return cwdPath;
|
|
28
30
|
|
|
29
31
|
// Relative to ~/.openbroker/automations/
|
package/scripts/auto/report.ts
CHANGED
|
@@ -338,12 +338,12 @@ function renderTextReport(data: ReturnType<typeof loadReport>, watchMode = false
|
|
|
338
338
|
console.log('\nEquity');
|
|
339
339
|
console.log('------');
|
|
340
340
|
if (report.equity.first) {
|
|
341
|
-
console.log(`First snapshot: ${formatUsd(report.equity.first.equity)} @ ${formatTimestamp(Number(report.equity.first.timestamp))}`);
|
|
341
|
+
console.log(`First snapshot: ${formatUsd(Number(report.equity.first.equity))} @ ${formatTimestamp(Number(report.equity.first.timestamp))}`);
|
|
342
342
|
} else {
|
|
343
343
|
console.log('First snapshot: -');
|
|
344
344
|
}
|
|
345
345
|
if (report.equity.latest) {
|
|
346
|
-
console.log(`Latest snapshot:${formatUsd(report.equity.latest.equity)} @ ${formatTimestamp(Number(report.equity.latest.timestamp))}`);
|
|
346
|
+
console.log(`Latest snapshot:${formatUsd(Number(report.equity.latest.equity))} @ ${formatTimestamp(Number(report.equity.latest.timestamp))}`);
|
|
347
347
|
} else {
|
|
348
348
|
console.log('Latest snapshot:-');
|
|
349
349
|
}
|
package/scripts/auto/runtime.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { AutomationEventBus } from './events.js';
|
|
|
15
15
|
import { loadAutomation } from './loader.js';
|
|
16
16
|
import { registerAutomation, unregisterAutomation, getRegisteredAutomations as getRegisteredFromFile } from './registry.js';
|
|
17
17
|
import { createAutomationAudit, toSerializable, type AutomationAuditSink } from './audit.js';
|
|
18
|
+
import { withDashboardForwarder, forwardAgentAction } from './dashboard-forwarder.js';
|
|
18
19
|
import type {
|
|
19
20
|
AutomationAPI,
|
|
20
21
|
AutomationEventPayloads,
|
|
@@ -178,6 +179,11 @@ function createAuditedClient(
|
|
|
178
179
|
result,
|
|
179
180
|
dryRun,
|
|
180
181
|
});
|
|
182
|
+
forwardAgentAction(
|
|
183
|
+
prop,
|
|
184
|
+
'success',
|
|
185
|
+
{ args: toSerializable(args), result: toSerializable(result), dryRun },
|
|
186
|
+
);
|
|
181
187
|
return result;
|
|
182
188
|
} catch (error) {
|
|
183
189
|
audit.recordAction({
|
|
@@ -187,6 +193,11 @@ function createAuditedClient(
|
|
|
187
193
|
error,
|
|
188
194
|
dryRun,
|
|
189
195
|
});
|
|
196
|
+
forwardAgentAction(
|
|
197
|
+
prop,
|
|
198
|
+
'error',
|
|
199
|
+
{ args: toSerializable(args), error: String(error), dryRun },
|
|
200
|
+
);
|
|
190
201
|
throw error;
|
|
191
202
|
}
|
|
192
203
|
};
|
|
@@ -424,10 +435,10 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
424
435
|
|
|
425
436
|
// Build the API object
|
|
426
437
|
const publish = createAuditedPublish(createPublish(id, log, gatewayPort, hooksToken), audit);
|
|
427
|
-
const auditApi: AutomationAudit = {
|
|
438
|
+
const auditApi: AutomationAudit = withDashboardForwarder({
|
|
428
439
|
record: (kind: string, payload?: unknown) => audit.recordNote(kind, payload),
|
|
429
440
|
metric: (name: string, value: number, tags?: Record<string, unknown>) => audit.recordMetric(name, value, tags),
|
|
430
|
-
};
|
|
441
|
+
});
|
|
431
442
|
const api: AutomationAPI = {
|
|
432
443
|
client,
|
|
433
444
|
utils: { roundPrice, roundSize, sleep, normalizeCoin, formatUsd, formatPercent, annualizeFundingRate },
|
|
@@ -558,21 +569,19 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
558
569
|
}, 'ws');
|
|
559
570
|
}
|
|
560
571
|
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
572
|
+
// NOTE: order_filled is emitted from the userFill handler below, not from
|
|
573
|
+
// here. The previous implementation fired it from orderUpdate.status
|
|
574
|
+
// === 'filled' using update.order.sz as the size, but that field is the
|
|
575
|
+
// REMAINING size (0 on a terminal fill), not the fill delta — so every
|
|
576
|
+
// consumer saw size=0. Additionally, Hyperliquid does not emit
|
|
577
|
+
// orderUpdate events for pure partial fills that don't transition
|
|
578
|
+
// status, so partial fills were silently dropped entirely. Sourcing
|
|
579
|
+
// order_filled from userFill fixes both issues: sz there IS the fill
|
|
580
|
+
// delta, and the userFills stream fires on every fill (partial and
|
|
581
|
+
// terminal).
|
|
571
582
|
});
|
|
572
583
|
|
|
573
584
|
ws.on('userFill', (fill) => {
|
|
574
|
-
// userFill events are already covered by order_update with status=filled
|
|
575
|
-
// But this provides the realized PnL and fee data that order_update doesn't have
|
|
576
585
|
audit.recordFill({
|
|
577
586
|
coin: fill.coin,
|
|
578
587
|
side: fill.side === 'B' ? 'buy' : 'sell',
|
|
@@ -585,6 +594,25 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
585
594
|
crossed: fill.crossed,
|
|
586
595
|
}, fill.time);
|
|
587
596
|
log.debug(`Fill: ${fill.side === 'B' ? 'BUY' : 'SELL'} ${fill.sz} ${fill.coin} @ ${fill.px} (PnL: ${fill.closedPnl})`);
|
|
597
|
+
|
|
598
|
+
// Emit order_filled with the authoritative fill delta + fee/pnl from
|
|
599
|
+
// the userFills WS stream. Covers both partial and terminal fills.
|
|
600
|
+
if (eventBus.has('order_filled')) {
|
|
601
|
+
const size = parseFloat(fill.sz);
|
|
602
|
+
const price = parseFloat(fill.px);
|
|
603
|
+
const fee = parseFloat(fill.fee);
|
|
604
|
+
const closedPnl = parseFloat(fill.closedPnl);
|
|
605
|
+
void emitAutomationEvent('order_filled', {
|
|
606
|
+
coin: fill.coin,
|
|
607
|
+
oid: fill.oid,
|
|
608
|
+
side: fill.side === 'B' ? 'buy' : 'sell',
|
|
609
|
+
size,
|
|
610
|
+
price,
|
|
611
|
+
fee: Number.isFinite(fee) ? fee : undefined,
|
|
612
|
+
closedPnl: Number.isFinite(closedPnl) ? closedPnl : undefined,
|
|
613
|
+
crossed: fill.crossed,
|
|
614
|
+
}, 'ws');
|
|
615
|
+
}
|
|
588
616
|
});
|
|
589
617
|
|
|
590
618
|
ws.on('userEvent', (event) => {
|
package/scripts/auto/types.ts
CHANGED
|
@@ -46,7 +46,23 @@ export interface AutomationEventPayloads {
|
|
|
46
46
|
position_changed: { coin: string; oldSize: number; newSize: number; entryPrice: number };
|
|
47
47
|
pnl_threshold: { coin: string; unrealizedPnl: number; changePct: number; positionValue: number };
|
|
48
48
|
margin_warning: { marginUsedPct: number; equity: number; marginUsed: number };
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Fires on every trade fill — partial and terminal — sourced from the
|
|
51
|
+
* Hyperliquid `userFills` WS stream. `size` is the fill delta (NOT remaining
|
|
52
|
+
* size of the order). `fee` and `closedPnl` are in USD; `crossed` is true
|
|
53
|
+
* when this side was the taker. Fee/pnl/crossed are optional so that older
|
|
54
|
+
* consumers that only read coin/oid/side/size/price keep working.
|
|
55
|
+
*/
|
|
56
|
+
order_filled: {
|
|
57
|
+
coin: string;
|
|
58
|
+
oid: number;
|
|
59
|
+
side: 'buy' | 'sell';
|
|
60
|
+
size: number;
|
|
61
|
+
price: number;
|
|
62
|
+
fee?: number;
|
|
63
|
+
closedPnl?: number;
|
|
64
|
+
crossed?: boolean;
|
|
65
|
+
};
|
|
50
66
|
/** Real-time order lifecycle event via WebSocket (filled, canceled, rejected, triggered, etc.) */
|
|
51
67
|
order_update: {
|
|
52
68
|
coin: string;
|