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 +4 -1
- package/SKILL.md +55 -62
- package/bin/cli.ts +2 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -9
- package/scripts/auto/cli.ts +114 -20
- package/scripts/auto/examples/dca.ts +72 -0
- package/scripts/auto/examples/funding-arb.ts +98 -0
- package/scripts/auto/examples/grid.ts +135 -0
- package/scripts/auto/examples/mm-maker.ts +125 -0
- package/scripts/auto/examples/mm-spread.ts +115 -0
- package/scripts/auto/loader.ts +47 -1
- package/scripts/auto/runtime.ts +13 -0
- package/scripts/auto/types.ts +14 -0
- package/scripts/info/orders.ts +70 -0
- package/scripts/plugin/tools.ts +51 -9
- package/scripts/strategies/dca.ts +0 -292
- package/scripts/strategies/funding-arb.ts +0 -352
- package/scripts/strategies/grid.ts +0 -397
- package/scripts/strategies/mm-maker.ts +0 -411
- package/scripts/strategies/mm-spread.ts +0 -402
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
-
|
|
921
|
-
-
|
|
922
|
-
-
|
|
923
|
-
-
|
|
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
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbroker",
|
|
3
|
-
"version": "1.0.
|
|
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/
|
|
60
|
+
"url": "git+https://github.com/aurracloud/open-broker.git"
|
|
66
61
|
},
|
|
67
|
-
"homepage": "https://github.com/aurracloud/
|
|
62
|
+
"homepage": "https://github.com/aurracloud/open-broker#readme",
|
|
68
63
|
"bugs": {
|
|
69
|
-
"url": "https://github.com/aurracloud/
|
|
64
|
+
"url": "https://github.com/aurracloud/open-broker/issues"
|
|
70
65
|
},
|
|
71
66
|
"author": "aurracloud",
|
|
72
67
|
"keywords": [
|
package/scripts/auto/cli.ts
CHANGED
|
@@ -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
|
|
41
|
-
openbroker auto run
|
|
42
|
-
openbroker auto
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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 <
|
|
199
|
-
if (
|
|
200
|
-
flagArgs.push(
|
|
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 <
|
|
203
|
-
flagArgs.push(
|
|
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(
|
|
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
|
+
}
|