openbroker 1.0.67 → 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/SKILL.md +25 -51
- 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.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
|
|
|
@@ -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
|
|
|
@@ -897,14 +866,17 @@ export default function(api) {
|
|
|
897
866
|
openbroker auto run my-strategy --dry # Test without trading
|
|
898
867
|
openbroker auto run ./funding-scalp.ts # Run from path
|
|
899
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
|
|
900
871
|
openbroker auto list # Show available scripts
|
|
901
872
|
openbroker auto status # Show running automations
|
|
902
873
|
```
|
|
903
874
|
|
|
904
875
|
**Plugin tools (for OpenClaw agents):**
|
|
905
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
|
|
906
878
|
- `ob_auto_stop` — `{ "id": "funding-scalp" }` — stop a running automation
|
|
907
|
-
- `ob_auto_list` — `{}` — list available and running automations
|
|
879
|
+
- `ob_auto_list` — `{}` — list available automations, bundled examples with config schemas, and running automations
|
|
908
880
|
|
|
909
881
|
**Options:**
|
|
910
882
|
| Flag | Description | Default |
|
|
@@ -913,6 +885,8 @@ openbroker auto status # Show running automations
|
|
|
913
885
|
| `--verbose` | Show debug output | false |
|
|
914
886
|
| `--id <name>` | Custom automation ID | filename |
|
|
915
887
|
| `--poll <ms>` | Poll interval in milliseconds | 10000 |
|
|
888
|
+
| `--example <name>` | Run a bundled example automation | - |
|
|
889
|
+
| `--set key=value` | Set config values (repeatable) | - |
|
|
916
890
|
|
|
917
891
|
**Guidelines for agents writing automations:**
|
|
918
892
|
|
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
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Funding Arbitrage — Collect funding by positioning opposite to the crowd
|
|
2
|
+
|
|
3
|
+
import type { AutomationAPI, AutomationConfig } from '../types.js';
|
|
4
|
+
|
|
5
|
+
export const config: AutomationConfig = {
|
|
6
|
+
description: 'Funding arbitrage — collect funding by positioning opposite to the crowd',
|
|
7
|
+
fields: {
|
|
8
|
+
coin: { type: 'string', description: 'Asset to trade', default: 'HYPE' },
|
|
9
|
+
sizeUsd: { type: 'number', description: 'Position size in USD notional', default: 5000 },
|
|
10
|
+
minFunding: { type: 'number', description: 'Min annualized % to enter', default: 20 },
|
|
11
|
+
maxFunding: { type: 'number', description: 'Max annualized % — avoid squeezes', default: 200 },
|
|
12
|
+
closeAt: { type: 'number', description: 'Close when funding drops below this %', default: 5 },
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default function fundingArb(api: AutomationAPI) {
|
|
17
|
+
const COIN = api.state.get<string>('coin', 'HYPE')!;
|
|
18
|
+
const SIZE_USD = api.state.get<number>('sizeUsd', 5000)!;
|
|
19
|
+
const MIN_FUNDING = api.state.get<number>('minFunding', 20)!;
|
|
20
|
+
const MAX_FUNDING = api.state.get<number>('maxFunding', 200)!;
|
|
21
|
+
const CLOSE_AT = api.state.get<number>('closeAt', 5)!;
|
|
22
|
+
|
|
23
|
+
let inPosition = api.state.get<boolean>('inPosition', false)!;
|
|
24
|
+
let positionSide = api.state.get<string>('positionSide', '')!;
|
|
25
|
+
let entryPrice = api.state.get<number>('entryPrice', 0)!;
|
|
26
|
+
let positionSize = api.state.get<number>('positionSize', 0)!;
|
|
27
|
+
let totalFunding = api.state.get<number>('totalFunding', 0)!;
|
|
28
|
+
|
|
29
|
+
api.onStart(() => {
|
|
30
|
+
api.log.info(`Funding arb: ${COIN} | $${SIZE_USD} | Enter >${MIN_FUNDING}% | Close <${CLOSE_AT}%`);
|
|
31
|
+
if (inPosition) {
|
|
32
|
+
api.log.info(`Resuming ${positionSide} position: ${positionSize.toFixed(6)} @ $${entryPrice.toFixed(2)}`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
api.on('funding_update', async ({ coin, annualized }) => {
|
|
37
|
+
if (coin !== COIN) return;
|
|
38
|
+
|
|
39
|
+
const annualizedPct = annualized * 100;
|
|
40
|
+
const absAnnualized = Math.abs(annualizedPct);
|
|
41
|
+
|
|
42
|
+
if (inPosition) {
|
|
43
|
+
// Check if we should close
|
|
44
|
+
const shouldClose =
|
|
45
|
+
(positionSide === 'short' && annualizedPct < CLOSE_AT) ||
|
|
46
|
+
(positionSide === 'long' && annualizedPct > -CLOSE_AT);
|
|
47
|
+
|
|
48
|
+
if (shouldClose) {
|
|
49
|
+
api.log.info(`Funding dropped to ${annualizedPct.toFixed(2)}% (below ${CLOSE_AT}%), closing ${positionSide}`);
|
|
50
|
+
const closeIsBuy = positionSide === 'short';
|
|
51
|
+
await api.client.marketOrder(coin, closeIsBuy, positionSize);
|
|
52
|
+
|
|
53
|
+
inPosition = false;
|
|
54
|
+
api.state.set('inPosition', false);
|
|
55
|
+
api.log.info(`Position closed. Funding collected: ~$${totalFunding.toFixed(2)}`);
|
|
56
|
+
} else {
|
|
57
|
+
api.log.debug(`${coin} funding: ${annualizedPct.toFixed(2)}% — holding ${positionSide}`);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Not in position — check if we should enter
|
|
63
|
+
if (absAnnualized >= MIN_FUNDING && absAnnualized <= MAX_FUNDING) {
|
|
64
|
+
const shouldShort = annualizedPct > 0; // Positive = longs pay shorts
|
|
65
|
+
const side = shouldShort ? 'short' : 'long';
|
|
66
|
+
|
|
67
|
+
const mids = await api.client.getAllMids();
|
|
68
|
+
const price = parseFloat(mids[coin]);
|
|
69
|
+
const size = SIZE_USD / price;
|
|
70
|
+
|
|
71
|
+
api.log.info(`Funding at ${annualizedPct.toFixed(2)}% — opening ${side} ${size.toFixed(6)} ${coin}`);
|
|
72
|
+
const response = await api.client.marketOrder(coin, !shouldShort, size);
|
|
73
|
+
|
|
74
|
+
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
75
|
+
const status = response.response.data.statuses[0];
|
|
76
|
+
if (status?.filled) {
|
|
77
|
+
positionSize = parseFloat(status.filled.totalSz);
|
|
78
|
+
entryPrice = parseFloat(status.filled.avgPx);
|
|
79
|
+
positionSide = side;
|
|
80
|
+
inPosition = true;
|
|
81
|
+
|
|
82
|
+
api.state.set('inPosition', true);
|
|
83
|
+
api.state.set('positionSide', side);
|
|
84
|
+
api.state.set('entryPrice', entryPrice);
|
|
85
|
+
api.state.set('positionSize', positionSize);
|
|
86
|
+
|
|
87
|
+
api.log.info(`Entered ${side} ${positionSize.toFixed(6)} @ $${entryPrice.toFixed(2)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
api.onStop(() => {
|
|
94
|
+
if (inPosition) {
|
|
95
|
+
api.log.warn(`Stopping with open ${positionSide} position of ${positionSize.toFixed(6)} ${COIN} — close manually if desired`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|