openbroker 1.3.2 → 1.5.0
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/CHANGELOG.md +23 -0
- package/dist/auto/audit.d.ts +57 -0
- package/dist/auto/audit.d.ts.map +1 -0
- package/dist/auto/audit.js +407 -0
- package/dist/auto/cli.d.ts +2 -0
- package/dist/auto/cli.d.ts.map +1 -0
- package/dist/auto/cli.js +423 -0
- package/dist/auto/events.d.ts +11 -0
- package/dist/auto/events.d.ts.map +1 -0
- package/dist/auto/events.js +36 -0
- package/dist/auto/examples/dca.d.ts +4 -0
- package/dist/auto/examples/dca.d.ts.map +1 -0
- package/dist/auto/examples/dca.js +60 -0
- package/dist/auto/examples/funding-arb.d.ts +4 -0
- package/dist/auto/examples/funding-arb.d.ts.map +1 -0
- package/dist/auto/examples/funding-arb.js +81 -0
- package/dist/auto/examples/grid.d.ts +4 -0
- package/dist/auto/examples/grid.d.ts.map +1 -0
- package/dist/auto/examples/grid.js +114 -0
- package/dist/auto/examples/mm-maker.d.ts +4 -0
- package/dist/auto/examples/mm-maker.d.ts.map +1 -0
- package/dist/auto/examples/mm-maker.js +131 -0
- package/dist/auto/examples/mm-spread.d.ts +4 -0
- package/dist/auto/examples/mm-spread.d.ts.map +1 -0
- package/dist/auto/examples/mm-spread.js +119 -0
- package/dist/auto/examples/price-alert.d.ts +4 -0
- package/dist/auto/examples/price-alert.d.ts.map +1 -0
- package/dist/auto/examples/price-alert.js +85 -0
- package/dist/auto/keep-awake.d.ts +11 -0
- package/dist/auto/keep-awake.d.ts.map +1 -0
- package/dist/auto/keep-awake.js +70 -0
- package/dist/auto/loader.d.ts +22 -0
- package/dist/auto/loader.d.ts.map +1 -0
- package/dist/auto/loader.js +127 -0
- package/dist/auto/prune.d.ts +40 -0
- package/dist/auto/prune.d.ts.map +1 -0
- package/dist/auto/prune.js +204 -0
- package/dist/auto/registry.d.ts +24 -0
- package/dist/auto/registry.d.ts.map +1 -0
- package/dist/auto/registry.js +93 -0
- package/dist/auto/report.d.ts +3 -0
- package/dist/auto/report.d.ts.map +1 -0
- package/dist/auto/report.js +385 -0
- package/dist/auto/runtime.d.ts +33 -0
- package/dist/auto/runtime.d.ts.map +1 -0
- package/dist/auto/runtime.js +844 -0
- package/dist/auto/types.d.ts +236 -0
- package/dist/auto/types.d.ts.map +1 -0
- package/dist/auto/types.js +3 -0
- package/dist/core/client.d.ts +691 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +2061 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +143 -0
- package/dist/core/types.d.ts +228 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/utils.d.ts +61 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +142 -0
- package/dist/core/ws.d.ts +121 -0
- package/dist/core/ws.d.ts.map +1 -0
- package/dist/core/ws.js +222 -0
- package/dist/info/account.d.ts +3 -0
- package/dist/info/account.d.ts.map +1 -0
- package/dist/info/account.js +198 -0
- package/dist/info/all-markets.d.ts +3 -0
- package/dist/info/all-markets.d.ts.map +1 -0
- package/dist/info/all-markets.js +272 -0
- package/dist/info/candles.d.ts +3 -0
- package/dist/info/candles.d.ts.map +1 -0
- package/dist/info/candles.js +120 -0
- package/dist/info/fees.d.ts +3 -0
- package/dist/info/fees.d.ts.map +1 -0
- package/dist/info/fees.js +87 -0
- package/dist/info/fills.d.ts +3 -0
- package/dist/info/fills.d.ts.map +1 -0
- package/dist/info/fills.js +105 -0
- package/dist/info/funding-history.d.ts +3 -0
- package/dist/info/funding-history.d.ts.map +1 -0
- package/dist/info/funding-history.js +98 -0
- package/dist/info/funding-scan.d.ts +3 -0
- package/dist/info/funding-scan.d.ts.map +1 -0
- package/dist/info/funding-scan.js +178 -0
- package/dist/info/funding.d.ts +3 -0
- package/dist/info/funding.d.ts.map +1 -0
- package/dist/info/funding.js +158 -0
- package/dist/info/markets.d.ts +3 -0
- package/dist/info/markets.d.ts.map +1 -0
- package/dist/info/markets.js +178 -0
- package/dist/info/order-status.d.ts +3 -0
- package/dist/info/order-status.d.ts.map +1 -0
- package/dist/info/order-status.js +85 -0
- package/dist/info/orders.d.ts +3 -0
- package/dist/info/orders.d.ts.map +1 -0
- package/dist/info/orders.js +162 -0
- package/dist/info/outcomes.d.ts +3 -0
- package/dist/info/outcomes.d.ts.map +1 -0
- package/dist/info/outcomes.js +175 -0
- package/dist/info/positions.d.ts +3 -0
- package/dist/info/positions.d.ts.map +1 -0
- package/dist/info/positions.js +127 -0
- package/dist/info/rate-limit.d.ts +3 -0
- package/dist/info/rate-limit.d.ts.map +1 -0
- package/dist/info/rate-limit.js +58 -0
- package/dist/info/search-markets.d.ts +3 -0
- package/dist/info/search-markets.d.ts.map +1 -0
- package/dist/info/search-markets.js +296 -0
- package/dist/info/spot.d.ts +3 -0
- package/dist/info/spot.d.ts.map +1 -0
- package/dist/info/spot.js +192 -0
- package/dist/info/trades.d.ts +3 -0
- package/dist/info/trades.d.ts.map +1 -0
- package/dist/info/trades.js +97 -0
- package/dist/lib.d.ts +14 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +17 -0
- package/dist/operations/bracket.d.ts +28 -0
- package/dist/operations/bracket.d.ts.map +1 -0
- package/dist/operations/bracket.js +266 -0
- package/dist/operations/cancel.d.ts +3 -0
- package/dist/operations/cancel.d.ts.map +1 -0
- package/dist/operations/cancel.js +107 -0
- package/dist/operations/chase.d.ts +25 -0
- package/dist/operations/chase.d.ts.map +1 -0
- package/dist/operations/chase.js +215 -0
- package/dist/operations/limit-order.d.ts +3 -0
- package/dist/operations/limit-order.d.ts.map +1 -0
- package/dist/operations/limit-order.js +144 -0
- package/dist/operations/market-order.d.ts +3 -0
- package/dist/operations/market-order.d.ts.map +1 -0
- package/dist/operations/market-order.js +153 -0
- package/dist/operations/outcome-order.d.ts +3 -0
- package/dist/operations/outcome-order.d.ts.map +1 -0
- package/dist/operations/outcome-order.js +171 -0
- package/dist/operations/scale.d.ts +3 -0
- package/dist/operations/scale.d.ts.map +1 -0
- package/dist/operations/scale.js +212 -0
- package/dist/operations/set-tpsl.d.ts +3 -0
- package/dist/operations/set-tpsl.d.ts.map +1 -0
- package/dist/operations/set-tpsl.js +277 -0
- package/dist/operations/spot-order.d.ts +3 -0
- package/dist/operations/spot-order.d.ts.map +1 -0
- package/dist/operations/spot-order.js +173 -0
- package/dist/operations/trigger-order.d.ts +3 -0
- package/dist/operations/trigger-order.d.ts.map +1 -0
- package/dist/operations/trigger-order.js +177 -0
- package/dist/operations/twap-cancel.d.ts +3 -0
- package/dist/operations/twap-cancel.d.ts.map +1 -0
- package/dist/operations/twap-cancel.js +57 -0
- package/dist/operations/twap-status.d.ts +3 -0
- package/dist/operations/twap-status.d.ts.map +1 -0
- package/dist/operations/twap-status.js +81 -0
- package/dist/operations/twap.d.ts +3 -0
- package/dist/operations/twap.d.ts.map +1 -0
- package/dist/operations/twap.js +124 -0
- package/dist/setup/approve-builder.d.ts +3 -0
- package/dist/setup/approve-builder.d.ts.map +1 -0
- package/dist/setup/approve-builder.js +155 -0
- package/dist/setup/env.d.ts +4 -0
- package/dist/setup/env.d.ts.map +1 -0
- package/dist/setup/env.js +8 -0
- package/dist/setup/onboard.d.ts +10 -0
- package/dist/setup/onboard.d.ts.map +1 -0
- package/dist/setup/onboard.js +462 -0
- package/package.json +10 -4
- package/scripts/core/client.ts +19 -1
- package/scripts/core/types.ts +7 -0
package/dist/auto/cli.js
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// CLI entry point for `openbroker auto` commands
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { parseArgs } from '../core/utils.js';
|
|
6
|
+
import { resolveScriptPath, resolveExamplePath, listAutomations, listExamples, loadExampleConfigs, ensureAutomationsDir } from './loader.js';
|
|
7
|
+
import { startAutomation, getRunningAutomations, getRegisteredAutomations } from './runtime.js';
|
|
8
|
+
import { unregisterAutomation, cleanRegistry } from './registry.js';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
function printUsage() {
|
|
12
|
+
console.log(`
|
|
13
|
+
OpenBroker Automations — event-driven trading scripts
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
openbroker auto run <script> [options] Run an automation script
|
|
17
|
+
openbroker auto run --example <name> Run a bundled example automation
|
|
18
|
+
openbroker auto report <id> Read the local audit report for an automation
|
|
19
|
+
openbroker auto examples List bundled example automations
|
|
20
|
+
openbroker auto stop <id> Unregister an automation (won't restart)
|
|
21
|
+
openbroker auto list List available automations
|
|
22
|
+
openbroker auto status Show running automations
|
|
23
|
+
openbroker auto clean Remove stale entries from registry + reconcile audit DB
|
|
24
|
+
openbroker auto prune [options] Delete stale runs from the audit DB
|
|
25
|
+
|
|
26
|
+
Options (for run):
|
|
27
|
+
--example <name> Run a bundled example (dca, grid, funding-arb, mm-spread, mm-maker)
|
|
28
|
+
--set key=value Set config values (repeatable, auto-parses numbers/booleans)
|
|
29
|
+
--dry Intercept write methods (no real trades)
|
|
30
|
+
--verbose Show debug output
|
|
31
|
+
--id <name> Custom automation ID (default: filename)
|
|
32
|
+
--poll <ms> Poll interval in milliseconds (default: 10000)
|
|
33
|
+
--no-ws Disable WebSocket; fall back to REST-only polling
|
|
34
|
+
--allow-sleep Do not request OS idle-sleep inhibition for this run
|
|
35
|
+
|
|
36
|
+
Options (for prune):
|
|
37
|
+
--older-than <d> Only prune runs started before this duration ago (e.g. 7d, 24h)
|
|
38
|
+
--status <list> CSV of statuses to consider (default: stopped,error,stale)
|
|
39
|
+
--keep-last <N> Keep the N most-recent runs per automation_id
|
|
40
|
+
--all Prune everything except runs that are still alive
|
|
41
|
+
--vacuum VACUUM the DB after deletion to reclaim disk space
|
|
42
|
+
--dry Preview what would be deleted without writing
|
|
43
|
+
|
|
44
|
+
Scripts are loaded from:
|
|
45
|
+
1. Absolute or relative path
|
|
46
|
+
2. ~/.openbroker/automations/<name>.ts
|
|
47
|
+
3. Bundled examples (via --example)
|
|
48
|
+
|
|
49
|
+
Writing an automation:
|
|
50
|
+
export default function(api) {
|
|
51
|
+
api.on('price_change', async ({ coin, changePct }) => {
|
|
52
|
+
api.log.info(\`\${coin} moved \${changePct.toFixed(2)}%\`);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Events: tick, price_change, funding_update, position_opened,
|
|
57
|
+
position_closed, position_changed, pnl_threshold, margin_warning
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
openbroker auto run --example dca --set coin=BTC --set amount=50 --dry
|
|
61
|
+
openbroker auto run --example grid --set coin=ETH --set lower=3000 --set upper=4000
|
|
62
|
+
openbroker auto run my-strategy --dry
|
|
63
|
+
openbroker auto report hype-mm-v2-live-r4
|
|
64
|
+
openbroker auto examples
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
/** Parse --set key=value flags from raw args, return parsed config object */
|
|
68
|
+
function parseSetFlags(rawArgs) {
|
|
69
|
+
const config = {};
|
|
70
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
71
|
+
if (rawArgs[i] === '--set' && i + 1 < rawArgs.length) {
|
|
72
|
+
const pair = rawArgs[i + 1];
|
|
73
|
+
const eqIdx = pair.indexOf('=');
|
|
74
|
+
if (eqIdx === -1) {
|
|
75
|
+
console.error(`Error: --set requires key=value format, got: ${pair}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const key = pair.slice(0, eqIdx);
|
|
79
|
+
const raw = pair.slice(eqIdx + 1);
|
|
80
|
+
const isHexLike = /^0x[0-9a-fA-F]+$/.test(raw);
|
|
81
|
+
const isDecimalLike = /^-?(?:\d+|\d+\.\d+|\.\d+)$/.test(raw);
|
|
82
|
+
// Auto-parse numbers and booleans
|
|
83
|
+
if (raw === 'true')
|
|
84
|
+
config[key] = true;
|
|
85
|
+
else if (raw === 'false')
|
|
86
|
+
config[key] = false;
|
|
87
|
+
else if (!isHexLike && isDecimalLike)
|
|
88
|
+
config[key] = Number(raw);
|
|
89
|
+
else
|
|
90
|
+
config[key] = raw;
|
|
91
|
+
i++; // skip the value
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return config;
|
|
95
|
+
}
|
|
96
|
+
/** Strip --set key=value pairs from raw args so parseArgs doesn't see them */
|
|
97
|
+
function stripSetFlags(rawArgs) {
|
|
98
|
+
const result = [];
|
|
99
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
100
|
+
if (rawArgs[i] === '--set' && i + 1 < rawArgs.length) {
|
|
101
|
+
i++; // skip --set and its value
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
result.push(rawArgs[i]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
async function runCommand(args, positional, initialState) {
|
|
110
|
+
const exampleName = args.example ? String(args.example) : undefined;
|
|
111
|
+
const scriptName = positional[0];
|
|
112
|
+
if (!scriptName && !exampleName) {
|
|
113
|
+
console.error('Error: script name or path required (or use --example <name>)');
|
|
114
|
+
console.log('Usage: openbroker auto run <script> [--dry] [--verbose]');
|
|
115
|
+
console.log(' openbroker auto run --example <name> [--set key=value] [--dry]');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const scriptPath = exampleName ? resolveExamplePath(exampleName) : resolveScriptPath(scriptName);
|
|
119
|
+
const dryRun = args.dry === true;
|
|
120
|
+
const verbose = args.verbose === true;
|
|
121
|
+
const useWebSocket = args['no-ws'] !== true;
|
|
122
|
+
const keepAwake = args['allow-sleep'] === true ? false : undefined;
|
|
123
|
+
const pollIntervalMs = args.poll ? parseInt(String(args.poll), 10) : undefined;
|
|
124
|
+
const id = args.id ? String(args.id) : undefined;
|
|
125
|
+
if (args.testnet === true) {
|
|
126
|
+
process.env.HYPERLIQUID_NETWORK = 'testnet';
|
|
127
|
+
}
|
|
128
|
+
else if (args.mainnet === true) {
|
|
129
|
+
process.env.HYPERLIQUID_NETWORK = 'mainnet';
|
|
130
|
+
}
|
|
131
|
+
if (pollIntervalMs !== undefined && (isNaN(pollIntervalMs) || pollIntervalMs < 1000)) {
|
|
132
|
+
console.error('Error: --poll must be at least 1000ms');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
const envHooksToken = process.env.OPENCLAW_HOOKS_TOKEN;
|
|
136
|
+
const envGatewayPortStr = process.env.OPENCLAW_GATEWAY_PORT;
|
|
137
|
+
const envGatewayPort = envGatewayPortStr ? parseInt(envGatewayPortStr, 10) : undefined;
|
|
138
|
+
const automation = await startAutomation({
|
|
139
|
+
scriptPath,
|
|
140
|
+
id,
|
|
141
|
+
dryRun,
|
|
142
|
+
verbose,
|
|
143
|
+
pollIntervalMs,
|
|
144
|
+
useWebSocket,
|
|
145
|
+
keepAwake,
|
|
146
|
+
initialState: Object.keys(initialState).length > 0 ? initialState : undefined,
|
|
147
|
+
hooksToken: envHooksToken,
|
|
148
|
+
gatewayPort: envGatewayPort && !isNaN(envGatewayPort) ? envGatewayPort : undefined,
|
|
149
|
+
});
|
|
150
|
+
// Graceful shutdown on SIGINT/SIGTERM
|
|
151
|
+
const shutdown = async () => {
|
|
152
|
+
console.log('\nShutting down...');
|
|
153
|
+
await automation.stop();
|
|
154
|
+
process.exit(0);
|
|
155
|
+
};
|
|
156
|
+
process.on('SIGINT', shutdown);
|
|
157
|
+
process.on('SIGTERM', shutdown);
|
|
158
|
+
// Keep process alive
|
|
159
|
+
await new Promise(() => { });
|
|
160
|
+
}
|
|
161
|
+
async function examplesCommand() {
|
|
162
|
+
const configs = await loadExampleConfigs();
|
|
163
|
+
const names = Object.keys(configs);
|
|
164
|
+
if (names.length === 0) {
|
|
165
|
+
console.log('No bundled examples found.');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
console.log('Bundled example automations:\n');
|
|
169
|
+
for (const name of names) {
|
|
170
|
+
const cfg = configs[name];
|
|
171
|
+
console.log(` ${name}`);
|
|
172
|
+
console.log(` ${cfg.description}\n`);
|
|
173
|
+
console.log(` Config (--set key=value):`);
|
|
174
|
+
for (const [key, field] of Object.entries(cfg.fields)) {
|
|
175
|
+
const def = JSON.stringify(field.default);
|
|
176
|
+
console.log(` ${key.padEnd(14)} ${field.type.padEnd(8)} ${field.description} (default: ${def})`);
|
|
177
|
+
}
|
|
178
|
+
console.log('');
|
|
179
|
+
}
|
|
180
|
+
console.log(`Run with: openbroker auto run --example <name> [--set key=value] [--dry]`);
|
|
181
|
+
console.log('Copy to ~/.openbroker/automations/ to customize.');
|
|
182
|
+
}
|
|
183
|
+
function listCommand() {
|
|
184
|
+
ensureAutomationsDir();
|
|
185
|
+
const automations = listAutomations();
|
|
186
|
+
const examples = listExamples();
|
|
187
|
+
if (automations.length === 0 && examples.length === 0) {
|
|
188
|
+
console.log('No automations found in ~/.openbroker/automations/');
|
|
189
|
+
console.log('\nCreate a .ts file there with:');
|
|
190
|
+
console.log(' export default function(api) { ... }');
|
|
191
|
+
console.log('\nOr run a bundled example: openbroker auto examples');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (automations.length > 0) {
|
|
195
|
+
console.log('User automations (~/.openbroker/automations/):\n');
|
|
196
|
+
for (const a of automations) {
|
|
197
|
+
console.log(` ${a.name.padEnd(30)} ${a.path}`);
|
|
198
|
+
}
|
|
199
|
+
console.log(`\nRun with: openbroker auto run <name>`);
|
|
200
|
+
}
|
|
201
|
+
if (examples.length > 0) {
|
|
202
|
+
if (automations.length > 0)
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log(`${examples.length} bundled examples available — run: openbroker auto examples`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function statusCommand() {
|
|
208
|
+
// Show in-process automations (if any running in this process)
|
|
209
|
+
const inProcess = getRunningAutomations();
|
|
210
|
+
// Show all registered automations from file-based registry (cross-process)
|
|
211
|
+
const registered = getRegisteredAutomations();
|
|
212
|
+
if (inProcess.length === 0 && registered.length === 0) {
|
|
213
|
+
console.log('No automations running');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Show in-process automations with live stats
|
|
217
|
+
if (inProcess.length > 0) {
|
|
218
|
+
console.log('Running in this process:\n');
|
|
219
|
+
for (const a of inProcess) {
|
|
220
|
+
const uptime = Math.round((Date.now() - a.startedAt.getTime()) / 1000);
|
|
221
|
+
console.log(` ${a.id}`);
|
|
222
|
+
console.log(` Script: ${a.scriptPath}`);
|
|
223
|
+
console.log(` Uptime: ${uptime}s`);
|
|
224
|
+
console.log(` Polls: ${a.pollCount}`);
|
|
225
|
+
console.log(` Events: ${a.eventsEmitted}`);
|
|
226
|
+
console.log(` Dry run: ${a.dryRun}`);
|
|
227
|
+
console.log('');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Show all registered automations (may include ones from other processes)
|
|
231
|
+
const external = registered.filter(r => !inProcess.some(ip => ip.id === r.id));
|
|
232
|
+
if (external.length > 0) {
|
|
233
|
+
if (inProcess.length > 0)
|
|
234
|
+
console.log('Other processes:\n');
|
|
235
|
+
else
|
|
236
|
+
console.log('Registered automations:\n');
|
|
237
|
+
for (const a of external) {
|
|
238
|
+
const uptime = a.status === 'running'
|
|
239
|
+
? `${Math.round((Date.now() - new Date(a.startedAt).getTime()) / 1000)}s`
|
|
240
|
+
: '-';
|
|
241
|
+
console.log(` ${a.id}`);
|
|
242
|
+
console.log(` Script: ${a.scriptPath}`);
|
|
243
|
+
console.log(` Status: ${a.status}${a.error ? ` (${a.error})` : ''}`);
|
|
244
|
+
console.log(` PID: ${a.pid}`);
|
|
245
|
+
console.log(` Uptime: ${uptime}`);
|
|
246
|
+
console.log(` Dry run: ${a.dryRun}`);
|
|
247
|
+
console.log('');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function stopCommand(positional) {
|
|
252
|
+
const id = positional[0];
|
|
253
|
+
if (!id) {
|
|
254
|
+
console.error('Error: automation ID required');
|
|
255
|
+
console.log('Usage: openbroker auto stop <id>');
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
// Check if running in this process
|
|
259
|
+
const inProcess = getRunningAutomations();
|
|
260
|
+
const running = inProcess.find(a => a.id === id);
|
|
261
|
+
if (running) {
|
|
262
|
+
running.stop().then(() => {
|
|
263
|
+
console.log(`Stopped and unregistered: ${id}`);
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Otherwise just remove from file registry (prevents restart)
|
|
268
|
+
unregisterAutomation(id);
|
|
269
|
+
console.log(`Unregistered: ${id} (will not restart on next gateway start)`);
|
|
270
|
+
}
|
|
271
|
+
async function cleanCommand() {
|
|
272
|
+
cleanRegistry();
|
|
273
|
+
console.log('Cleaned stale entries from registry');
|
|
274
|
+
// Also reconcile the audit DB so dead processes whose rows still say
|
|
275
|
+
// 'running' get marked 'stopped'. Without this, the dashboard keeps showing
|
|
276
|
+
// 'stale' badges for automations the operator already cleaned out of the
|
|
277
|
+
// registry.
|
|
278
|
+
try {
|
|
279
|
+
const { prune } = await import('./prune.js');
|
|
280
|
+
const result = prune({ reconcileOnly: true });
|
|
281
|
+
if (result.reconciled > 0) {
|
|
282
|
+
console.log(`Reconciled ${result.reconciled} orphan run row${result.reconciled === 1 ? '' : 's'} in audit DB (status: running → stopped)`);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
console.log('Audit DB already consistent');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
console.warn(`Could not reconcile audit DB: ${err instanceof Error ? err.message : String(err)}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function pruneCommand(args) {
|
|
293
|
+
const { fmtBytes, parseDuration, prune } = await import('./prune.js');
|
|
294
|
+
const olderThanRaw = args['older-than'];
|
|
295
|
+
const olderThanMs = typeof olderThanRaw === 'string' ? parseDuration(olderThanRaw) : undefined;
|
|
296
|
+
const statusesRaw = typeof args.status === 'string' ? args.status : undefined;
|
|
297
|
+
const statuses = statusesRaw
|
|
298
|
+
? new Set(statusesRaw.split(',').map((s) => s.trim()).filter(Boolean))
|
|
299
|
+
: undefined;
|
|
300
|
+
const keepLastRaw = args['keep-last'];
|
|
301
|
+
const keepLast = typeof keepLastRaw === 'string' ? Number(keepLastRaw)
|
|
302
|
+
: typeof keepLastRaw === 'number' ? keepLastRaw
|
|
303
|
+
: undefined;
|
|
304
|
+
if (keepLast !== undefined && (!Number.isFinite(keepLast) || keepLast < 0)) {
|
|
305
|
+
console.error('Error: --keep-last must be a non-negative integer');
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
const opts = {
|
|
309
|
+
olderThanMs,
|
|
310
|
+
statuses,
|
|
311
|
+
keepLastPerAutomation: keepLast,
|
|
312
|
+
all: args.all === true,
|
|
313
|
+
vacuum: args.vacuum === true,
|
|
314
|
+
dryRun: args.dry === true,
|
|
315
|
+
};
|
|
316
|
+
const result = prune(opts);
|
|
317
|
+
if (result.reconciled > 0) {
|
|
318
|
+
const verb = result.dryRun ? 'Would reconcile' : 'Reconciled';
|
|
319
|
+
console.log(`${verb} ${result.reconciled} orphan run row${result.reconciled === 1 ? '' : 's'} (running → stopped)`);
|
|
320
|
+
}
|
|
321
|
+
const verb = result.dryRun ? 'Would delete' : 'Deleted';
|
|
322
|
+
if (result.candidateRunIds.length === 0) {
|
|
323
|
+
console.log('No runs matched pruning filters.');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
console.log(`${verb} ${result.candidateRunIds.length} automation run${result.candidateRunIds.length === 1 ? '' : 's'}:`);
|
|
327
|
+
for (const id of result.candidateRunIds.slice(0, 25)) {
|
|
328
|
+
console.log(` · ${id}`);
|
|
329
|
+
}
|
|
330
|
+
if (result.candidateRunIds.length > 25) {
|
|
331
|
+
console.log(` … and ${result.candidateRunIds.length - 25} more`);
|
|
332
|
+
}
|
|
333
|
+
if (!result.dryRun) {
|
|
334
|
+
const totalChild = Object.values(result.deletedRows).reduce((a, b) => a + b, 0);
|
|
335
|
+
console.log(`Removed ${totalChild.toLocaleString()} child rows across ${Object.keys(result.deletedRows).length} tables`);
|
|
336
|
+
if (result.freedBytes > 0) {
|
|
337
|
+
console.log(`Reclaimed ~${fmtBytes(result.freedBytes)}${opts.vacuum ? ' (post-VACUUM)' : ''}`);
|
|
338
|
+
}
|
|
339
|
+
else if (opts.vacuum) {
|
|
340
|
+
console.log('No disk reclaimed (VACUUM completed)');
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
console.log('Run again with --vacuum to reclaim disk space.');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function reportCommand(rawArgs) {
|
|
348
|
+
const scriptPath = path.join(__dirname, 'report.ts');
|
|
349
|
+
const result = spawnSync(process.execPath, ['--experimental-sqlite', '--no-warnings', '--import', 'tsx', scriptPath, ...rawArgs], {
|
|
350
|
+
stdio: 'inherit',
|
|
351
|
+
cwd: path.resolve(__dirname, '../..'),
|
|
352
|
+
env: { ...process.env },
|
|
353
|
+
});
|
|
354
|
+
if (result.error) {
|
|
355
|
+
throw result.error;
|
|
356
|
+
}
|
|
357
|
+
if (typeof result.status === 'number' && result.status !== 0) {
|
|
358
|
+
process.exit(result.status);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async function main() {
|
|
362
|
+
const rawArgs = process.argv.slice(2);
|
|
363
|
+
if (rawArgs.length === 0 || rawArgs[0] === '--help' || rawArgs[0] === '-h') {
|
|
364
|
+
printUsage();
|
|
365
|
+
process.exit(0);
|
|
366
|
+
}
|
|
367
|
+
const subcommand = rawArgs[0];
|
|
368
|
+
const restArgs = rawArgs.slice(1);
|
|
369
|
+
// Parse --set flags before stripping them
|
|
370
|
+
const initialState = parseSetFlags(restArgs);
|
|
371
|
+
const cleanedArgs = stripSetFlags(restArgs);
|
|
372
|
+
// Extract positional args (non-flag args)
|
|
373
|
+
const positional = [];
|
|
374
|
+
const flagArgs = [];
|
|
375
|
+
for (let i = 0; i < cleanedArgs.length; i++) {
|
|
376
|
+
if (cleanedArgs[i].startsWith('--')) {
|
|
377
|
+
flagArgs.push(cleanedArgs[i]);
|
|
378
|
+
// If next arg doesn't start with --, it's a flag value
|
|
379
|
+
if (i + 1 < cleanedArgs.length && !cleanedArgs[i + 1].startsWith('--')) {
|
|
380
|
+
flagArgs.push(cleanedArgs[i + 1]);
|
|
381
|
+
i++;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
positional.push(cleanedArgs[i]);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const args = parseArgs(flagArgs);
|
|
389
|
+
switch (subcommand) {
|
|
390
|
+
case 'run':
|
|
391
|
+
await runCommand(args, positional, initialState);
|
|
392
|
+
break;
|
|
393
|
+
case 'stop':
|
|
394
|
+
stopCommand(positional);
|
|
395
|
+
break;
|
|
396
|
+
case 'list':
|
|
397
|
+
listCommand();
|
|
398
|
+
break;
|
|
399
|
+
case 'examples':
|
|
400
|
+
await examplesCommand();
|
|
401
|
+
break;
|
|
402
|
+
case 'status':
|
|
403
|
+
statusCommand();
|
|
404
|
+
break;
|
|
405
|
+
case 'clean':
|
|
406
|
+
await cleanCommand();
|
|
407
|
+
break;
|
|
408
|
+
case 'prune':
|
|
409
|
+
await pruneCommand(args);
|
|
410
|
+
break;
|
|
411
|
+
case 'report':
|
|
412
|
+
reportCommand(restArgs);
|
|
413
|
+
break;
|
|
414
|
+
default:
|
|
415
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
416
|
+
console.log('Run "openbroker auto --help" for usage');
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
main().catch(err => {
|
|
421
|
+
console.error(err.message || err);
|
|
422
|
+
process.exit(1);
|
|
423
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AutomationEventType, AutomationEventPayloads, AutomationEventHandler } from './types.js';
|
|
2
|
+
export declare class AutomationEventBus {
|
|
3
|
+
private handlers;
|
|
4
|
+
on<E extends AutomationEventType>(event: E, handler: AutomationEventHandler<E>): void;
|
|
5
|
+
/** Emit an event — handlers run sequentially, errors are returned (not thrown) */
|
|
6
|
+
emit<E extends AutomationEventType>(event: E, payload: AutomationEventPayloads[E]): Promise<Error[]>;
|
|
7
|
+
/** Check if any handlers are registered for an event */
|
|
8
|
+
has(event: AutomationEventType): boolean;
|
|
9
|
+
removeAll(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../scripts/auto/events.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAiD;IAEjE,EAAE,CAAC,CAAC,SAAS,mBAAmB,EAC9B,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,sBAAsB,CAAC,CAAC,CAAC,GACjC,IAAI;IASP,kFAAkF;IAC5E,IAAI,CAAC,CAAC,SAAS,mBAAmB,EACtC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,KAAK,EAAE,CAAC;IAenB,wDAAwD;IACxD,GAAG,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO;IAKxC,SAAS,IAAI,IAAI;CAGlB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Lightweight typed event bus for the automation runtime
|
|
2
|
+
export class AutomationEventBus {
|
|
3
|
+
handlers = new Map();
|
|
4
|
+
on(event, handler) {
|
|
5
|
+
let set = this.handlers.get(event);
|
|
6
|
+
if (!set) {
|
|
7
|
+
set = new Set();
|
|
8
|
+
this.handlers.set(event, set);
|
|
9
|
+
}
|
|
10
|
+
set.add(handler);
|
|
11
|
+
}
|
|
12
|
+
/** Emit an event — handlers run sequentially, errors are returned (not thrown) */
|
|
13
|
+
async emit(event, payload) {
|
|
14
|
+
const set = this.handlers.get(event);
|
|
15
|
+
if (!set || set.size === 0)
|
|
16
|
+
return [];
|
|
17
|
+
const errors = [];
|
|
18
|
+
for (const handler of set) {
|
|
19
|
+
try {
|
|
20
|
+
await handler(payload);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return errors;
|
|
27
|
+
}
|
|
28
|
+
/** Check if any handlers are registered for an event */
|
|
29
|
+
has(event) {
|
|
30
|
+
const set = this.handlers.get(event);
|
|
31
|
+
return set !== undefined && set.size > 0;
|
|
32
|
+
}
|
|
33
|
+
removeAll() {
|
|
34
|
+
this.handlers.clear();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dca.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/dca.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,eAAO,MAAM,MAAM,EAAE,gBAQpB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,GAAG,EAAE,aAAa,QAyD7C"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// DCA (Dollar Cost Averaging) — Buy fixed USD amounts at regular intervals
|
|
2
|
+
export const config = {
|
|
3
|
+
description: 'Dollar cost averaging — buy fixed USD amounts at regular intervals',
|
|
4
|
+
fields: {
|
|
5
|
+
coin: { type: 'string', description: 'Asset to accumulate', default: 'HYPE' },
|
|
6
|
+
amount: { type: 'number', description: 'USD per purchase', default: 100 },
|
|
7
|
+
interval: { type: 'number', description: 'Milliseconds between buys (3600000 = 1h)', default: 3_600_000 },
|
|
8
|
+
count: { type: 'number', description: 'Total number of purchases', default: 24 },
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
export default function dca(api) {
|
|
12
|
+
const COIN = api.state.get('coin', 'HYPE');
|
|
13
|
+
const AMOUNT_USD = api.state.get('amount', 100);
|
|
14
|
+
const INTERVAL_MS = api.state.get('interval', 3_600_000);
|
|
15
|
+
const MAX_PURCHASES = api.state.get('count', 24);
|
|
16
|
+
let purchased = api.state.get('purchased', 0);
|
|
17
|
+
let totalSpent = api.state.get('totalSpent', 0);
|
|
18
|
+
let totalAcquired = api.state.get('totalAcquired', 0);
|
|
19
|
+
api.onStart(() => {
|
|
20
|
+
api.log.info(`DCA: ${MAX_PURCHASES} buys of $${AMOUNT_USD} ${COIN} every ${INTERVAL_MS / 60000}m`);
|
|
21
|
+
api.log.info(`Progress: ${purchased}/${MAX_PURCHASES} completed`);
|
|
22
|
+
});
|
|
23
|
+
api.every(INTERVAL_MS, async () => {
|
|
24
|
+
if (purchased >= MAX_PURCHASES) {
|
|
25
|
+
api.log.info(`DCA complete: ${purchased} purchases, $${totalSpent.toFixed(2)} spent, ${totalAcquired.toFixed(6)} ${COIN} acquired`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const mids = await api.client.getAllMids();
|
|
29
|
+
const price = parseFloat(mids[COIN]);
|
|
30
|
+
if (!price) {
|
|
31
|
+
api.log.warn(`No price for ${COIN}, skipping`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const size = AMOUNT_USD / price;
|
|
35
|
+
api.log.info(`[${purchased + 1}/${MAX_PURCHASES}] Buying ~$${AMOUNT_USD} of ${COIN} @ $${price.toFixed(2)}`);
|
|
36
|
+
const response = await api.client.marketOrder(COIN, true, size);
|
|
37
|
+
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
38
|
+
const status = response.response.data.statuses[0];
|
|
39
|
+
if (status?.filled) {
|
|
40
|
+
const filledSize = parseFloat(status.filled.totalSz);
|
|
41
|
+
const avgPx = parseFloat(status.filled.avgPx);
|
|
42
|
+
totalSpent += filledSize * avgPx;
|
|
43
|
+
totalAcquired += filledSize;
|
|
44
|
+
purchased++;
|
|
45
|
+
api.state.set('purchased', purchased);
|
|
46
|
+
api.state.set('totalSpent', totalSpent);
|
|
47
|
+
api.state.set('totalAcquired', totalAcquired);
|
|
48
|
+
const avgPrice = totalSpent / totalAcquired;
|
|
49
|
+
api.log.info(`Filled ${filledSize.toFixed(6)} @ $${avgPx.toFixed(2)} | Avg: $${avgPrice.toFixed(2)} | Total: ${totalAcquired.toFixed(6)} ${COIN}`);
|
|
50
|
+
}
|
|
51
|
+
else if (status?.error) {
|
|
52
|
+
api.log.error(`Order failed: ${status.error}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
api.onStop(() => {
|
|
57
|
+
const avgPrice = totalAcquired > 0 ? totalSpent / totalAcquired : 0;
|
|
58
|
+
api.log.info(`DCA stopped: ${purchased}/${MAX_PURCHASES} | Spent: $${totalSpent.toFixed(2)} | Acquired: ${totalAcquired.toFixed(6)} ${COIN} | Avg: $${avgPrice.toFixed(2)}`);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"funding-arb.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/funding-arb.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,eAAO,MAAM,MAAM,EAAE,gBASpB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,GAAG,EAAE,aAAa,QAkFpD"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Funding Arbitrage — Collect funding by positioning opposite to the crowd
|
|
2
|
+
export const config = {
|
|
3
|
+
description: 'Funding arbitrage — collect funding by positioning opposite to the crowd',
|
|
4
|
+
fields: {
|
|
5
|
+
coin: { type: 'string', description: 'Asset to trade', default: 'HYPE' },
|
|
6
|
+
sizeUsd: { type: 'number', description: 'Position size in USD notional', default: 5000 },
|
|
7
|
+
minFunding: { type: 'number', description: 'Min annualized % to enter', default: 20 },
|
|
8
|
+
maxFunding: { type: 'number', description: 'Max annualized % — avoid squeezes', default: 200 },
|
|
9
|
+
closeAt: { type: 'number', description: 'Close when funding drops below this %', default: 5 },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
export default function fundingArb(api) {
|
|
13
|
+
const COIN = api.state.get('coin', 'HYPE');
|
|
14
|
+
const SIZE_USD = api.state.get('sizeUsd', 5000);
|
|
15
|
+
const MIN_FUNDING = api.state.get('minFunding', 20);
|
|
16
|
+
const MAX_FUNDING = api.state.get('maxFunding', 200);
|
|
17
|
+
const CLOSE_AT = api.state.get('closeAt', 5);
|
|
18
|
+
let inPosition = api.state.get('inPosition', false);
|
|
19
|
+
let positionSide = api.state.get('positionSide', '');
|
|
20
|
+
let entryPrice = api.state.get('entryPrice', 0);
|
|
21
|
+
let positionSize = api.state.get('positionSize', 0);
|
|
22
|
+
let totalFunding = api.state.get('totalFunding', 0);
|
|
23
|
+
api.onStart(() => {
|
|
24
|
+
api.log.info(`Funding arb: ${COIN} | $${SIZE_USD} | Enter >${MIN_FUNDING}% | Close <${CLOSE_AT}%`);
|
|
25
|
+
if (inPosition) {
|
|
26
|
+
api.log.info(`Resuming ${positionSide} position: ${positionSize.toFixed(6)} @ $${entryPrice.toFixed(2)}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
api.on('funding_update', async ({ coin, annualized }) => {
|
|
30
|
+
if (coin !== COIN)
|
|
31
|
+
return;
|
|
32
|
+
const annualizedPct = annualized * 100;
|
|
33
|
+
const absAnnualized = Math.abs(annualizedPct);
|
|
34
|
+
if (inPosition) {
|
|
35
|
+
// Check if we should close
|
|
36
|
+
const shouldClose = (positionSide === 'short' && annualizedPct < CLOSE_AT) ||
|
|
37
|
+
(positionSide === 'long' && annualizedPct > -CLOSE_AT);
|
|
38
|
+
if (shouldClose) {
|
|
39
|
+
api.log.info(`Funding dropped to ${annualizedPct.toFixed(2)}% (below ${CLOSE_AT}%), closing ${positionSide}`);
|
|
40
|
+
const closeIsBuy = positionSide === 'short';
|
|
41
|
+
await api.client.marketOrder(coin, closeIsBuy, positionSize);
|
|
42
|
+
inPosition = false;
|
|
43
|
+
api.state.set('inPosition', false);
|
|
44
|
+
api.log.info(`Position closed. Funding collected: ~$${totalFunding.toFixed(2)}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
api.log.debug(`${coin} funding: ${annualizedPct.toFixed(2)}% — holding ${positionSide}`);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Not in position — check if we should enter
|
|
52
|
+
if (absAnnualized >= MIN_FUNDING && absAnnualized <= MAX_FUNDING) {
|
|
53
|
+
const shouldShort = annualizedPct > 0; // Positive = longs pay shorts
|
|
54
|
+
const side = shouldShort ? 'short' : 'long';
|
|
55
|
+
const mids = await api.client.getAllMids();
|
|
56
|
+
const price = parseFloat(mids[coin]);
|
|
57
|
+
const size = SIZE_USD / price;
|
|
58
|
+
api.log.info(`Funding at ${annualizedPct.toFixed(2)}% — opening ${side} ${size.toFixed(6)} ${coin}`);
|
|
59
|
+
const response = await api.client.marketOrder(coin, !shouldShort, size);
|
|
60
|
+
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
61
|
+
const status = response.response.data.statuses[0];
|
|
62
|
+
if (status?.filled) {
|
|
63
|
+
positionSize = parseFloat(status.filled.totalSz);
|
|
64
|
+
entryPrice = parseFloat(status.filled.avgPx);
|
|
65
|
+
positionSide = side;
|
|
66
|
+
inPosition = true;
|
|
67
|
+
api.state.set('inPosition', true);
|
|
68
|
+
api.state.set('positionSide', side);
|
|
69
|
+
api.state.set('entryPrice', entryPrice);
|
|
70
|
+
api.state.set('positionSize', positionSize);
|
|
71
|
+
api.log.info(`Entered ${side} ${positionSize.toFixed(6)} @ $${entryPrice.toFixed(2)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
api.onStop(() => {
|
|
77
|
+
if (inPosition) {
|
|
78
|
+
api.log.warn(`Stopping with open ${positionSide} position of ${positionSize.toFixed(6)} ${COIN} — close manually if desired`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/grid.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,eAAO,MAAM,MAAM,EAAE,gBAUpB,CAAC;AASF,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,GAAG,EAAE,aAAa,QA+G9C"}
|