openbroker 1.0.65 → 1.0.67

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/README.md CHANGED
@@ -172,10 +172,12 @@ openbroker fills --coin BTC --side buy --top 50
172
172
 
173
173
  #### `orders` — Order History
174
174
 
175
- View all historical orders including filled, canceled, and rejected.
175
+ View all historical orders including filled, canceled, and rejected. Use `--open` to show only currently open orders.
176
176
 
177
177
  ```bash
178
178
  openbroker orders # Recent orders
179
+ openbroker orders --open # Currently open orders only
180
+ openbroker orders --open --coin ETH # Open orders for a specific coin
179
181
  openbroker orders --coin ETH --status filled
180
182
  openbroker orders --top 50
181
183
  ```
@@ -184,6 +186,7 @@ openbroker orders --top 50
184
186
  |------|-------------|---------|
185
187
  | `--coin` | Filter by coin symbol | — |
186
188
  | `--status` | Filter by status (filled, canceled, open, triggered, etc.) | — |
189
+ | `--open` | Show only currently open orders | — |
187
190
  | `--top` | Number of recent orders to show | `20` |
188
191
 
189
192
  #### `order-status` — Check Order Status
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.65", "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)"}]}}
7
+ metadata: {"author": "monemetrics", "version": "1.0.67", "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_twap ob_bracket ob_chase ob_watcher_status ob_auto_run ob_auto_stop ob_auto_list Bash(openbroker:*)
9
9
  ---
10
10
 
@@ -100,7 +100,7 @@ The simplest setup for agents. A fresh wallet is generated, the builder fee is a
100
100
  **Flow:**
101
101
  1. Run `openbroker setup` and choose option 1 ("Generate a fresh wallet")
102
102
  2. The CLI generates a wallet, saves the config, and approves the builder fee automatically
103
- 3. Fund the wallet with USDC on Arbitrum, then deposit at https://app.hyperliquid.xyz/
103
+ 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.**
104
104
  4. Start trading
105
105
 
106
106
  ### API Wallet Setup (Alternative)
@@ -177,6 +177,8 @@ openbroker fills --coin BTC --side buy --top 50
177
177
  ### Order History
178
178
  ```bash
179
179
  openbroker orders # Recent orders (all statuses)
180
+ openbroker orders --open # Currently open orders only
181
+ openbroker orders --open --coin ETH # Open orders for a specific coin
180
182
  openbroker orders --coin ETH --status filled
181
183
  openbroker orders --top 50
182
184
  ```
@@ -912,15 +914,32 @@ openbroker auto status # Show running automations
912
914
  | `--id <name>` | Custom automation ID | filename |
913
915
  | `--poll <ms>` | Poll interval in milliseconds | 10000 |
914
916
 
915
- **Important notes for agents writing automations:**
916
- - Always test with `--dry` first before live trading
917
- - Use `api.state` to track position state across restarts
918
- - Use `api.onStop()` to clean up close positions, cancel orders
919
- - Use `api.publish()` to send alerts/events back to the OpenClaw agent do NOT manually construct webhook requests
920
- - The runtime catches errors per handler — one failing handler won't crash others
921
- - Scripts are loaded from `~/.openbroker/automations/` by name, or from any absolute path
922
- - All trading commands support HIP-3 assets (`api.client.marketOrder('xyz:CL', true, 1)`)
923
- - Automations persist across gateway restarts they are automatically restarted when the gateway comes back up
917
+ **Guidelines for agents writing automations:**
918
+
919
+ **Risk & Safety (mandatory):**
920
+ - 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.
921
+ - 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.
922
+ - 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.
923
+ - Do NOT use `--dry` unless the user explicitly asks for it. Automations should run live by default.
924
+ - Never place trades without validating that sufficient margin is available. Check account state before sizing orders.
925
+ - 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.
926
+ - Always set TP/SL on new positions — either within the automation or by confirming the user has them set. Unprotected positions are a liability.
927
+
928
+ **State & Reliability:**
929
+ - 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.
930
+ - 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.
931
+ - 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.
932
+
933
+ **Communication:**
934
+ - Use `api.publish()` to send alerts/events back to the OpenClaw agent — do NOT manually construct webhook requests.
935
+ - 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.
936
+ - Include actionable context in publish messages — coin, price, size, PnL, and what happened — so the user can make informed decisions without checking the terminal.
937
+
938
+ **General:**
939
+ - Scripts are loaded from `~/.openbroker/automations/` by name, or from any absolute path.
940
+ - All trading commands support HIP-3 assets (`api.client.marketOrder('xyz:CL', true, 1)`).
941
+ - Automations persist across gateway restarts — they are automatically restarted when the gateway comes back up.
942
+ - Prefer `api.every(ms, fn)` over `tick` for periodic tasks with intervals longer than the poll cycle.
924
943
 
925
944
  ## Risk Warning
926
945
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openbroker",
3
3
  "name": "OpenBroker — Hyperliquid Trading",
4
- "version": "1.0.65",
4
+ "version": "1.0.67",
5
5
  "description": "Trade on Hyperliquid DEX with background position monitoring",
6
6
  "configSchema": {
7
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbroker",
3
- "version": "1.0.65",
3
+ "version": "1.0.67",
4
4
  "description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1221,8 +1221,28 @@ export class HyperliquidClient {
1221
1221
 
1222
1222
  async getOpenOrders(user?: string): Promise<OpenOrder[]> {
1223
1223
  this.log('Fetching openOrders for:', user ?? this.address);
1224
- const response = await this.info.openOrders({ user: user ?? this.address });
1225
- return response as OpenOrder[];
1224
+ await this.getMetaAndAssetCtxs(); // Ensure HIP-3 dex list is loaded
1225
+
1226
+ // Fetch main dex orders
1227
+ const orders = await this.info.openOrders({ user: user ?? this.address }) as OpenOrder[];
1228
+
1229
+ // Fetch HIP-3 dex orders
1230
+ const dexs = await this.getPerpDexs();
1231
+ for (let i = 1; i < dexs.length; i++) {
1232
+ const dex = dexs[i];
1233
+ if (!dex) continue;
1234
+ try {
1235
+ const dexOrders = await this.info.openOrders({ user: user ?? this.address, dex: dex.name }) as OpenOrder[];
1236
+ if (dexOrders.length > 0) {
1237
+ this.log(`Found ${dexOrders.length} open orders on HIP-3 dex ${dex.name}`);
1238
+ orders.push(...dexOrders);
1239
+ }
1240
+ } catch (err) {
1241
+ this.log(`Failed to fetch open orders for dex ${dex.name}:`, err instanceof Error ? err.message : String(err));
1242
+ }
1243
+ }
1244
+
1245
+ return orders;
1226
1246
  }
1227
1247
 
1228
1248
  // ============ Trading ============
@@ -11,11 +11,14 @@ Usage: openbroker orders [options]
11
11
  Options:
12
12
  --coin <symbol> Filter by coin (e.g. ETH, BTC)
13
13
  --status <status> Filter by status (filled, canceled, open, triggered, rejected, etc.)
14
+ --open Show only currently open orders
14
15
  --top <n> Show last N orders (default: 20)
15
16
  --help, -h Show this help
16
17
 
17
18
  Examples:
18
19
  openbroker orders
20
+ openbroker orders --open
21
+ openbroker orders --open --coin ETH
19
22
  openbroker orders --coin ETH --status filled
20
23
  openbroker orders --top 50
21
24
  `);
@@ -31,11 +34,78 @@ async function main() {
31
34
 
32
35
  const filterCoin = args.coin as string | undefined;
33
36
  const filterStatus = args.status as string | undefined;
37
+ const openOnly = args.open as boolean;
34
38
  const top = parseInt(args.top as string) || 20;
35
39
  const jsonOutput = args.json as boolean;
36
40
  const client = getClient();
37
41
 
38
42
  try {
43
+ if (openOnly) {
44
+ // Use the dedicated open orders endpoint
45
+ let openOrders = await client.getOpenOrders();
46
+
47
+ if (filterCoin) {
48
+ openOrders = openOrders.filter(o => o.coin === normalizeCoin(filterCoin));
49
+ }
50
+
51
+ openOrders.sort((a, b) => b.timestamp - a.timestamp);
52
+ openOrders = openOrders.slice(0, top);
53
+
54
+ if (jsonOutput) {
55
+ console.log(JSON.stringify(openOrders.map(o => ({
56
+ time: new Date(o.timestamp).toISOString(),
57
+ coin: o.coin,
58
+ side: o.side === 'B' ? 'buy' : 'sell',
59
+ orderType: o.orderType,
60
+ size: o.sz,
61
+ origSize: o.origSz,
62
+ price: o.limitPx,
63
+ status: 'open',
64
+ oid: o.oid,
65
+ })), null, 2));
66
+ return;
67
+ }
68
+
69
+ console.log('Open Broker - Open Orders');
70
+ console.log('=========================\n');
71
+
72
+ if (openOrders.length === 0) {
73
+ console.log('No open orders found');
74
+ return;
75
+ }
76
+
77
+ // Table header
78
+ console.log(
79
+ 'Time'.padEnd(20) +
80
+ 'Coin'.padEnd(10) +
81
+ 'Side'.padEnd(6) +
82
+ 'Type'.padEnd(14) +
83
+ 'Size'.padEnd(12) +
84
+ 'Price'.padEnd(14) +
85
+ 'OID'
86
+ );
87
+ console.log('─'.repeat(90));
88
+
89
+ for (const o of openOrders) {
90
+ const time = new Date(o.timestamp).toLocaleString();
91
+ const side = o.side === 'B' ? 'BUY' : 'SELL';
92
+
93
+ console.log(
94
+ time.padEnd(20) +
95
+ o.coin.padEnd(10) +
96
+ side.padEnd(6) +
97
+ o.orderType.padEnd(14) +
98
+ o.sz.padEnd(12) +
99
+ formatUsd(parseFloat(o.limitPx)).padEnd(14) +
100
+ String(o.oid)
101
+ );
102
+ }
103
+
104
+ console.log('─'.repeat(90));
105
+ console.log(`Showing ${openOrders.length} open orders`);
106
+ return;
107
+ }
108
+
39
109
  let orders = await client.getHistoricalOrders();
40
110
 
41
111
  if (filterCoin) {
@@ -491,18 +491,48 @@ export function createTools(watcherOrCtx: PositionWatcher | null | ToolsContext)
491
491
 
492
492
  {
493
493
  name: 'ob_orders',
494
- description: 'View order history with status (filled, canceled, open, etc.)',
494
+ description: 'View order history with status (filled, canceled, open, etc.). Use open_only to get currently open orders.',
495
495
  parameters: {
496
496
  type: 'object',
497
497
  properties: {
498
498
  coin: { type: 'string', description: 'Filter by coin symbol' },
499
499
  status: { type: 'string', description: 'Filter by status (filled, canceled, open, etc.)' },
500
+ open_only: { type: 'boolean', description: 'If true, return only currently open orders (uses dedicated endpoint)' },
500
501
  top: { type: 'number', description: 'Number of recent orders (default: 20)' },
501
502
  },
502
503
  },
503
504
  async execute(_id, params) {
504
505
  const { getClient } = await import('../core/client.js');
505
506
  const client = getClient();
507
+ const top = (params.top as number) || 20;
508
+
509
+ if (params.open_only) {
510
+ let openOrders = await client.getOpenOrders();
511
+
512
+ if (params.coin) {
513
+ const coin = normalizeCoin(params.coin as string);
514
+ openOrders = openOrders.filter(o => o.coin === coin);
515
+ }
516
+
517
+ openOrders.sort((a, b) => b.timestamp - a.timestamp);
518
+ openOrders = openOrders.slice(0, top);
519
+
520
+ return json({
521
+ address: client.address,
522
+ orders: openOrders.map(o => ({
523
+ coin: o.coin,
524
+ side: o.side === 'B' ? 'buy' : 'sell',
525
+ size: o.sz,
526
+ origSize: o.origSz,
527
+ price: o.limitPx,
528
+ orderType: o.orderType,
529
+ oid: o.oid,
530
+ status: 'open',
531
+ timestamp: o.timestamp,
532
+ })),
533
+ });
534
+ }
535
+
506
536
  let orders = await client.getHistoricalOrders();
507
537
 
508
538
  if (params.coin) {
@@ -515,7 +545,6 @@ export function createTools(watcherOrCtx: PositionWatcher | null | ToolsContext)
515
545
  }
516
546
 
517
547
  orders.sort((a, b) => b.order.timestamp - a.order.timestamp);
518
- const top = (params.top as number) || 20;
519
548
  orders = orders.slice(0, top);
520
549
 
521
550
  return json({