openbroker 1.0.66 → 1.0.68

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.66", "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.68", "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
  ```
@@ -232,7 +234,6 @@ All trading commands support HIP-3 assets using `dex:COIN` syntax:
232
234
  openbroker buy --coin xyz:CL --size 1 # Buy crude oil on xyz dex
233
235
  openbroker sell --coin xyz:BRENTOIL --size 1 # Sell brent oil
234
236
  openbroker limit --coin xyz:GOLD --side buy --size 0.1 --price 2500
235
- openbroker funding-arb --coin xyz:CL --size 5000 # Funding arb on HIP-3
236
237
  ```
237
238
 
238
239
  ### Market Orders (Quick)
@@ -323,53 +324,6 @@ openbroker chase --coin ETH --side buy --size 0.5 --timeout 300
323
324
  openbroker chase --coin SOL --side buy --size 10 --offset 2 --timeout 60
324
325
  ```
325
326
 
326
- ## Trading Strategies
327
-
328
- ### Funding Arbitrage
329
- ```bash
330
- # Collect funding on ETH if rate > 25% annualized
331
- openbroker funding-arb --coin ETH --size 5000 --min-funding 25
332
-
333
- # Run for 24 hours, check every 30 minutes
334
- openbroker funding-arb --coin BTC --size 10000 --duration 24 --check 30 --dry
335
- ```
336
-
337
- ### Grid Trading
338
- ```bash
339
- # ETH grid from $3000-$4000 with 10 levels, 0.1 ETH per level
340
- openbroker grid --coin ETH --lower 3000 --upper 4000 --grids 10 --size 0.1
341
-
342
- # Accumulation grid (buys only)
343
- openbroker grid --coin BTC --lower 90000 --upper 100000 --grids 5 --size 0.01 --mode long
344
- ```
345
-
346
- ### DCA (Dollar Cost Averaging)
347
- ```bash
348
- # Buy $100 of ETH every hour for 24 hours
349
- openbroker dca --coin ETH --amount 100 --interval 1h --count 24
350
-
351
- # Invest $5000 in BTC over 30 days with daily purchases
352
- openbroker dca --coin BTC --total 5000 --interval 1d --count 30
353
- ```
354
-
355
- ### Market Making Spread
356
- ```bash
357
- # Market make ETH with 0.1 size, 10bps spread
358
- openbroker mm-spread --coin ETH --size 0.1 --spread 10
359
-
360
- # Tighter spread with position limit
361
- openbroker mm-spread --coin BTC --size 0.01 --spread 5 --max-position 0.1
362
- ```
363
-
364
- ### Maker-Only MM (ALO orders)
365
- ```bash
366
- # Market make using ALO (post-only) orders - guarantees maker rebates
367
- openbroker mm-maker --coin HYPE --size 1 --offset 1
368
-
369
- # Wider offset for volatile assets
370
- openbroker mm-maker --coin ETH --size 0.1 --offset 2 --max-position 0.5
371
- ```
372
-
373
327
  ## Order Types
374
328
 
375
329
  ### Limit Orders vs Trigger Orders
@@ -493,7 +447,24 @@ Without hooks, the watcher still runs and tracks state (accessible via `ob_watch
493
447
 
494
448
  ## Trading Automations
495
449
 
496
- Automations let you write custom event-driven trading logic as TypeScript scripts. Instead of using the rigid built-in strategies, write exactly the logic you need and OpenBroker handles the polling, event detection, and SDK access.
450
+ 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.
451
+
452
+ ### IMPORTANT: Bundled Examples Are References, Not Ready-Made Strategies
453
+
454
+ 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:
455
+
456
+ 1. Read the relevant bundled example source code to understand the automation API patterns
457
+ 2. Write a **custom automation** tailored to the user's specific requirements (coin, size, thresholds, risk parameters, TP/SL logic, etc.)
458
+ 3. Save it to `~/.openbroker/automations/` and run it with `openbroker auto run`
459
+
460
+ 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.
461
+
462
+ To view bundled examples and their config schemas:
463
+ ```bash
464
+ openbroker auto examples # List examples with config fields
465
+ ```
466
+
467
+ Available examples: `dca`, `grid`, `funding-arb`, `mm-spread`, `mm-maker`
497
468
 
