openbroker 1.0.89 → 1.1.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/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "openbroker",
3
- "version": "1.0.89",
3
+ "version": "1.1.0",
4
4
  "description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "openbroker": "./bin/openbroker.js"
8
8
  },
9
- "openclaw": {
10
- "extensions": ["./scripts/plugin/index.ts"]
9
+ "main": "./scripts/lib.ts",
10
+ "exports": {
11
+ ".": "./scripts/lib.ts",
12
+ "./package.json": "./package.json"
11
13
  },
12
14
  "files": [
13
15
  "bin/",
14
16
  "scripts/",
15
17
  "config/example.env",
16
- "openclaw.plugin.json",
17
18
  "README.md",
18
- "CHANGELOG.md",
19
- "SKILL.md"
19
+ "CHANGELOG.md"
20
20
  ],
21
21
  "scripts": {
22
22
  "onboard": "tsx scripts/setup/onboard.ts",
@@ -130,8 +130,6 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
130
130
  process.exit(1);
131
131
  }
132
132
 
133
- // Resolve OpenClaw gateway env vars here (no network code in this file)
134
- // so the runtime stays clean of process.env reads next to fetch() calls.
135
133
  const envHooksToken = process.env.OPENCLAW_HOOKS_TOKEN;
136
134
  const envGatewayPortStr = process.env.OPENCLAW_GATEWAY_PORT;
137
135
  const envGatewayPort = envGatewayPortStr ? parseInt(envGatewayPortStr, 10) : undefined;
@@ -349,10 +349,6 @@ function createPublish(
349
349
  hooksToken?: string,
350
350
  ): (message: string, options?: PublishOptions) => Promise<boolean> {
351
351
  return async (message: string, options?: PublishOptions): Promise<boolean> => {
352
- // Token & port come exclusively from options. Env-var fallbacks live in
353
- // the call sites (plugin/index.ts and auto/cli.ts), so the env reads
354
- // aren't co-located with the fetch() below and don't trip the OpenClaw
355
- // "credential harvesting" scanner rule.
356
352
  const token = hooksToken;
357
353
  const port = gatewayPort || 18789;
358
354
 
package/scripts/lib.ts ADDED
@@ -0,0 +1,80 @@
1
+ // Public library API for `openbroker`.
2
+ //
3
+ // External packages — notably `openbroker-plugin` — import from here to
4
+ // drive the CLI's functionality in-process (no `child_process` dispatch).
5
+ //
6
+ // Stability: every symbol re-exported here is a public API. Renames or
7
+ // removals are breaking changes and require a major version bump.
8
+
9
+ export {
10
+ HyperliquidClient,
11
+ getClient,
12
+ } from './core/client.js';
13
+
14
+ export {
15
+ loadConfig,
16
+ isConfigured,
17
+ getNetwork,
18
+ isMainnet,
19
+ ensureConfigDir,
20
+ getConfigPath,
21
+ GLOBAL_CONFIG_DIR,
22
+ GLOBAL_ENV_PATH,
23
+ OPEN_BROKER_BUILDER_ADDRESS,
24
+ } from './core/config.js';
25
+
26
+ export {
27
+ roundPrice,
28
+ roundSize,
29
+ sleep,
30
+ normalizeCoin,
31
+ formatUsd,
32
+ formatPercent,
33
+ annualizeFundingRate,
34
+ parseArgs,
35
+ getSlippagePrice,
36
+ getTimestampMs,
37
+ generateCloid,
38
+ orderToWire,
39
+ checkBuilderFeeApproval,
40
+ } from './core/utils.js';
41
+
42
+ export type * from './core/types.js';
43
+
44
+ // ── Operations (in-process callable) ────────────────────────────────
45
+
46
+ export { runBracket } from './operations/bracket.js';
47
+ export type { BracketOptions, BracketResult } from './operations/bracket.js';
48
+
49
+ export { runChase } from './operations/chase.js';
50
+ export type { ChaseOptions, ChaseResult } from './operations/chase.js';
51
+
52
+ // ── Automation runtime ──────────────────────────────────────────────
53
+
54
+ export {
55
+ startAutomation,
56
+ getRunningAutomations,
57
+ getAutomation,
58
+ getRegisteredAutomations,
59
+ } from './auto/runtime.js';
60
+ export type { RuntimeOptions } from './auto/runtime.js';
61
+
62
+ export {
63
+ resolveScriptPath,
64
+ resolveExamplePath,
65
+ listAutomations,
66
+ listExamples,
67
+ loadExampleConfigs,
68
+ ensureAutomationsDir,
69
+ loadAutomation,
70
+ } from './auto/loader.js';
71
+
72
+ export {
73
+ registerAutomation,
74
+ unregisterAutomation,
75
+ cleanRegistry,
76
+ getAutomationsToRestart,
77
+ markAutomationError,
78
+ } from './auto/registry.js';
79
+
80
+ export type * from './auto/types.js';
@@ -43,17 +43,215 @@ Examples:
43
43
  `);
44
44
  }
45
45
 
46
- interface BracketPlan {
46
+ export interface BracketOptions {
47
47
  coin: string;
48
- side: 'long' | 'short';
48
+ side: 'buy' | 'sell';
49
49
  size: number;
50
- entryType: 'market' | 'limit';
51
- entryPrice: number;
52
- tpPrice: number;
53
- slPrice: number;
54
50
  tpPct: number;
55
51
  slPct: number;
56
- riskReward: number;
52
+ entryType?: 'market' | 'limit';
53
+ entryPrice?: number;
54
+ slippage?: number;
55
+ leverage?: number;
56
+ dryRun?: boolean;
57
+ verbose?: boolean;
58
+ /** Receives each output line. Defaults to console.log. */
59
+ output?: (line: string) => void;
60
+ }
61
+
62
+ export interface BracketResult {
63
+ status: 'dry' | 'limit_resting' | 'complete' | 'entry_failed' | 'partial';
64
+ entryPrice?: number;
65
+ tpPrice?: number;
66
+ slPrice?: number;
67
+ tpOid?: number | null;
68
+ slOid?: number | null;
69
+ entryOid?: number | null;
70
+ reason?: string;
71
+ }
72
+
73
+ export async function runBracket(opts: BracketOptions): Promise<BracketResult> {
74
+ const out = opts.output ?? ((line: string) => console.log(line));
75
+ const entryType = opts.entryType ?? 'market';
76
+ const isLong = opts.side === 'buy';
77
+
78
+ if (opts.size <= 0 || isNaN(opts.size)) throw new Error('size must be positive');
79
+ if (opts.tpPct <= 0 || opts.slPct <= 0) throw new Error('tp and sl must be positive percentages');
80
+ if (entryType === 'limit' && opts.entryPrice === undefined) {
81
+ throw new Error('entryPrice is required for limit entry');
82
+ }
83
+
84
+ const client = getClient();
85
+ if (opts.verbose) client.verbose = true;
86
+
87
+ out('Open Broker - Bracket Order');
88
+ out('===========================\n');
89
+
90
+ const mids = await client.getAllMids();
91
+ const midPrice = parseFloat(mids[opts.coin]);
92
+ if (!midPrice) throw new Error(`No market data for ${opts.coin}`);
93
+
94
+ const entry = entryType === 'limit' ? opts.entryPrice! : midPrice;
95
+
96
+ let tpPrice = isLong
97
+ ? entry * (1 + opts.tpPct / 100)
98
+ : entry * (1 - opts.tpPct / 100);
99
+ let slPrice = isLong
100
+ ? entry * (1 - opts.slPct / 100)
101
+ : entry * (1 + opts.slPct / 100);
102
+
103
+ const riskReward = opts.tpPct / opts.slPct;
104
+ const notional = entry * opts.size;
105
+
106
+ out('Bracket Plan');
107
+ out('------------');
108
+ out(`Coin: ${opts.coin}`);
109
+ out(`Position: ${isLong ? 'LONG' : 'SHORT'}`);
110
+ out(`Size: ${opts.size}`);
111
+ out(`Entry Type: ${entryType.toUpperCase()}`);
112
+ out(`Current Mid: ${formatUsd(midPrice)}`);
113
+ out(`Entry Price: ${formatUsd(entry)}${entryType === 'market' ? ' (approx)' : ''}`);
114
+ out(`Take Profit: ${formatUsd(tpPrice)} (+${opts.tpPct}%)`);
115
+ out(`Stop Loss: ${formatUsd(slPrice)} (-${opts.slPct}%)`);
116
+ out(`Risk/Reward: 1:${riskReward.toFixed(2)}`);
117
+ out(`Est. Notional: ${formatUsd(notional)}`);
118
+
119
+ const potentialProfit = notional * (opts.tpPct / 100);
120
+ const potentialLoss = notional * (opts.slPct / 100);
121
+ out('\nRisk Analysis');
122
+ out('-------------');
123
+ out(`Potential Profit: ${formatUsd(potentialProfit)}`);
124
+ out(`Potential Loss: ${formatUsd(potentialLoss)}`);
125
+
126
+ if (opts.dryRun) {
127
+ out('\n🔍 Dry run - bracket not executed');
128
+ return { status: 'dry', entryPrice: entry, tpPrice, slPrice };
129
+ }
130
+
131
+ out('\nExecuting bracket...\n');
132
+
133
+ // Step 1: Entry
134
+ out('Step 1: Entry order');
135
+ let actualEntry = entry;
136
+ let entryOid: number | null = null;
137
+
138
+ if (entryType === 'market') {
139
+ const entryResponse = await client.marketOrder(opts.coin, isLong, opts.size, opts.slippage, opts.leverage);
140
+
141
+ if (entryResponse.status === 'ok' && entryResponse.response && typeof entryResponse.response === 'object') {
142
+ const status = entryResponse.response.data.statuses[0];
143
+ if (status?.filled) {
144
+ actualEntry = parseFloat(status.filled.avgPx);
145
+ out(` ✅ Filled @ ${formatUsd(actualEntry)}`);
146
+ } else if (status?.error) {
147
+ out(` ❌ Entry failed: ${status.error}`);
148
+ out('\n⚠️ Bracket aborted - no position opened');
149
+ return { status: 'entry_failed', reason: status.error };
150
+ }
151
+ } else {
152
+ const reason = typeof entryResponse.response === 'string' ? entryResponse.response : 'Unknown error';
153
+ out(` ❌ Entry failed: ${reason}`);
154
+ out('\n⚠️ Bracket aborted - no position opened');
155
+ return { status: 'entry_failed', reason };
156
+ }
157
+ } else {
158
+ const entryResponse = await client.limitOrder(opts.coin, isLong, opts.size, entry, 'Gtc', false, opts.leverage);
159
+
160
+ if (entryResponse.status === 'ok' && entryResponse.response && typeof entryResponse.response === 'object') {
161
+ const status = entryResponse.response.data.statuses[0];
162
+ if (status?.resting) {
163
+ entryOid = status.resting.oid;
164
+ out(` ✅ Limit order placed @ ${formatUsd(entry)} (OID: ${entryOid})`);
165
+ out(` ⏳ Waiting for fill before placing TP/SL...`);
166
+ out('\n⚠️ Note: TP/SL will be placed after entry fills. Monitor manually or use a strategy script.');
167
+ return { status: 'limit_resting', entryOid, entryPrice: entry };
168
+ } else if (status?.filled) {
169
+ actualEntry = parseFloat(status.filled.avgPx);
170
+ out(` ✅ Filled immediately @ ${formatUsd(actualEntry)}`);
171
+ } else if (status?.error) {
172
+ out(` ❌ Entry failed: ${status.error}`);
173
+ return { status: 'entry_failed', reason: status.error };
174
+ }
175
+ } else {
176
+ out(` ❌ Entry failed`);
177
+ return { status: 'entry_failed', reason: 'Unknown error' };
178
+ }
179
+ }
180
+
181
+ // Recalculate TP/SL based on actual entry
182
+ if (isLong) {
183
+ tpPrice = actualEntry * (1 + opts.tpPct / 100);
184
+ slPrice = actualEntry * (1 - opts.slPct / 100);
185
+ } else {
186
+ tpPrice = actualEntry * (1 - opts.tpPct / 100);
187
+ slPrice = actualEntry * (1 + opts.slPct / 100);
188
+ }
189
+
190
+ await sleep(500);
191
+
192
+ // Step 2: Take Profit (trigger order)
193
+ out('\nStep 2: Take Profit order (trigger)');
194
+ const tpSide = !isLong;
195
+ const tpResponse = await client.takeProfit(opts.coin, tpSide, opts.size, tpPrice);
196
+
197
+ let tpOid: number | null = null;
198
+ if (tpResponse.status === 'ok' && tpResponse.response && typeof tpResponse.response === 'object') {
199
+ const status = tpResponse.response.data.statuses[0];
200
+ if (status?.resting) {
201
+ tpOid = status.resting.oid;
202
+ out(` ✅ TP trigger placed @ ${formatUsd(tpPrice)} (OID: ${tpOid})`);
203
+ } else if (status?.error) {
204
+ out(` ❌ TP failed: ${status.error}`);
205
+ } else {
206
+ out(` ⚠️ TP status: ${JSON.stringify(status)}`);
207
+ }
208
+ } else {
209
+ const reason = typeof tpResponse.response === 'string' ? tpResponse.response : 'Unknown error';
210
+ out(` ❌ TP failed: ${reason}`);
211
+ }
212
+
213
+ await sleep(500);
214
+
215
+ // Step 3: Stop Loss (trigger order)
216
+ out('\nStep 3: Stop Loss order (trigger)');
217
+ const slSide = !isLong;
218
+ const slResponse = await client.stopLoss(opts.coin, slSide, opts.size, slPrice);
219
+
220
+ let slOid: number | null = null;
221
+ if (slResponse.status === 'ok' && slResponse.response && typeof slResponse.response === 'object') {
222
+ const status = slResponse.response.data.statuses[0];
223
+ if (status?.resting) {
224
+ slOid = status.resting.oid;
225
+ out(` ✅ SL trigger placed @ ${formatUsd(slPrice)} (OID: ${slOid})`);
226
+ } else if (status?.error) {
227
+ out(` ❌ SL failed: ${status.error}`);
228
+ } else {
229
+ out(` ⚠️ SL status: ${JSON.stringify(status)}`);
230
+ }
231
+ } else {
232
+ const reason = typeof slResponse.response === 'string' ? slResponse.response : 'Unknown error';
233
+ out(` ❌ SL failed: ${reason}`);
234
+ }
235
+
236
+ out('\n========== Bracket Summary ==========');
237
+ out(`Position: ${isLong ? 'LONG' : 'SHORT'} ${opts.size} ${opts.coin}`);
238
+ out(`Entry: ${formatUsd(actualEntry)}`);
239
+ out(`Take Profit: ${formatUsd(tpPrice)} (+${opts.tpPct}%) - Trigger order`);
240
+ out(`Stop Loss: ${formatUsd(slPrice)} (-${opts.slPct}%) - Trigger order`);
241
+ if (tpOid && slOid) {
242
+ out(`\n✅ Bracket complete! TP and SL are trigger orders.`);
243
+ out(` They will only execute when price reaches trigger level.`);
244
+ out(` When one fills, cancel the other manually.`);
245
+ }
246
+
247
+ return {
248
+ status: tpOid && slOid ? 'complete' : 'partial',
249
+ entryPrice: actualEntry,
250
+ tpPrice,
251
+ slPrice,
252
+ tpOid,
253
+ slOid,
254
+ };
57
255
  }
58
256
 
59
257
  async function main() {
@@ -74,212 +272,28 @@ async function main() {
74
272
  printUsage();
75
273
  process.exit(1);
76
274
  }
77
-
78
275
  if (side !== 'buy' && side !== 'sell') {
79
276
  console.error('Error: --side must be "buy" or "sell"');
80
277
  process.exit(1);
81
278
  }
82
279
 
83
- if (entryType === 'limit' && !entryPrice) {
84
- console.error('Error: --price is required for limit entry');
85
- process.exit(1);
86
- }
87
-
88
- if (tpPct <= 0 || slPct <= 0) {
89
- console.error('Error: --tp and --sl must be positive percentages');
90
- process.exit(1);
91
- }
92
-
93
- const isLong = side === 'buy';
94
- const client = getClient();
95
-
96
- if (args.verbose) {
97
- client.verbose = true;
98
- }
99
-
100
- console.log('Open Broker - Bracket Order');
101
- console.log('===========================\n');
102
-
103
280
  try {
104
- const mids = await client.getAllMids();
105
- const midPrice = parseFloat(mids[coin]);
106
- if (!midPrice) {
107
- console.error(`Error: No market data for ${coin}`);
108
- process.exit(1);
109
- }
110
-
111
- // Calculate prices
112
- const entry = entryType === 'limit' ? entryPrice! : midPrice;
113
-
114
- let tpPrice: number;
115
- let slPrice: number;
116
-
117
- if (isLong) {
118
- // Long: TP above, SL below
119
- tpPrice = entry * (1 + tpPct / 100);
120
- slPrice = entry * (1 - slPct / 100);
121
- } else {
122
- // Short: TP below, SL above
123
- tpPrice = entry * (1 - tpPct / 100);
124
- slPrice = entry * (1 + slPct / 100);
125
- }
126
-
127
- const riskReward = tpPct / slPct;
128
- const notional = entry * size;
129
-
130
- const plan: BracketPlan = {
281
+ const result = await runBracket({
131
282
  coin,
132
- side: isLong ? 'long' : 'short',
283
+ side: side as 'buy' | 'sell',
133
284
  size,
134
- entryType,
135
- entryPrice: entry,
136
- tpPrice,
137
- slPrice,
138
285
  tpPct,
139
286
  slPct,
140
- riskReward,
141
- };
142
-
143
- console.log('Bracket Plan');
144
- console.log('------------');
145
- console.log(`Coin: ${coin}`);
146
- console.log(`Position: ${isLong ? 'LONG' : 'SHORT'}`);
147
- console.log(`Size: ${size}`);
148
- console.log(`Entry Type: ${entryType.toUpperCase()}`);
149
- console.log(`Current Mid: ${formatUsd(midPrice)}`);
150
- console.log(`Entry Price: ${formatUsd(entry)}${entryType === 'market' ? ' (approx)' : ''}`);
151
- console.log(`Take Profit: ${formatUsd(tpPrice)} (+${tpPct}%)`);
152
- console.log(`Stop Loss: ${formatUsd(slPrice)} (-${slPct}%)`);
153
- console.log(`Risk/Reward: 1:${riskReward.toFixed(2)}`);
154
- console.log(`Est. Notional: ${formatUsd(notional)}`);
155
-
156
- // Risk analysis
157
- const potentialProfit = notional * (tpPct / 100);
158
- const potentialLoss = notional * (slPct / 100);
159
- console.log(`\nRisk Analysis`);
160
- console.log('-------------');
161
- console.log(`Potential Profit: ${formatUsd(potentialProfit)}`);
162
- console.log(`Potential Loss: ${formatUsd(potentialLoss)}`);
163
-
164
- if (dryRun) {
165
- console.log('\n🔍 Dry run - bracket not executed');
166
- return;
167
- }
168
-
169
- console.log('\nExecuting bracket...\n');
170
-
171
- // Step 1: Entry
172
- console.log('Step 1: Entry order');
173
- let actualEntry = entry;
174
-
175
- if (entryType === 'market') {
176
- const entryResponse = await client.marketOrder(coin, isLong, size, slippage, leverage);
177
-
178
- if (entryResponse.status === 'ok' && entryResponse.response && typeof entryResponse.response === 'object') {
179
- const status = entryResponse.response.data.statuses[0];
180
- if (status?.filled) {
181
- actualEntry = parseFloat(status.filled.avgPx);
182
- console.log(` ✅ Filled @ ${formatUsd(actualEntry)}`);
183
- } else if (status?.error) {
184
- console.log(` ❌ Entry failed: ${status.error}`);
185
- console.log('\n⚠️ Bracket aborted - no position opened');
186
- process.exit(1);
187
- }
188
- } else {
189
- console.log(` ❌ Entry failed: ${typeof entryResponse.response === 'string' ? entryResponse.response : 'Unknown error'}`);
190
- console.log('\n⚠️ Bracket aborted - no position opened');
191
- process.exit(1);
192
- }
193
- } else {
194
- const entryResponse = await client.limitOrder(coin, isLong, size, entry, 'Gtc', false, leverage);
195
-
196
- if (entryResponse.status === 'ok' && entryResponse.response && typeof entryResponse.response === 'object') {
197
- const status = entryResponse.response.data.statuses[0];
198
- if (status?.resting) {
199
- console.log(` ✅ Limit order placed @ ${formatUsd(entry)} (OID: ${status.resting.oid})`);
200
- console.log(` ⏳ Waiting for fill before placing TP/SL...`);
201
- console.log('\n⚠️ Note: TP/SL will be placed after entry fills. Monitor manually or use a strategy script.');
202
- return;
203
- } else if (status?.filled) {
204
- actualEntry = parseFloat(status.filled.avgPx);
205
- console.log(` ✅ Filled immediately @ ${formatUsd(actualEntry)}`);
206
- } else if (status?.error) {
207
- console.log(` ❌ Entry failed: ${status.error}`);
208
- process.exit(1);
209
- }
210
- } else {
211
- console.log(` ❌ Entry failed`);
212
- process.exit(1);
213
- }
214
- }
215
-
216
- // Recalculate TP/SL based on actual entry
217
- if (isLong) {
218
- tpPrice = actualEntry * (1 + tpPct / 100);
219
- slPrice = actualEntry * (1 - slPct / 100);
220
- } else {
221
- tpPrice = actualEntry * (1 - tpPct / 100);
222
- slPrice = actualEntry * (1 + slPct / 100);
223
- }
224
-
225
- await sleep(500); // Brief delay
226
-
227
- // Step 2: Take Profit (trigger order)
228
- console.log('\nStep 2: Take Profit order (trigger)');
229
- const tpSide = !isLong; // Opposite of entry: long -> sell TP, short -> buy TP
230
- const tpResponse = await client.takeProfit(coin, tpSide, size, tpPrice);
231
-
232
- let tpOid: number | null = null;
233
- if (tpResponse.status === 'ok' && tpResponse.response && typeof tpResponse.response === 'object') {
234
- const status = tpResponse.response.data.statuses[0];
235
- if (status?.resting) {
236
- tpOid = status.resting.oid;
237
- console.log(` ✅ TP trigger placed @ ${formatUsd(tpPrice)} (OID: ${tpOid})`);
238
- } else if (status?.error) {
239
- console.log(` ❌ TP failed: ${status.error}`);
240
- } else {
241
- console.log(` ⚠️ TP status:`, JSON.stringify(status));
242
- }
243
- } else {
244
- console.log(` ❌ TP failed: ${typeof tpResponse.response === 'string' ? tpResponse.response : 'Unknown error'}`);
245
- }
246
-
247
- await sleep(500);
248
-
249
- // Step 3: Stop Loss (trigger order)
250
- console.log('\nStep 3: Stop Loss order (trigger)');
251
- const slSide = !isLong; // Opposite of entry: long -> sell SL, short -> buy SL
252
- const slResponse = await client.stopLoss(coin, slSide, size, slPrice);
253
-
254
- let slOid: number | null = null;
255
- if (slResponse.status === 'ok' && slResponse.response && typeof slResponse.response === 'object') {
256
- const status = slResponse.response.data.statuses[0];
257
- if (status?.resting) {
258
- slOid = status.resting.oid;
259
- console.log(` ✅ SL trigger placed @ ${formatUsd(slPrice)} (OID: ${slOid})`);
260
- } else if (status?.error) {
261
- console.log(` ❌ SL failed: ${status.error}`);
262
- } else {
263
- console.log(` ⚠️ SL status:`, JSON.stringify(status));
264
- }
265
- } else {
266
- console.log(` ❌ SL failed: ${typeof slResponse.response === 'string' ? slResponse.response : 'Unknown error'}`);
267
- }
268
-
269
- // Summary
270
- console.log('\n========== Bracket Summary ==========');
271
- console.log(`Position: ${isLong ? 'LONG' : 'SHORT'} ${size} ${coin}`);
272
- console.log(`Entry: ${formatUsd(actualEntry)}`);
273
- console.log(`Take Profit: ${formatUsd(tpPrice)} (+${tpPct}%) - Trigger order`);
274
- console.log(`Stop Loss: ${formatUsd(slPrice)} (-${slPct}%) - Trigger order`);
275
- if (tpOid && slOid) {
276
- console.log(`\n✅ Bracket complete! TP and SL are trigger orders.`);
277
- console.log(` They will only execute when price reaches trigger level.`);
278
- console.log(` When one fills, cancel the other manually.`);
279
- }
280
-
287
+ entryType,
288
+ entryPrice,
289
+ slippage,
290
+ leverage,
291
+ dryRun,
292
+ verbose: args.verbose as boolean,
293
+ });
294
+ if (result.status === 'entry_failed') process.exit(1);
281
295
  } catch (error) {
282
- console.error('Error:', error);
296
+ console.error('Error:', error instanceof Error ? error.message : error);
283
297
  process.exit(1);
284
298
  }
285
299
  }