openbroker 1.0.67 → 1.0.69
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 +102 -57
- 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/plugin/tools.ts +20 -7
- 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/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.69", "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
|
|
|
@@ -234,7 +234,6 @@ All trading commands support HIP-3 assets using `dex:COIN` syntax:
|
|
|
234
234
|
openbroker buy --coin xyz:CL --size 1 # Buy crude oil on xyz dex
|
|
235
235
|
openbroker sell --coin xyz:BRENTOIL --size 1 # Sell brent oil
|
|
236
236
|
openbroker limit --coin xyz:GOLD --side buy --size 0.1 --price 2500
|
|
237
|
-
openbroker funding-arb --coin xyz:CL --size 5000 # Funding arb on HIP-3
|
|
238
237
|
```
|
|
239
238
|
|
|
240
239
|
### Market Orders (Quick)
|
|
@@ -325,53 +324,6 @@ openbroker chase --coin ETH --side buy --size 0.5 --timeout 300
|
|
|
325
324
|
openbroker chase --coin SOL --side buy --size 10 --offset 2 --timeout 60
|
|
326
325
|
```
|
|
327
326
|
|
|
328
|
-
## Trading Strategies
|
|
329
|
-
|
|
330
|
-
### Funding Arbitrage
|
|
331
|
-
```bash
|
|
332
|
-
# Collect funding on ETH if rate > 25% annualized
|
|
333
|
-
openbroker funding-arb --coin ETH --size 5000 --min-funding 25
|
|
334
|
-
|
|
335
|
-
# Run for 24 hours, check every 30 minutes
|
|
336
|
-
openbroker funding-arb --coin BTC --size 10000 --duration 24 --check 30 --dry
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### Grid Trading
|
|
340
|
-
```bash
|
|
341
|
-
# ETH grid from $3000-$4000 with 10 levels, 0.1 ETH per level
|
|
342
|
-
openbroker grid --coin ETH --lower 3000 --upper 4000 --grids 10 --size 0.1
|
|
343
|
-
|
|
344
|
-
# Accumulation grid (buys only)
|
|
345
|
-
openbroker grid --coin BTC --lower 90000 --upper 100000 --grids 5 --size 0.01 --mode long
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### DCA (Dollar Cost Averaging)
|
|
349
|
-
```bash
|
|
350
|
-
# Buy $100 of ETH every hour for 24 hours
|
|
351
|
-
openbroker dca --coin ETH --amount 100 --interval 1h --count 24
|
|
352
|
-
|
|
353
|
-
# Invest $5000 in BTC over 30 days with daily purchases
|
|
354
|
-
openbroker dca --coin BTC --total 5000 --interval 1d --count 30
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### Market Making Spread
|
|
358
|
-
```bash
|
|
359
|
-
# Market make ETH with 0.1 size, 10bps spread
|
|
360
|
-
openbroker mm-spread --coin ETH --size 0.1 --spread 10
|
|
361
|
-
|
|
362
|
-
# Tighter spread with position limit
|
|
363
|
-
openbroker mm-spread --coin BTC --size 0.01 --spread 5 --max-position 0.1
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### Maker-Only MM (ALO orders)
|
|
367
|
-
```bash
|
|
368
|
-
# Market make using ALO (post-only) orders - guarantees maker rebates
|
|
369
|
-
openbroker mm-maker --coin HYPE --size 1 --offset 1
|
|
370
|
-
|
|
371
|
-
# Wider offset for volatile assets
|
|
372
|
-
openbroker mm-maker --coin ETH --size 0.1 --offset 2 --max-position 0.5
|
|
373
|
-
```
|
|
374
|
-
|
|
375
327
|
## Order Types
|
|
376
328
|
|
|
377
329
|
### Limit Orders vs Trigger Orders
|
|
@@ -495,7 +447,24 @@ Without hooks, the watcher still runs and tracks state (accessible via `ob_watch
|
|
|
495
447
|
|
|
496
448
|
## Trading Automations
|
|
497
449
|
|
|
498
|
-
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`
|
|
499
468
|
|
|
500
469
|
### How Automations Work
|
|
501
470
|
|
|
@@ -763,15 +732,86 @@ api.on('margin_warning', async ({ marginUsedPct, equity }) => {
|
|
|
763
732
|
|
|
764
733
|
### Client Methods Available
|
|
765
734
|
|
|
766
|
-
The `api.client` object exposes the full
|
|
735
|
+
The `api.client` object exposes the full `HyperliquidClient`. All `coin` params accept HIP-3 prefixed tickers (e.g. `xyz:CL`). Optional `user` params default to the configured wallet address.
|
|
736
|
+
|
|
737
|
+
#### Trading
|
|
738
|
+
|
|
739
|
+
| Method | Description |
|
|
740
|
+
|--------|-------------|
|
|
741
|
+
| `marketOrder(coin, isBuy, size, slippageBps?, leverage?)` | Market order via IOC limit at mid ± slippage. Returns `OrderResponse` |
|
|
742
|
+
| `limitOrder(coin, isBuy, size, price, tif?, reduceOnly?, leverage?)` | Limit order. `tif`: `'Gtc'` (default), `'Ioc'`, `'Alo'`. Returns `OrderResponse` |
|
|
743
|
+
| `triggerOrder(coin, isBuy, size, triggerPrice, limitPrice, tpsl, reduceOnly?, leverage?)` | Trigger (conditional) order. `tpsl`: `'tp'` or `'sl'`. Activates when price hits `triggerPrice`, then fills as limit at `limitPrice`. Returns `OrderResponse` |
|
|
744
|
+
| `stopLoss(coin, isBuy, size, triggerPrice, slippageBps?)` | Stop loss shortcut. Sets limit price with slippage buffer (default 100 bps / 1%) to ensure fill. `reduceOnly` is always true. Returns `OrderResponse` |
|
|
745
|
+
| `takeProfit(coin, isBuy, size, triggerPrice)` | Take profit shortcut. Limit price = trigger price (favorable direction). `reduceOnly` is always true. Returns `OrderResponse` |
|
|
746
|
+
| `cancel(coin, oid)` | Cancel a single order by numeric OID. Returns `CancelResponse` |
|
|
747
|
+
| `cancelAll(coin?)` | Cancel all open orders. If `coin` is provided, only cancels orders for that asset. Returns `CancelResponse[]` |
|
|
748
|
+
| `order(coin, isBuy, size, price, orderType, reduceOnly?, includeBuilder?, leverage?)` | Low-level order placement. `orderType`: `{ limit: { tif: 'Gtc' | 'Ioc' | 'Alo' } }`. Automatically injects builder fee, rounds price/size, and handles HIP-3 margin setup. Returns `OrderResponse` |
|
|
749
|
+
|
|
750
|
+
#### Market Data
|
|
751
|
+
|
|
752
|
+
| Method | Returns |
|
|
753
|
+
|--------|---------|
|
|
754
|
+
| `getAllMids()` | `Record<string, string>` — mid prices for all assets (main + HIP-3). Key = coin name, value = price string |
|
|
755
|
+
| `getMetaAndAssetCtxs()` | `MetaAndAssetCtxs` — market metadata (universe of assets with `szDecimals`, `maxLeverage`) and asset contexts (funding, open interest, volume, mark/oracle prices) |
|
|
756
|
+
| `getL2Book(coin)` | `{ bids, asks, bestBid, bestAsk, midPrice, spread, spreadBps }` — L2 order book with computed spread |
|
|
757
|
+
| `getRecentTrades(coin)` | `Array<{ coin, side, px, sz, time, hash, tid }>` — recent trade tape. `side`: `'B'` (buy) or `'A'` (sell) |
|
|
758
|
+
| `getCandleSnapshot(coin, interval, startTime, endTime?)` | `Array<{ t, T, s, i, o, c, h, l, v, n }>` — OHLCV candles. `interval`: `'1m'`, `'5m'`, `'15m'`, `'1h'`, `'4h'`, `'1d'`. Times are Unix ms |
|
|
759
|
+
| `getFundingHistory(coin, startTime, endTime?)` | `Array<{ coin, fundingRate, premium, time }>` — historical hourly funding rates |
|
|
760
|
+
| `getPredictedFundings()` | `Array<[coin, Array<[venue, { fundingRate, nextFundingTime }]>]>` — predicted funding rates across all venues |
|
|
761
|
+
| `getPerpDexs()` | `Array<{ name, fullName, deployer } | null>` — list of perp DEXs. Index 0 is `null` (main), rest are HIP-3 |
|
|
762
|
+
| `getAllPerpMetas()` | `Array<{ dexName, meta, assetCtxs }>` — metadata + contexts for every perp DEX (main + all HIP-3) |
|
|
763
|
+
| `getSpotMeta()` | `{ tokens, universe }` — spot market metadata (token info, trading pairs) |
|
|
764
|
+
| `getSpotMetaAndAssetCtxs()` | `{ meta, assetCtxs }` — spot metadata + price/volume contexts |
|
|
765
|
+
| `getTokenDetails(tokenId)` | Token details: supply, deployer, prices. Returns `null` if not found |
|
|
766
|
+
|
|
767
|
+
#### Account
|
|
768
|
+
|
|
769
|
+
| Method | Returns |
|
|
770
|
+
|--------|---------|
|
|
771
|
+
| `getUserStateAll(user?)` | `ClearinghouseState` — full account state across all dexes: `marginSummary` (accountValue, totalMarginUsed, withdrawable), `crossMarginSummary`, and `assetPositions[]` (each with `position.coin`, `.szi`, `.entryPx`, `.unrealizedPnl`, `.positionValue`, `.leverage`, `.marginUsed`, `.liquidationPx`) |
|
|
772
|
+
| `getUserState(user?, dex?)` | `ClearinghouseState` — account state for a single dex (omit `dex` for main perps) |
|
|
773
|
+
| `getOpenOrders(user?)` | `OpenOrder[]` — all open orders across all dexes. Each: `{ coin, side, limitPx, sz, oid, timestamp, orderType }` |
|
|
774
|
+
| `getUserFills(user?, aggregateByTime?)` | `Array<{ coin, px, sz, side, time, closedPnl, fee, oid, tid, crossed, builderFee }>` — trade fill history. `side`: `'B'` (buy) or `'A'` (sell) |
|
|
775
|
+
| `getHistoricalOrders(user?)` | `Array<{ order: { coin, side, limitPx, sz, origSz, oid, timestamp, orderType, tif, triggerCondition, triggerPx, isTrigger, isPositionTpsl, reduceOnly }, status, statusTimestamp }>` — all orders (filled, cancelled, etc.) |
|
|
776
|
+
| `getOrderStatus(oid, user?)` | `{ status, order? }` — status of a specific order by numeric OID or string CLOID |
|
|
777
|
+
| `getUserFunding(user?, startTime?, endTime?)` | `Array<{ time, hash, delta: { coin, usdc, szi, fundingRate } }>` — funding payments received/paid |
|
|
778
|
+
| `getUserFees(user?)` | `{ dailyUserVlm, feeSchedule, userCrossRate, userAddRate, activeReferralDiscount, activeStakingDiscount }` — fee tier, rates, and volume |
|
|
779
|
+
| `getUserRateLimit(user?)` | `{ cumVlm, nRequestsUsed, nRequestsCap, nRequestsSurplus }` — API rate limit status |
|
|
780
|
+
| `getSpotBalances(user?)` | `{ balances: Array<{ coin, token, hold, total, entryNtl }> }` — spot token balances |
|
|
781
|
+
| `getSubAccounts(user?)` | `Array<{ subAccountUser, name }>` — sub-accounts for a master wallet |
|
|
782
|
+
| `getAccountMode(user?)` | `string` — account abstraction mode: `'standard'`, `'unified'`, `'portfolio'`, or `'dexAbstraction'` |
|
|
783
|
+
| `isUnifiedAccount(user?)` | `boolean` — `true` if unified or portfolio margin (shared USDC across dexes) |
|
|
784
|
+
|
|
785
|
+
#### Leverage & Config
|
|
786
|
+
|
|
787
|
+
| Method | Description |
|
|
788
|
+
|--------|-------------|
|
|
789
|
+
| `updateLeverage(coin, leverage, isCross?)` | Set leverage. `isCross` defaults to `true` (cross margin). HIP-3 assets are forced to isolated and clamped to their max leverage |
|
|
790
|
+
| `approveBuilderFee(maxFeeRate?, builder?)` | Approve builder fee (must be called from main wallet, not API wallet). Default rate: `'0.1%'` |
|
|
791
|
+
| `getMaxBuilderFee(user?, builder?)` | Check approved builder fee. Returns fee string (e.g. `'0.01%'`) or `null` if not approved |
|
|
792
|
+
|
|
793
|
+
#### Utility Properties
|
|
767
794
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
795
|
+
| Property / Method | Description |
|
|
796
|
+
|-------------------|-------------|
|
|
797
|
+
| `getAssetIndex(coin)` | Get numeric asset index for a coin (used internally for order wire) |
|
|
798
|
+
| `getSzDecimals(coin)` | Get size decimal precision for a coin |
|
|
799
|
+
| `isHip3(coin)` | Check if a coin is a HIP-3 asset |
|
|
800
|
+
| `getCoinDex(coin)` | Get dex name for a coin (`null` for main perps) |
|
|
801
|
+
| `getAllAssetNames()` | Get all known asset names (main + HIP-3) |
|
|
802
|
+
| `getHip3AssetNames()` | Get only HIP-3 asset names |
|
|
803
|
+
| `invalidateMetaCache()` | Force refresh of market metadata on next call |
|
|
771
804
|
|
|
772
|
-
|
|
805
|
+
#### Utility Functions (`api.utils`)
|
|
773
806
|
|
|
774
|
-
|
|
807
|
+
| Function | Description |
|
|
808
|
+
|----------|-------------|
|
|
809
|
+
| `roundPrice(price, szDecimals, isSpot?)` | Round price to 5 significant figures (max 6 decimals perp, 8 spot) |
|
|
810
|
+
| `roundSize(size, szDecimals)` | Round size to asset-specific decimal precision |
|
|
811
|
+
| `sleep(ms)` | Promise-based delay |
|
|
812
|
+
| `normalizeCoin(coin)` | Normalize coin name (uppercase, trim whitespace) |
|
|
813
|
+
| `formatUsd(amount)` | Format number as USD string (e.g. `$1,234.56`) |
|
|
814
|
+
| `annualizeFundingRate(hourlyRate)` | Convert hourly funding rate to annualized percentage |
|
|
775
815
|
|
|
776
816
|
### Example: Price Breakout
|
|
777
817
|
|
|
@@ -897,14 +937,17 @@ export default function(api) {
|
|
|
897
937
|
openbroker auto run my-strategy --dry # Test without trading
|
|
898
938
|
openbroker auto run ./funding-scalp.ts # Run from path
|
|
899
939
|
openbroker auto run my-strategy --poll 5000 # Poll every 5s
|
|
940
|
+
openbroker auto run --example dca --set coin=HYPE --set amount=50 --dry # Run bundled example
|
|
941
|
+
openbroker auto examples # List bundled examples with config
|
|
900
942
|
openbroker auto list # Show available scripts
|
|
901
943
|
openbroker auto status # Show running automations
|
|
902
944
|
```
|
|
903
945
|
|
|
904
946
|
**Plugin tools (for OpenClaw agents):**
|
|
905
947
|
- `ob_auto_run` — `{ "script": "funding-scalp", "dry": true }` — start an automation
|
|
948
|
+
- `ob_auto_run` — `{ "example": "dca", "config": { "coin": "HYPE", "amount": 50 }, "dry": true }` — run a bundled example
|
|
906
949
|
- `ob_auto_stop` — `{ "id": "funding-scalp" }` — stop a running automation
|
|
907
|
-
- `ob_auto_list` — `{}` — list available and running automations
|
|
950
|
+
- `ob_auto_list` — `{}` — list available automations, bundled examples with config schemas, and running automations
|
|
908
951
|
|
|
909
952
|
**Options:**
|
|
910
953
|
| Flag | Description | Default |
|
|
@@ -913,6 +956,8 @@ openbroker auto status # Show running automations
|
|
|
913
956
|
| `--verbose` | Show debug output | false |
|
|
914
957
|
| `--id <name>` | Custom automation ID | filename |
|
|
915
958
|
| `--poll <ms>` | Poll interval in milliseconds | 10000 |
|
|
959
|
+
| `--example <name>` | Run a bundled example automation | - |
|
|
960
|
+
| `--set key=value` | Set config values (repeatable) | - |
|
|
916
961
|
|
|
917
962
|
**Guidelines for agents writing automations:**
|
|
918
963
|
|
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.69",
|
|
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
|
+
}
|