498
469
  ### How Automations Work
499
470
 
@@ -895,14 +866,17 @@ export default function(api) {
895
866
  openbroker auto run my-strategy --dry # Test without trading
896
867
  openbroker auto run ./funding-scalp.ts # Run from path
897
868
  openbroker auto run my-strategy --poll 5000 # Poll every 5s
869
+ openbroker auto run --example dca --set coin=HYPE --set amount=50 --dry # Run bundled example
870
+ openbroker auto examples # List bundled examples with config
898
871
  openbroker auto list # Show available scripts
899
872
  openbroker auto status # Show running automations
900
873
  ```
901
874
 
902
875
  **Plugin tools (for OpenClaw agents):**
903
876
  - `ob_auto_run` — `{ "script": "funding-scalp", "dry": true }` — start an automation
877
+ - `ob_auto_run` — `{ "example": "dca", "config": { "coin": "HYPE", "amount": 50 }, "dry": true }` — run a bundled example
904
878
  - `ob_auto_stop` — `{ "id": "funding-scalp" }` — stop a running automation
905
- - `ob_auto_list` — `{}` — list available and running automations
879
+ - `ob_auto_list` — `{}` — list available automations, bundled examples with config schemas, and running automations
906
880
 
907
881
  **Options:**
908
882
  | Flag | Description | Default |
@@ -911,16 +885,35 @@ openbroker auto status # Show running automations
911
885
  | `--verbose` | Show debug output | false |
912
886
  | `--id <name>` | Custom automation ID | filename |
913
887
  | `--poll <ms>` | Poll interval in milliseconds | 10000 |
914
-
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
888
+ | `--example <name>` | Run a bundled example automation | - |
889
+ | `--set key=value` | Set config values (repeatable) | - |
890
+
891
+ **Guidelines for agents writing automations:**
892
+
893
+ **Risk & Safety (mandatory):**
894
+ - 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.
895
+ - 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.
896
+ - 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.
897
+ - Do NOT use `--dry` unless the user explicitly asks for it. Automations should run live by default.
898
+ - Never place trades without validating that sufficient margin is available. Check account state before sizing orders.
899
+ - 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.
900
+ - Always set TP/SL on new positions — either within the automation or by confirming the user has them set. Unprotected positions are a liability.
901
+
902
+ **State & Reliability:**
903
+ - 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.
904
+ - 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.
905
+ - 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.
906
+
907
+ **Communication:**
908
+ - Use `api.publish()` to send alerts/events back to the OpenClaw agent — do NOT manually construct webhook requests.
909
+ - 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.
910
+ - Include actionable context in publish messages — coin, price, size, PnL, and what happened — so the user can make informed decisions without checking the terminal.
911
+
912
+ **General:**
913
+ - Scripts are loaded from `~/.openbroker/automations/` by name, or from any absolute path.
914
+ - All trading commands support HIP-3 assets (`api.client.marketOrder('xyz:CL', true, 1)`).
915
+ - Automations persist across gateway restarts — they are automatically restarted when the gateway comes back up.
916
+ - Prefer `api.every(ms, fn)` over `tick` for periodic tasks with intervals longer than the poll cycle.
924
917
 
925
918
  ## Risk Warning
926
919
 
package/bin/cli.ts CHANGED
@@ -46,13 +46,6 @@ const commands: Record<string, { script: string; description: string }> = {
46
46
  'bracket': { script: 'operations/bracket.ts', description: 'Bracket order (entry + TP + SL)' },
47
47
  'chase': { script: 'operations/chase.ts', description: 'Chase order with ALO' },
48
48
 
49
- // Strategies
50
- 'funding-arb': { script: 'strategies/funding-arb.ts', description: 'Funding arbitrage strategy' },
51
- 'grid': { script: 'strategies/grid.ts', description: 'Grid trading strategy' },
52
- 'dca': { script: 'strategies/dca.ts', description: 'DCA strategy' },
53
- 'mm-spread': { script: 'strategies/mm-spread.ts', description: 'Market making (spread)' },
54
- 'mm-maker': { script: 'strategies/mm-maker.ts', description: 'Market making (ALO)' },
55
-
56
49
  // Automations
57
50
  'auto': { script: 'auto/cli.ts', description: 'Run/manage trading automations' },
58
51
  };
@@ -99,15 +92,10 @@ Advanced Execution:
99
92
  bracket Entry with TP and SL
100
93
  chase Chase price with ALO orders
101
94
 
102
- Strategies:
103
- funding-arb Funding rate arbitrage
104
- grid Grid trading
105
- dca Dollar cost averaging
106
- mm-spread Market making (spread-based)
107
- mm-maker Market making (ALO orders)
108
-
109
95
  Automations:
110
96
  auto run <script> Run a custom trading automation
97
+ auto run --example <name> [--set key=value] Run a bundled example
98
+ auto examples List bundled examples (dca, grid, funding-arb, mm-spread, mm-maker)
111
99
  auto list List available automations
112
100
  auto status Show running automations
113
101
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openbroker",
3
3
  "name": "OpenBroker — Hyperliquid Trading",
4
- "version": "1.0.66",
4
+ "version": "1.0.68",
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.66",
3
+ "version": "1.0.68",
4
4
  "description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,11 +39,6 @@
39
39
  "bracket": "tsx scripts/operations/bracket.ts",
40
40
  "chase": "tsx scripts/operations/chase.ts",
41
41
  "funding-scan": "tsx scripts/info/funding-scan.ts",
42
- "funding-arb": "tsx scripts/strategies/funding-arb.ts",
43
- "grid": "tsx scripts/strategies/grid.ts",
44
- "dca": "tsx scripts/strategies/dca.ts",
45
- "mm-spread": "tsx scripts/strategies/mm-spread.ts",
46
- "mm-maker": "tsx scripts/strategies/mm-maker.ts",
47
42
  "prepublishOnly": "npm run test:cli",
48
43
  "test:cli": "node --import tsx bin/cli.ts --help"
49
44
  },
@@ -62,11 +57,11 @@
62
57
  },
63
58
  "repository": {
64
59
  "type": "git",
65
- "url": "git+https://github.com/aurracloud/openbroker.git"
60
+ "url": "git+https://github.com/aurracloud/open-broker.git"
66
61
  },
67
- "homepage": "https://github.com/aurracloud/openbroker#readme",
62
+ "homepage": "https://github.com/aurracloud/open-broker#readme",
68
63
  "bugs": {
69
- "url": "https://github.com/aurracloud/openbroker/issues"
64
+ "url": "https://github.com/aurracloud/open-broker/issues"
70
65
  },
71
66
  "author": "aurracloud",
72
67
  "keywords": [
@@ -1,7 +1,7 @@
1
1
  // CLI entry point for `openbroker auto` commands
2
2
 
3
3
  import { parseArgs } from '../core/utils.js';
4
- import { resolveScriptPath, listAutomations, ensureAutomationsDir } from './loader.js';
4
+ import { resolveScriptPath, resolveExamplePath, listAutomations, listExamples, loadExampleConfigs, ensureAutomationsDir } from './loader.js';
5
5
  import { startAutomation, getRunningAutomations, getRegisteredAutomations } from './runtime.js';
6
6
  import { unregisterAutomation, cleanRegistry } from './registry.js';
7
7
 
@@ -11,12 +11,16 @@ OpenBroker Automations — event-driven trading scripts
11
11
 
12
12
  Usage:
13
13
  openbroker auto run <script> [options] Run an automation script
14
+ openbroker auto run --example <name> Run a bundled example automation
15
+ openbroker auto examples List bundled example automations
14
16
  openbroker auto stop <id> Unregister an automation (won't restart)
15
17
  openbroker auto list List available automations
16
18
  openbroker auto status Show running automations
17
19
  openbroker auto clean Remove stale entries from registry
18
20
 
19
21
  Options (for run):
22
+ --example <name> Run a bundled example (dca, grid, funding-arb, mm-spread, mm-maker)
23
+ --set key=value Set config values (repeatable, auto-parses numbers/booleans)
20
24
  --dry Intercept write methods (no real trades)
21
25
  --verbose Show debug output
22
26
  --id <name> Custom automation ID (default: filename)
@@ -25,6 +29,7 @@ Options (for run):
25
29
  Scripts are loaded from:
26
30
  1. Absolute or relative path
27
31
  2. ~/.openbroker/automations/<name>.ts
32
+ 3. Bundled examples (via --example)
28
33
 
29
34
  Writing an automation:
30
35
  export default function(api) {
@@ -37,21 +42,66 @@ Events: tick, price_change, funding_update, position_opened,
37
42
  position_closed, position_changed, pnl_threshold, margin_warning
38
43
 
39
44
  Examples:
40
- openbroker auto run my-strategy --dry # Test without trading
41
- openbroker auto run ./funding-scalp.ts # Run from path
42
- openbroker auto list # Show available scripts
45
+ openbroker auto run --example dca --set coin=BTC --set amount=50 --dry
46
+ openbroker auto run --example grid --set coin=ETH --set lower=3000 --set upper=4000
47
+ openbroker auto run my-strategy --dry
48
+ openbroker auto examples
43
49
  `);
