openbroker 1.3.0 → 1.3.2
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 +267 -0
- package/package.json +2 -2
- package/scripts/auto/cli.ts +3 -0
- package/scripts/auto/keep-awake.ts +100 -0
- package/scripts/auto/runtime.ts +17 -0
- package/scripts/core/client.ts +13 -3
- package/scripts/info/all-markets.ts +18 -2
- package/scripts/info/search-markets.ts +18 -2
package/SKILL.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: openbroker
|
|
3
|
+
description: Hyperliquid trading CLI skill for agents. Use when an agent needs to inspect markets/accounts, place or manage perp/spot/HIP-4 orders, or write and run Hyperliquid trading automations directly through the `openbroker` CLI without requiring the OpenClaw plugin.
|
|
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.3.2"}
|
|
8
|
+
allowed-tools: Bash(openbroker:*)
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# OpenBroker — Hyperliquid CLI skill
|
|
12
|
+
|
|
13
|
+
OpenBroker is first a CLI. The OpenClaw plugin is optional: use `ob_*` tools when present for common structured calls, but keep the CLI model in mind because it is the complete surface area and the safest fallback.
|
|
14
|
+
|
|
15
|
+
## Operating rules
|
|
16
|
+
|
|
17
|
+
- For unfamiliar assets, **search before trading**. Hyperliquid has main perps, HIP-3 perps, spot markets, and HIP-4 outcomes that can share names.
|
|
18
|
+
- Prefer `--json` for machine-readable info commands.
|
|
19
|
+
- Before any write, verify the asset, account, open positions/orders, size, and whether the action should be reduce-only.
|
|
20
|
+
- For new or changed trading logic, start with `--dry`, inspect the plan/audit trail, then go live only when that matches the user’s intent.
|
|
21
|
+
- Treat CLI output as exchange state, not just prose: parse order IDs, balances, fills, and errors instead of assuming success.
|
|
22
|
+
|
|
23
|
+
## Setup and identity
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g openbroker
|
|
27
|
+
openbroker setup
|
|
28
|
+
openbroker account --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`setup` supports:
|
|
32
|
+
|
|
33
|
+
1. **Fresh wallet** — simplest for agents; builder fee approval is handled automatically.
|
|
34
|
+
2. **Imported key** — use an existing wallet.
|
|
35
|
+
3. **API wallet** — can trade but not withdraw; the human owner must approve it in a browser.
|
|
36
|
+
|
|
37
|
+
For API wallets, `HYPERLIQUID_PRIVATE_KEY` is the signing key and `HYPERLIQUID_ACCOUNT_ADDRESS` must be the funded master account. If account output shows `$0` equity unexpectedly, check that mapping first.
|
|
38
|
+
|
|
39
|
+
Common globals:
|
|
40
|
+
|
|
41
|
+
| Flag | Meaning |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `-c, --config <path>` | Use a specific `.env` file |
|
|
44
|
+
| `--testnet` | Use testnet |
|
|
45
|
+
| `--dry` | Preview write commands without executing |
|
|
46
|
+
| `--verbose` | Debug output |
|
|
47
|
+
| `--json` | Machine-readable output on info commands |
|
|
48
|
+
|
|
49
|
+
## Asset discovery and IDs
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
openbroker search --query GOLD --json
|
|
53
|
+
openbroker search --query BTC --type perp --json
|
|
54
|
+
openbroker all-markets --type hip3 --json
|
|
55
|
+
openbroker all-markets --type outcome --json
|
|
56
|
+
openbroker outcomes --query BTC --json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- HIP-3 perps use `dex:COIN`, e.g. `xyz:CL`, not bare `CL`.
|
|
60
|
+
- `assetId` is the canonical identifier for comparisons and persisted agent state; order placement still uses `--coin`.
|
|
61
|
+
- For HIP-4 discovery, use `outcomes --json` for grouped market metadata and `all-markets --type outcome --json` for flattened side rows.
|
|
62
|
+
- HIP-4 outcome orders use `--outcome <id|#encoding|+encoding>` plus `--outcome-side yes|no` when the reference is a plain ID. Encoded sides use `encoding = 10 * outcomeId + side`, where side `0` is the first side and side `1` is the second side.
|
|
63
|
+
- HIP-4 order books use `#<encoding>` coins; spot balances may show `+<encoding>` token names.
|
|
64
|
+
- On testnet, HIP-3 metadata may need an explicit prefixed coin such as `dex:COIN`.
|
|
65
|
+
|
|
66
|
+
## CLI command map
|
|
67
|
+
|
|
68
|
+
### Info commands
|
|
69
|
+
|
|
70
|
+
Most info commands accept `--json`. Use `--coin`, `--top`, and `--address` where the command supports them rather than repeating bespoke parsing logic.
|
|
71
|
+
|
|
72
|
+
| Command | Main use | Distinct flags |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `account` | Equity, margin, spot balances, positions | `--orders`, `--address` |
|
|
75
|
+
| `positions` | Open perp positions and liquidation distance | `--coin`, `--address` |
|
|
76
|
+
| `funding` | Funding rates | `--coin`, `--top`, `--sort annualized|hourly|oi`, `--all`, `--include-hip3` |
|
|
77
|
+
| `markets` | Perp market data | `--coin`, `--top`, `--sort volume|oi|change`, `--include-hip3` |
|
|
78
|
+
| `all-markets` | Browse every venue type | `--type perp|hip3|spot|outcome|all`, `--top`, `--json` |
|
|
79
|
+
| `search` | Find markets across providers | `--query`, `--type` |
|
|
80
|
+
| `spot` | Spot markets or balances | `--coin`, `--balances`, `--address`, `--top` |
|
|
81
|
+
| `fills` | Recent fills | `--coin`, `--side buy|sell`, `--top`, `--address` |
|
|
82
|
+
| `orders` | Order history/open orders | `--coin`, `--status`, `--open`, `--top`, `--address` |
|
|
83
|
+
| `order-status` | One order by exchange/client ID | `--oid`, `--address` |
|
|
84
|
+
| `fees` | Fee tier and rates | `--address` |
|
|
85
|
+
| `candles` | OHLCV | `--coin`, `--interval`, `--bars` |
|
|
86
|
+
| `funding-history` | Historical funding | `--coin`, `--hours` |
|
|
87
|
+
| `trades` | Recent tape | `--coin`, `--top` |
|
|
88
|
+
| `rate-limit` | API usage | — |
|
|
89
|
+
| `funding-scan` | Cross-dex scan | `--threshold`, `--main-only`, `--hip3-only`, `--pairs`, `--watch`, `--interval`, `--top` |
|
|
90
|
+
| `outcomes` | HIP-4 discovery/balances | `--query`, `--outcome`, `--side`, `--balances`, `--top`, `--json` |
|
|
91
|
+
|
|
92
|
+
### Perp trading
|
|
93
|
+
|
|
94
|
+
Shared perp order flags:
|
|
95
|
+
|
|
96
|
+
| Flag | Meaning |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `--coin <COIN>` | Main perp or HIP-3 `dex:COIN` |
|
|
99
|
+
| `--side buy|sell` | Required except `buy` / `sell` shortcuts |
|
|
100
|
+
| `--size <SIZE>` | Base-asset size |
|
|
101
|
+
| `--leverage <N>` | Main perps use cross; HIP-3 uses isolated |
|
|
102
|
+
| `--reduce` | Reduce-only; use when closing/reducing exposure |
|
|
103
|
+
| `--slippage <bps>` | Market/SL slippage tolerance |
|
|
104
|
+
|
|
105
|
+
| Command | Shape |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `buy`, `sell` | Market shortcuts: `openbroker buy --coin ETH --size 0.1` |
|
|
108
|
+
| `market` | Explicit market order with `--side` |
|
|
109
|
+
| `limit` | Add `--price` and optional `--tif GTC|IOC|ALO` |
|
|
110
|
+
| `trigger` | Add `--trigger`, `--type tp|sl`, optional `--limit` |
|
|
111
|
+
| `tpsl` | Protect an existing position with `--tp` and/or `--sl`; accepts absolute, `%`, or `entry` forms |
|
|
112
|
+
| `cancel` | `--all`, `--coin`, or `--oid` |
|
|
113
|
+
|
|
114
|
+
### Spot and HIP-4 outcome trading
|
|
115
|
+
|
|
116
|
+
| Family | Commands | Shared flags |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| Spot | `spot-buy`, `spot-sell`, `spot-order` | `--coin`, `--side`, `--size`, optional `--price`, `--tif Gtc|Ioc|Alo`, `--slippage` |
|
|
119
|
+
| Outcomes | `outcome-buy`, `outcome-sell`, `outcome-open`, `outcome-close`, `outcome-order` | `--outcome`, `--outcome-side`, `--side`, `--size`, optional `--price`, `--tif`, `--slippage`, `--sz-decimals` |
|
|
120
|
+
|
|
121
|
+
### Advanced execution
|
|
122
|
+
|
|
123
|
+
| Command | Use | Distinct flags |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `twap` | Exchange-managed TWAP | `--duration`, `--randomize`, `--reduce-only` |
|
|
126
|
+
| `twap-cancel` | Stop a TWAP | `--coin`, `--twap-id` |
|
|
127
|
+
| `twap-status` | Inspect TWAPs | `--active` |
|
|
128
|
+
| `scale` | Multi-level ladder | `--levels`, `--range`, `--distribution linear|exponential|flat`, `--tif` |
|
|
129
|
+
| `bracket` | Entry + TP + SL | `--entry market|limit`, `--price`, `--tp`, `--sl` |
|
|
130
|
+
| `chase` | Repriced ALO order | `--offset`, `--timeout`, `--interval`, `--max-chase` |
|
|
131
|
+
|
|
132
|
+
## High-signal workflows
|
|
133
|
+
|
|
134
|
+
Inspect before trading:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
openbroker search --query HYPE --json
|
|
138
|
+
openbroker account --orders --json
|
|
139
|
+
openbroker positions --json
|
|
140
|
+
openbroker markets --coin HYPE --json
|
|
141
|
+
openbroker buy --coin HYPE --size 1 --dry
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Close rather than flip:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
openbroker positions --coin ETH --json
|
|
148
|
+
openbroker sell --coin ETH --size 0.1 --reduce --dry
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For large or passive execution, prefer `limit`, `chase`, `scale`, or `twap` over a blind market order.
|
|
152
|
+
|
|
153
|
+
## Automations
|
|
154
|
+
|
|
155
|
+
Automations are TypeScript scripts run by the CLI:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
openbroker auto run <script> [--id <name>] [--set key=value] [--poll <ms>] [--dry]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Management commands:
|
|
162
|
+
|
|
163
|
+
| Command | Use |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `auto examples` | Inspect bundled examples and schemas |
|
|
166
|
+
| `auto run <script>` | Run a custom script by path or from `~/.openbroker/automations/` |
|
|
167
|
+
| `auto list` | List available automations |
|
|
168
|
+
| `auto status` | Show running automations |
|
|
169
|
+
| `auto stop <id>` | Unregister/stop an automation |
|
|
170
|
+
| `auto report <id>` | Summarize audit data |
|
|
171
|
+
| `auto clean` | Reconcile stale registry entries |
|
|
172
|
+
| `auto prune ...` | Delete old audit runs |
|
|
173
|
+
|
|
174
|
+
Run flags:
|
|
175
|
+
|
|
176
|
+
| Flag | Meaning |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `--set key=value` | Repeatable typed config values |
|
|
179
|
+
| `--id <name>` | Stable automation ID |
|
|
180
|
+
| `--poll <ms>` | Poll interval, minimum 1000 ms |
|
|
181
|
+
| `--dry` | Intercept write methods |
|
|
182
|
+
| `--no-ws` | Disable WebSocket and rely on REST polling |
|
|
183
|
+
| `--allow-sleep` | Do not request OS sleep inhibition |
|
|
184
|
+
|
|
185
|
+
Bundled examples are **references, not production strategies**. Read them for API patterns, then write a purpose-built script with explicit sizing, exit logic, and failure behavior.
|
|
186
|
+
|
|
187
|
+
### Automation API essentials
|
|
188
|
+
|
|
189
|
+
- `api.client` — full Hyperliquid client.
|
|
190
|
+
- `api.on(...)`, `api.every(...)`, `api.onStart(...)`, `api.onStop(...)`, `api.onError(...)`.
|
|
191
|
+
- `api.state` — persisted state; survives restarts.
|
|
192
|
+
- `api.audit.record(...)` / `api.audit.metric(...)` — durable observability.
|
|
193
|
+
- `api.publish(...)` — notify an OpenClaw agent when hooks are configured.
|
|
194
|
+
- `api.dryRun` — whether writes are intercepted.
|
|
195
|
+
|
|
196
|
+
Core events include `tick`, `price_change`, `funding_update`, `position_opened`, `position_closed`, `position_changed`, `pnl_threshold`, `margin_warning`, `order_filled`, `order_update`, and `liquidation`.
|
|
197
|
+
|
|
198
|
+
### Monitoring and dashboard
|
|
199
|
+
|
|
200
|
+
`openbroker-monitoring` is optional but useful for long-running automations, live debugging, and post-run inspection.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npm install openbroker-monitoring
|
|
204
|
+
openbroker auto run ./my-automation.ts --id my-auto
|
|
205
|
+
openbroker-monitoring serve --host 127.0.0.1 --port 3001
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- The local dashboard reads `~/.openbroker/automation-audit.sqlite` directly; it works for any standard `openbroker auto run` automation and does **not** need vault config, webhooks, or remote forwarding.
|
|
209
|
+
- Configure it with `OB_MONITOR_HOST`, `OB_MONITOR_PORT`, or `OPENBROKER_AUDIT_DB_PATH`, or the equivalent `serve --host/--port/--db` flags.
|
|
210
|
+
- When installed alongside OpenBroker, the package is convention-loaded as an audit observer. Remote forwarding is separate and only activates when `OB_DASHBOARD_URL` plus `HYPERSTABLE_VAULT_ADDRESS` or `VAULT` are configured; `OB_DASHBOARD_API_KEY` is optional.
|
|
211
|
+
- Start the dashboard when the user wants a live view, troubleshooting help, or ongoing monitoring. It is helpful infrastructure, not a prerequisite for every automation.
|
|
212
|
+
|
|
213
|
+
### Hyperliquid automation design rules
|
|
214
|
+
|
|
215
|
+
These matter more than boilerplate:
|
|
216
|
+
|
|
217
|
+
1. **Model the strategy as a state machine.** Persist flags, streaks, targets, and recovery state with `api.state`; handlers can fire repeatedly and processes can restart.
|
|
218
|
+
2. **Use hysteresis, not one-print decisions.** Confirmation loops, separate enter/exit thresholds, and debounce logic prevent churn from noisy funding or tiny price moves.
|
|
219
|
+
3. **Use the freshest correct signal.** For funding strategies, prefer `getPredictedFundings()` when available; if you fall back to instantaneous funding from metadata, ensure the metadata cache is refreshed so the signal does not freeze after startup.
|
|
220
|
+
4. **Price the flip, not just the signal.** Before closing and later reopening a carry, compare expected hold cost with round-trip trading cost. The HYPE carry automation counts maker fees across both legs plus builder fees before deciding whether a mildly negative funding window is worth exiting.
|
|
221
|
+
5. **Respect settlement timing.** If the current predicted funding is still positive, a close right before hourly settlement can be economically wrong even when the broader signal weakened. Add a settlement-proximity guard when the strategy depends on funding capture.
|
|
222
|
+
6. **Sequence multi-leg hedges deliberately.** For spot-long / perp-short carry, build spot first, then short only up to spot-backed exposure; unwind spot first, then close the short reduce-only. Recover accidental one-sided exposure explicitly instead of pretending it cannot happen.
|
|
223
|
+
7. **Separate strategy logic from execution policy.** Maker-first execution can reduce fees, but it needs bounded retries, post-only rejection handling, order cancellation, partial-fill accounting, minimum trade thresholds, and a defined IOC fallback. Measure progress from refreshed balances/positions, not only from submit responses.
|
|
224
|
+
8. **Size from real NAV and hard caps.** Multi-leg strategies often need spot balances, spot USDC, and perp account value combined; a 50/50 carry target derived from total NAV must then be halved per side and still respect a hard per-side cap.
|
|
225
|
+
9. **Define stop behavior intentionally.** On shutdown, always handle working orders, but do not blindly flatten every strategy. A hedged carry may need “preserve hedge and alert,” while a transient execution bot may need “cancel and flatten.”
|
|
226
|
+
10. **Instrument first-class decisions.** Log and audit funding source, targets, leg notionals, settlement distance, hold/close decisions, fills, retries, and error paths. If using the plugin, publish events that need human attention.
|
|
227
|
+
|
|
228
|
+
Additional practical caveats:
|
|
229
|
+
|
|
230
|
+
- Positive-funding carry and negative-funding carry are not automatically symmetric. If the hedge requires short spot and the client/runtime cannot express that safely, do not invent an unhedged mirror trade.
|
|
231
|
+
- `funding_update` fires for many assets every poll; filter by coin early.
|
|
232
|
+
- Dust matters: if residual size falls below exchange precision or `minTradeUsd`, stop chasing it.
|
|
233
|
+
- `ALO` / post-only orders can be rejected when they would cross; treat that as an execution branch, not a surprise.
|
|
234
|
+
- Naked directional positions usually need explicit TP/SL or equivalent risk logic. Hedged multi-leg strategies need strategy-specific exits instead of cargo-cult TP/SL rules.
|
|
235
|
+
- For new automations, do a dry run, inspect `auto report`, and only then run live unless the user explicitly requested immediate live execution.
|
|
236
|
+
|
|
237
|
+
## Plugin-aware use
|
|
238
|
+
|
|
239
|
+
When the OpenClaw plugin is available:
|
|
240
|
+
|
|
241
|
+
- Prefer `ob_*` tools for common structured reads and simple writes.
|
|
242
|
+
- Use `ob_watcher_status` for background monitoring state.
|
|
243
|
+
- Use `ob_auto_run`, `ob_auto_stop`, and `ob_auto_list` for supported automation actions.
|
|
244
|
+
- Fall back to the CLI for unsupported commands, debugging, richer flags, or if a tool returns empty/unexpected data.
|
|
245
|
+
|
|
246
|
+
Representative mappings:
|
|
247
|
+
|
|
248
|
+
| Plugin tool | CLI equivalent |
|
|
249
|
+
|---|---|
|
|
250
|
+
| `ob_account` | `openbroker account --json` |
|
|
251
|
+
| `ob_positions` | `openbroker positions --json` |
|
|
252
|
+
| `ob_funding` | `openbroker funding --json --include-hip3` |
|
|
253
|
+
| `ob_search` | `openbroker search --query <QUERY> --json` |
|
|
254
|
+
| `ob_buy` / `ob_sell` | `openbroker buy|sell --coin <COIN> --size <SIZE>` |
|
|
255
|
+
| `ob_limit` | `openbroker limit ...` |
|
|
256
|
+
| `ob_tpsl` | `openbroker tpsl ...` |
|
|
257
|
+
| `ob_auto_run` | `openbroker auto run <script> ...` |
|
|
258
|
+
|
|
259
|
+
Skill-only mode is fully usable through the CLI; the plugin adds agent tools, watcher notifications, and OpenClaw webhook integration.
|
|
260
|
+
|
|
261
|
+
## Failure checks
|
|
262
|
+
|
|
263
|
+
- `No market data found` → search again; likely wrong venue prefix.
|
|
264
|
+
- `$0` equity on an API wallet → likely missing `HYPERLIQUID_ACCOUNT_ADDRESS`.
|
|
265
|
+
- Unexpected funding behavior → check whether you are reading predicted vs cached instantaneous data.
|
|
266
|
+
- Strategy churn → inspect confirmation loops, fee-aware hold logic, settlement guards, and min-trade thresholds before changing position size.
|
|
267
|
+
- Tool failure in plugin mode → rerun the equivalent CLI command with `--json` and `--verbose` if needed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbroker",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"bin/",
|
|
16
16
|
"scripts/",
|
|
17
17
|
"config/example.env",
|
|
18
|
+
"SKILL.md",
|
|
18
19
|
"README.md",
|
|
19
20
|
"CHANGELOG.md"
|
|
20
21
|
],
|
|
@@ -49,7 +50,6 @@
|
|
|
49
50
|
"dependencies": {
|
|
50
51
|
"@nktkas/hyperliquid": "^0.30.3",
|
|
51
52
|
"dotenv": "^17.2.3",
|
|
52
|
-
"openbroker-monitoring": "file:../openbroker-monitoring",
|
|
53
53
|
"tsx": "^4.19.0",
|
|
54
54
|
"viem": "^2.21.0"
|
|
55
55
|
},
|
package/scripts/auto/cli.ts
CHANGED
|
@@ -34,6 +34,7 @@ Options (for run):
|
|
|
34
34
|
--id <name> Custom automation ID (default: filename)
|
|
35
35
|
--poll <ms> Poll interval in milliseconds (default: 10000)
|
|
36
36
|
--no-ws Disable WebSocket; fall back to REST-only polling
|
|
37
|
+
--allow-sleep Do not request OS idle-sleep inhibition for this run
|
|
37
38
|
|
|
38
39
|
Options (for prune):
|
|
39
40
|
--older-than <d> Only prune runs started before this duration ago (e.g. 7d, 24h)
|
|
@@ -125,6 +126,7 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
|
|
|
125
126
|
const dryRun = args.dry === true;
|
|
126
127
|
const verbose = args.verbose === true;
|
|
127
128
|
const useWebSocket = args['no-ws'] !== true;
|
|
129
|
+
const keepAwake = args['allow-sleep'] === true ? false : undefined;
|
|
128
130
|
const pollIntervalMs = args.poll ? parseInt(String(args.poll), 10) : undefined;
|
|
129
131
|
const id = args.id ? String(args.id) : undefined;
|
|
130
132
|
|
|
@@ -150,6 +152,7 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
|
|
|
150
152
|
verbose,
|
|
151
153
|
pollIntervalMs,
|
|
152
154
|
useWebSocket,
|
|
155
|
+
keepAwake,
|
|
153
156
|
initialState: Object.keys(initialState).length > 0 ? initialState : undefined,
|
|
154
157
|
hooksToken: envHooksToken,
|
|
155
158
|
gatewayPort: envGatewayPort && !isNaN(envGatewayPort) ? envGatewayPort : undefined,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from 'child_process';
|
|
2
|
+
|
|
3
|
+
type KeepAwakeLogger = {
|
|
4
|
+
info(message: string): void;
|
|
5
|
+
warn(message: string): void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export interface KeepAwakeHandle {
|
|
9
|
+
backend: string;
|
|
10
|
+
stop(): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createHandle(
|
|
14
|
+
child: ChildProcess,
|
|
15
|
+
backend: string,
|
|
16
|
+
log: KeepAwakeLogger,
|
|
17
|
+
): KeepAwakeHandle {
|
|
18
|
+
let stopping = false;
|
|
19
|
+
|
|
20
|
+
child.once('error', (error) => {
|
|
21
|
+
if (!stopping) {
|
|
22
|
+
log.warn(`keep-awake unavailable via ${backend}: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
child.once('exit', (code, signal) => {
|
|
27
|
+
if (!stopping) {
|
|
28
|
+
const suffix = signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`;
|
|
29
|
+
log.warn(`keep-awake helper ${backend} exited unexpectedly (${suffix}); host may sleep.`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
backend,
|
|
35
|
+
stop() {
|
|
36
|
+
stopping = true;
|
|
37
|
+
if (!child.killed) child.kill();
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function startDarwin(log: KeepAwakeLogger): KeepAwakeHandle {
|
|
43
|
+
const child = spawn('caffeinate', ['-i', '-w', String(process.pid)], {
|
|
44
|
+
stdio: 'ignore',
|
|
45
|
+
});
|
|
46
|
+
return createHandle(child, 'caffeinate', log);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function startLinux(reason: string, log: KeepAwakeLogger): KeepAwakeHandle {
|
|
50
|
+
const child = spawn(
|
|
51
|
+
'systemd-inhibit',
|
|
52
|
+
[
|
|
53
|
+
'--what=sleep',
|
|
54
|
+
'--who=OpenBroker',
|
|
55
|
+
`--why=${reason}`,
|
|
56
|
+
'--mode=block',
|
|
57
|
+
'sh',
|
|
58
|
+
'-c',
|
|
59
|
+
'while kill -0 "$1" 2>/dev/null; do sleep 30; done',
|
|
60
|
+
'sh',
|
|
61
|
+
String(process.pid),
|
|
62
|
+
],
|
|
63
|
+
{ stdio: 'ignore' },
|
|
64
|
+
);
|
|
65
|
+
return createHandle(child, 'systemd-inhibit', log);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function startWindows(log: KeepAwakeLogger): KeepAwakeHandle {
|
|
69
|
+
const script = [
|
|
70
|
+
'Add-Type -Namespace OpenBroker -Name Native -MemberDefinition',
|
|
71
|
+
'\'"[DllImport(\\\"kernel32.dll\\\")] public static extern uint SetThreadExecutionState(uint esFlags);"\';',
|
|
72
|
+
'[OpenBroker.Native]::SetThreadExecutionState(0x80000001) | Out-Null;',
|
|
73
|
+
`Wait-Process -Id ${process.pid};`,
|
|
74
|
+
'[OpenBroker.Native]::SetThreadExecutionState(0x80000000) | Out-Null;',
|
|
75
|
+
].join(' ');
|
|
76
|
+
|
|
77
|
+
const child = spawn(
|
|
78
|
+
'powershell.exe',
|
|
79
|
+
['-NoProfile', '-NonInteractive', '-Command', script],
|
|
80
|
+
{
|
|
81
|
+
stdio: 'ignore',
|
|
82
|
+
windowsHide: true,
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
return createHandle(child, 'SetThreadExecutionState', log);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function startKeepAwake(reason: string, log: KeepAwakeLogger): KeepAwakeHandle | null {
|
|
89
|
+
switch (process.platform) {
|
|
90
|
+
case 'darwin':
|
|
91
|
+
return startDarwin(log);
|
|
92
|
+
case 'linux':
|
|
93
|
+
return startLinux(reason, log);
|
|
94
|
+
case 'win32':
|
|
95
|
+
return startWindows(log);
|
|
96
|
+
default:
|
|
97
|
+
log.warn(`keep-awake is not supported on platform ${process.platform}; host may sleep.`);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
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 { startKeepAwake, type KeepAwakeHandle } from './keep-awake.js';
|
|
18
19
|
import type {
|
|
19
20
|
AutomationAPI,
|
|
20
21
|
AutomationAuditObserver,
|
|
@@ -451,6 +452,11 @@ export interface RuntimeOptions {
|
|
|
451
452
|
* @default true
|
|
452
453
|
*/
|
|
453
454
|
useWebSocket?: boolean;
|
|
455
|
+
/**
|
|
456
|
+
* Best-effort host idle-sleep inhibition for the lifetime of the automation.
|
|
457
|
+
* Enabled by default for live runs and disabled by default for dry runs.
|
|
458
|
+
*/
|
|
459
|
+
keepAwake?: boolean;
|
|
454
460
|
}
|
|
455
461
|
|
|
456
462
|
/** Registry of all running automations */
|
|
@@ -516,6 +522,14 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
516
522
|
stateController.attachAudit(audit);
|
|
517
523
|
|
|
518
524
|
const log = createLogger(id, verbose, audit);
|
|
525
|
+
const keepAwakeEnabled = options.keepAwake ?? !dryRun;
|
|
526
|
+
let keepAwake: KeepAwakeHandle | null = null;
|
|
527
|
+
if (keepAwakeEnabled) {
|
|
528
|
+
keepAwake = startKeepAwake(`OpenBroker automation ${id} is running`, log);
|
|
529
|
+
if (keepAwake) {
|
|
530
|
+
log.info(`keep-awake enabled via ${keepAwake.backend}.`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
519
533
|
const observers = await loadConventionObservers(log);
|
|
520
534
|
const baseClient = dryRun ? createDryClient(rawClient, log) : rawClient;
|
|
521
535
|
const client = createAuditedClient(baseClient, audit, dryRun, observers);
|
|
@@ -576,6 +590,7 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
576
590
|
pollCount: 0,
|
|
577
591
|
eventsEmitted: 0,
|
|
578
592
|
});
|
|
593
|
+
keepAwake?.stop();
|
|
579
594
|
throw error;
|
|
580
595
|
}
|
|
581
596
|
|
|
@@ -949,6 +964,8 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
949
964
|
}
|
|
950
965
|
}
|
|
951
966
|
|
|
967
|
+
keepAwake?.stop();
|
|
968
|
+
|
|
952
969
|
eventBus.removeAll();
|
|
953
970
|
registry.delete(id);
|
|
954
971
|
|
package/scripts/core/client.ts
CHANGED
|
@@ -668,9 +668,19 @@ export class HyperliquidClient {
|
|
|
668
668
|
private parseOutcomeDescription(description: string): Record<string, string> {
|
|
669
669
|
const parsed: Record<string, string> = {};
|
|
670
670
|
for (const part of description.split('|')) {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
671
|
+
const rawSegment = part.trim();
|
|
672
|
+
const metadataIdx = rawSegment.indexOf('metadata=');
|
|
673
|
+
const segments = metadataIdx >= 0
|
|
674
|
+
? [rawSegment, rawSegment.slice(metadataIdx + 'metadata='.length)]
|
|
675
|
+
: [rawSegment];
|
|
676
|
+
|
|
677
|
+
for (const segment of segments) {
|
|
678
|
+
const idx = segment.indexOf(':');
|
|
679
|
+
if (idx <= 0) continue;
|
|
680
|
+
const key = segment.slice(0, idx).trim();
|
|
681
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key)) continue;
|
|
682
|
+
parsed[key] = segment.slice(idx + 1).trim();
|
|
683
|
+
}
|
|
674
684
|
}
|
|
675
685
|
return parsed;
|
|
676
686
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
// All Markets - View all available markets across perps, spot, and HIP-
|
|
2
|
+
// All Markets - View all available markets across perps, HIP-3, spot, and HIP-4 outcomes
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
5
|
|
|
@@ -88,9 +88,22 @@ interface MarketRow {
|
|
|
88
88
|
maxLeverage?: number;
|
|
89
89
|
outcome?: number;
|
|
90
90
|
outcomeSide?: string;
|
|
91
|
+
outcomeName?: string;
|
|
92
|
+
tokenName?: string;
|
|
93
|
+
parsedDescription?: Record<string, string>;
|
|
91
94
|
description?: string;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
function handleMarketFetchError(kind: NonNullable<Args['type']>, error: unknown, requestedType: Args['type'], verbose?: boolean): void {
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
if (requestedType === kind) {
|
|
100
|
+
throw new Error(`Failed to fetch ${kind} markets: ${message}`);
|
|
101
|
+
}
|
|
102
|
+
if (verbose) {
|
|
103
|
+
console.error(`Failed to fetch ${kind} markets:`, error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
async function main() {
|
|
95
108
|
const args = parseArgs();
|
|
96
109
|
const client = getClient();
|
|
@@ -202,12 +215,15 @@ async function main() {
|
|
|
202
215
|
volume24h: parseFloat(side.dayNtlVlm || '0'),
|
|
203
216
|
outcome: market.outcome,
|
|
204
217
|
outcomeSide: side.name,
|
|
218
|
+
outcomeName: market.name,
|
|
219
|
+
tokenName: side.tokenName,
|
|
220
|
+
parsedDescription: market.parsedDescription,
|
|
205
221
|
description: market.description,
|
|
206
222
|
});
|
|
207
223
|
}
|
|
208
224
|
}
|
|
209
225
|
} catch (e) {
|
|
210
|
-
|
|
226
|
+
handleMarketFetchError('outcome', e, args.type, args.verbose);
|
|
211
227
|
}
|
|
212
228
|
}
|
|
213
229
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
// Search Markets - Find specific assets across all providers (perps, HIP-3, spot)
|
|
2
|
+
// Search Markets - Find specific assets across all providers (perps, HIP-3, spot, HIP-4)
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
5
|
|
|
@@ -85,6 +85,16 @@ function formatFunding(rate: string): string {
|
|
|
85
85
|
return `${sign}${annualized.toFixed(2)}%`;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function handleMarketFetchError(kind: NonNullable<Args['type']>, error: unknown, requestedType: Args['type'], verbose?: boolean): void {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
if (requestedType === kind) {
|
|
91
|
+
throw new Error(`Failed to fetch ${kind} markets: ${message}`);
|
|
92
|
+
}
|
|
93
|
+
if (verbose) {
|
|
94
|
+
console.error(`Failed to fetch ${kind} markets:`, error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
async function main() {
|
|
89
99
|
const args = parseArgs();
|
|
90
100
|
const client = getClient();
|
|
@@ -107,6 +117,9 @@ async function main() {
|
|
|
107
117
|
openInterest?: string;
|
|
108
118
|
outcome?: number;
|
|
109
119
|
outcomeSide?: string;
|
|
120
|
+
outcomeName?: string;
|
|
121
|
+
tokenName?: string;
|
|
122
|
+
parsedDescription?: Record<string, string>;
|
|
110
123
|
description?: string;
|
|
111
124
|
}
|
|
112
125
|
|
|
@@ -247,12 +260,15 @@ async function main() {
|
|
|
247
260
|
volume24h: parseFloat(side.dayNtlVlm || '0'),
|
|
248
261
|
outcome: market.outcome,
|
|
249
262
|
outcomeSide: side.name,
|
|
263
|
+
outcomeName: market.name,
|
|
264
|
+
tokenName: side.tokenName,
|
|
265
|
+
parsedDescription: market.parsedDescription,
|
|
250
266
|
description: market.description,
|
|
251
267
|
});
|
|
252
268
|
}
|
|
253
269
|
}
|
|
254
270
|
} catch (e) {
|
|
255
|
-
|
|
271
|
+
handleMarketFetchError('outcome', e, args.type, args.verbose);
|
|
256
272
|
}
|
|
257
273
|
}
|
|
258
274
|
|