44
50
  }
45
51
 
46
- async function runCommand(args: Record<string, string | boolean>, positional: string[]) {
52
+ /** Parse --set key=value flags from raw args, return parsed config object */
53
+ function parseSetFlags(rawArgs: string[]): Record<string, unknown> {
54
+ const config: Record<string, unknown> = {};
55
+
56
+ for (let i = 0; i < rawArgs.length; i++) {
57
+ if (rawArgs[i] === '--set' && i + 1 < rawArgs.length) {
58
+ const pair = rawArgs[i + 1];
59
+ const eqIdx = pair.indexOf('=');
60
+ if (eqIdx === -1) {
61
+ console.error(`Error: --set requires key=value format, got: ${pair}`);
62
+ process.exit(1);
63
+ }
64
+ const key = pair.slice(0, eqIdx);
65
+ const raw = pair.slice(eqIdx + 1);
66
+
67
+ // Auto-parse numbers and booleans
68
+ if (raw === 'true') config[key] = true;
69
+ else if (raw === 'false') config[key] = false;
70
+ else if (raw !== '' && !isNaN(Number(raw))) config[key] = Number(raw);
71
+ else config[key] = raw;
72
+
73
+ i++; // skip the value
74
+ }
75
+ }
76
+
77
+ return config;
78
+ }
79
+
80
+ /** Strip --set key=value pairs from raw args so parseArgs doesn't see them */
81
+ function stripSetFlags(rawArgs: string[]): string[] {
82
+ const result: string[] = [];
83
+ for (let i = 0; i < rawArgs.length; i++) {
84
+ if (rawArgs[i] === '--set' && i + 1 < rawArgs.length) {
85
+ i++; // skip --set and its value
86
+ } else {
87
+ result.push(rawArgs[i]);
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+
93
+ async function runCommand(args: Record<string, string | boolean>, positional: string[], initialState: Record<string, unknown>) {
94
+ const exampleName = args.example ? String(args.example) : undefined;
47
95
  const scriptName = positional[0];
48
- if (!scriptName) {
49
- console.error('Error: script name or path required');
96
+
97
+ if (!scriptName && !exampleName) {
98
+ console.error('Error: script name or path required (or use --example <name>)');
50
99
  console.log('Usage: openbroker auto run <script> [--dry] [--verbose]');
100
+ console.log(' openbroker auto run --example <name> [--set key=value] [--dry]');
51
101
  process.exit(1);
52
102
  }
53
103
 
54
- const scriptPath = resolveScriptPath(scriptName);
104
+ const scriptPath = exampleName ? resolveExamplePath(exampleName) : resolveScriptPath(scriptName!);
55
105
  const dryRun = args.dry === true;
56
106
  const verbose = args.verbose === true;
57
107
  const pollIntervalMs = args.poll ? parseInt(String(args.poll), 10) : 10_000;
@@ -68,6 +118,7 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
68
118
  dryRun,
69
119
  verbose,
70
120
  pollIntervalMs,
121
+ initialState: Object.keys(initialState).length > 0 ? initialState : undefined,
71
122
  });
72
123
 
73
124
  // Graceful shutdown on SIGINT/SIGTERM
@@ -84,22 +135,58 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
84
135
  await new Promise(() => {});
85
136
  }
86
137
 
138
+ async function examplesCommand() {
139
+ const configs = await loadExampleConfigs();
140
+ const names = Object.keys(configs);
141
+
142
+ if (names.length === 0) {
143
+ console.log('No bundled examples found.');
144
+ return;
145
+ }
146
+
147
+ console.log('Bundled example automations:\n');
148
+ for (const name of names) {
149
+ const cfg = configs[name];
150
+ console.log(` ${name}`);
151
+ console.log(` ${cfg.description}\n`);
152
+
153
+ console.log(` Config (--set key=value):`);
154
+ for (const [key, field] of Object.entries(cfg.fields)) {
155
+ const def = JSON.stringify(field.default);
156
+ console.log(` ${key.padEnd(14)} ${field.type.padEnd(8)} ${field.description} (default: ${def})`);
157
+ }
158
+ console.log('');
159
+ }
160
+
161
+ console.log(`Run with: openbroker auto run --example <name> [--set key=value] [--dry]`);
162
+ console.log('Copy to ~/.openbroker/automations/ to customize.');
163
+ }
164
+
87
165
  function listCommand() {
88
166
  ensureAutomationsDir();
89
167
  const automations = listAutomations();
168
+ const examples = listExamples();
90
169
 
91
- if (automations.length === 0) {
170
+ if (automations.length === 0 && examples.length === 0) {
92
171
  console.log('No automations found in ~/.openbroker/automations/');
93
172
  console.log('\nCreate a .ts file there with:');
94
173
  console.log(' export default function(api) { ... }');
174
+ console.log('\nOr run a bundled example: openbroker auto examples');
95
175
  return;
96
176
  }
97
177
 
98
- console.log('Available automations:\n');
99
- for (const a of automations) {
100
- console.log(` ${a.name.padEnd(30)} ${a.path}`);
178
+ if (automations.length > 0) {
179
+ console.log('User automations (~/.openbroker/automations/):\n');
180
+ for (const a of automations) {
181
+ console.log(` ${a.name.padEnd(30)} ${a.path}`);
182
+ }
183
+ console.log(`\nRun with: openbroker auto run <name>`);
184
+ }
185
+
186
+ if (examples.length > 0) {
187
+ if (automations.length > 0) console.log('');
188
+ console.log(`${examples.length} bundled examples available — run: openbroker auto examples`);
101
189
  }
102
- console.log(`\nRun with: openbroker auto run <name>`);
103
190
  }
104
191
 
105
192
  function statusCommand() {
@@ -192,19 +279,23 @@ async function main() {
192
279
  const subcommand = rawArgs[0];
193
280
  const restArgs = rawArgs.slice(1);
194
281
 
282
+ // Parse --set flags before stripping them
283
+ const initialState = parseSetFlags(restArgs);
284
+ const cleanedArgs = stripSetFlags(restArgs);
285
+
195
286
  // Extract positional args (non-flag args)
196
287
  const positional: string[] = [];
197
288
  const flagArgs: string[] = [];
198
- for (let i = 0; i < restArgs.length; i++) {
199
- if (restArgs[i].startsWith('--')) {
200
- flagArgs.push(restArgs[i]);
289
+ for (let i = 0; i < cleanedArgs.length; i++) {
290
+ if (cleanedArgs[i].startsWith('--')) {
291
+ flagArgs.push(cleanedArgs[i]);
201
292
  // If next arg doesn't start with --, it's a flag value
202
- if (i + 1 < restArgs.length && !restArgs[i + 1].startsWith('--')) {
203
- flagArgs.push(restArgs[i + 1]);
293
+ if (i + 1 < cleanedArgs.length && !cleanedArgs[i + 1].startsWith('--')) {
294
+ flagArgs.push(cleanedArgs[i + 1]);
204
295
  i++;
205
296
  }
206
297
  } else {
207
- positional.push(restArgs[i]);
298
+ positional.push(cleanedArgs[i]);
208
299
  }
209
300
  }
210
301
 
@@ -212,7 +303,7 @@ async function main() {
212
303
 
213
304
  switch (subcommand) {
214
305
  case 'run':
215
- await runCommand(args, positional);
306
+ await runCommand(args, positional, initialState);
216
307
  break;
217
308
  case 'stop':
218
309
  stopCommand(positional);
@@ -220,6 +311,9 @@ async function main() {
220
311
  case 'list':
221
312
  listCommand();
222
313
  break;
314
+ case 'examples':
315
+ await examplesCommand();
316
+ break;
223
317
  case 'status':
224
318
  statusCommand();
225
319
  break;
@@ -0,0 +1,72 @@
1
+ // DCA (Dollar Cost Averaging) — Buy fixed USD amounts at regular intervals
2
+
3
+ import type { AutomationAPI, AutomationConfig } from '../types.js';
4
+
5
+ export const config: AutomationConfig = {
6
+ description: 'Dollar cost averaging — buy fixed USD amounts at regular intervals',
7
+ fields: {
8
+ coin: { type: 'string', description: 'Asset to accumulate', default: 'HYPE' },
9
+ amount: { type: 'number', description: 'USD per purchase', default: 100 },
10
+ interval: { type: 'number', description: 'Milliseconds between buys (3600000 = 1h)', default: 3_600_000 },
11
+ count: { type: 'number', description: 'Total number of purchases', default: 24 },
12
+ },
13
+ };
14
+
15
+ export default function dca(api: AutomationAPI) {
16
+ const COIN = api.state.get<string>('coin', 'HYPE')!;
17
+ const AMOUNT_USD = api.state.get<number>('amount', 100)!;
18
+ const INTERVAL_MS = api.state.get<number>('interval', 3_600_000)!;
19
+ const MAX_PURCHASES = api.state.get<number>('count', 24)!;
20
+
21
+ let purchased = api.state.get<number>('purchased', 0)!;
22
+ let totalSpent = api.state.get<number>('totalSpent', 0)!;
23
+ let totalAcquired = api.state.get<number>('totalAcquired', 0)!;
24
+
25
+ api.onStart(() => {
26
+ api.log.info(`DCA: ${MAX_PURCHASES} buys of $${AMOUNT_USD} ${COIN} every ${INTERVAL_MS / 60000}m`);
27
+ api.log.info(`Progress: ${purchased}/${MAX_PURCHASES} completed`);
28
+ });
29
+
30
+ api.every(INTERVAL_MS, async () => {
31
+ if (purchased >= MAX_PURCHASES) {
32
+ api.log.info(`DCA complete: ${purchased} purchases, $${totalSpent.toFixed(2)} spent, ${totalAcquired.toFixed(6)} ${COIN} acquired`);
33
+ return;
34
+ }
35
+
36
+ const mids = await api.client.getAllMids();
37
+ const price = parseFloat(mids[COIN]);
38
+ if (!price) {
39
+ api.log.warn(`No price for ${COIN}, skipping`);
40
+ return;
41
+ }
42
+
43
+ const size = AMOUNT_USD / price;
44
+ api.log.info(`[${purchased + 1}/${MAX_PURCHASES}] Buying ~$${AMOUNT_USD} of ${COIN} @ $${price.toFixed(2)}`);
45
+
46
+ const response = await api.client.marketOrder(COIN, true, size);
47
+ if (response.status === 'ok' && response.response && typeof response.response === 'object') {
48
+ const status = response.response.data.statuses[0];
49
+ if (status?.filled) {
50
+ const filledSize = parseFloat(status.filled.totalSz);
51
+ const avgPx = parseFloat(status.filled.avgPx);
52
+ totalSpent += filledSize * avgPx;
53
+ totalAcquired += filledSize;
54
+ purchased++;
55
+
56
+ api.state.set('purchased', purchased);
57
+ api.state.set('totalSpent', totalSpent);
58
+ api.state.set('totalAcquired', totalAcquired);
59
+
60
+ const avgPrice = totalSpent / totalAcquired;
61
+ api.log.info(`Filled ${filledSize.toFixed(6)} @ $${avgPx.toFixed(2)} | Avg: $${avgPrice.toFixed(2)} | Total: ${totalAcquired.toFixed(6)} ${COIN}`);
62
+ } else if (status?.error) {
63
+ api.log.error(`Order failed: ${status.error}`);
64
+ }
65
+ }
66
+ });
67
+
68
+ api.onStop(() => {
69
+ const avgPrice = totalAcquired > 0 ? totalSpent / totalAcquired : 0;
70
+ api.log.info(`DCA stopped: ${purchased}/${MAX_PURCHASES} | Spent: $${totalSpent.toFixed(2)} | Acquired: ${totalAcquired.toFixed(6)} ${COIN} | Avg: $${avgPrice.toFixed(2)}`);
71
+ });
72
+ }