minara 0.2.9 → 0.3.1
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/dist/commands/perps.js +110 -10
- package/package.json +1 -1
package/dist/commands/perps.js
CHANGED
|
@@ -286,6 +286,8 @@ const cancelCmd = new Command('cancel')
|
|
|
286
286
|
const closeCmd = new Command('close')
|
|
287
287
|
.description('Close an open perps position at market price')
|
|
288
288
|
.option('-y, --yes', 'Skip confirmation')
|
|
289
|
+
.option('-a, --all', 'Close all open positions (non-interactive)')
|
|
290
|
+
.option('-s, --symbol <symbol>', 'Close position by symbol (non-interactive, e.g. BTC, ETH)')
|
|
289
291
|
.action(wrapAction(async (opts) => {
|
|
290
292
|
const creds = requireAuth();
|
|
291
293
|
const spin = spinner('Fetching positions…');
|
|
@@ -307,21 +309,119 @@ const closeCmd = new Command('close')
|
|
|
307
309
|
const color = n >= 0 ? chalk.green : chalk.red;
|
|
308
310
|
return color(`${n >= 0 ? '+' : ''}${fmt(n)}`);
|
|
309
311
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
312
|
+
// Helper to close specific positions (used by --all and --symbol)
|
|
313
|
+
const closePositions = async (positionsToClose, title) => {
|
|
314
|
+
console.log('');
|
|
315
|
+
console.log(chalk.bold(title));
|
|
316
|
+
console.log(` Positions to close: ${positionsToClose.length}`);
|
|
317
|
+
positionsToClose.forEach((p) => {
|
|
313
318
|
const symbol = String(p.symbol ?? '');
|
|
314
319
|
const side = String(p.side ?? '').toLowerCase();
|
|
315
|
-
const sideLabel = side === 'long' || side === 'buy' ?
|
|
320
|
+
const sideLabel = side === 'long' || side === 'buy' ? 'LONG' : 'SHORT';
|
|
316
321
|
const sz = String(p.size ?? '');
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
+
console.log(` - ${symbol} ${sideLabel} ${sz}`);
|
|
323
|
+
});
|
|
324
|
+
console.log('');
|
|
325
|
+
if (!opts.yes) {
|
|
326
|
+
await requireTransactionConfirmation(`Close ${positionsToClose.length} position(s) @ Market`);
|
|
327
|
+
}
|
|
328
|
+
await requireTouchId();
|
|
329
|
+
const orderSpin = spinner('Closing positions…');
|
|
330
|
+
const results = [];
|
|
331
|
+
for (const pos of positionsToClose) {
|
|
332
|
+
const symbol = String(pos.symbol ?? '');
|
|
333
|
+
const side = String(pos.side ?? '').toLowerCase();
|
|
334
|
+
const sz = String(pos.size ?? '');
|
|
335
|
+
const isLong = side === 'long' || side === 'buy';
|
|
336
|
+
const isBuy = !isLong;
|
|
337
|
+
const assetMeta = assets.find((a) => a.name.toUpperCase() === symbol.toUpperCase());
|
|
338
|
+
const marketPx = assetMeta?.markPx;
|
|
339
|
+
if (!marketPx || marketPx <= 0) {
|
|
340
|
+
results.push({ symbol, side, success: false, error: 'Could not fetch price' });
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const slippagePx = isBuy ? marketPx * 1.01 : marketPx * 0.99;
|
|
344
|
+
const limitPx = slippagePx.toPrecision(5);
|
|
345
|
+
const order = {
|
|
346
|
+
a: symbol,
|
|
347
|
+
b: isBuy,
|
|
348
|
+
p: limitPx,
|
|
349
|
+
s: sz,
|
|
350
|
+
r: true,
|
|
351
|
+
t: { trigger: { triggerPx: String(marketPx), tpsl: 'tp', isMarket: true } },
|
|
322
352
|
};
|
|
323
|
-
|
|
353
|
+
try {
|
|
354
|
+
const orderRes = await perpsApi.placeOrders(creds.accessToken, { orders: [order], grouping: 'na' });
|
|
355
|
+
if (orderRes.success) {
|
|
356
|
+
results.push({ symbol, side, success: true });
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
const errMsg = orderRes.error ? `${orderRes.error.code}: ${orderRes.error.message}` : 'Unknown error';
|
|
360
|
+
results.push({ symbol, side, success: false, error: errMsg });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
results.push({ symbol, side, success: false, error: String(e) });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
orderSpin.stop();
|
|
368
|
+
// Report results
|
|
369
|
+
const succeeded = results.filter((r) => r.success);
|
|
370
|
+
const failed = results.filter((r) => !r.success);
|
|
371
|
+
if (succeeded.length > 0) {
|
|
372
|
+
success(`Closed ${succeeded.length} position(s):`);
|
|
373
|
+
succeeded.forEach((r) => {
|
|
374
|
+
console.log(` ✓ ${r.symbol} ${r.side.toUpperCase()}`);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
if (failed.length > 0) {
|
|
378
|
+
warn(`Failed to close ${failed.length} position(s):`);
|
|
379
|
+
failed.forEach((r) => {
|
|
380
|
+
console.log(` ✗ ${r.symbol} ${r.side.toUpperCase()}: ${r.error}`);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
// If --all flag is set, close all positions directly (non-interactive)
|
|
385
|
+
if (opts.all) {
|
|
386
|
+
await closePositions(positions, 'Close ALL Positions:');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
// If --symbol flag is set, close positions matching the symbol (non-interactive)
|
|
390
|
+
if (opts.symbol) {
|
|
391
|
+
const symbolUpper = opts.symbol.toUpperCase();
|
|
392
|
+
const matchingPositions = positions.filter((p) => String(p.symbol ?? '').toUpperCase() === symbolUpper);
|
|
393
|
+
if (matchingPositions.length === 0) {
|
|
394
|
+
warn(`No open positions found for symbol: ${opts.symbol}`);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
await closePositions(matchingPositions, `Close ${opts.symbol.toUpperCase()} Positions:`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const positionChoices = positions.map((p) => {
|
|
401
|
+
const symbol = String(p.symbol ?? '');
|
|
402
|
+
const side = String(p.side ?? '').toLowerCase();
|
|
403
|
+
const sideLabel = side === 'long' || side === 'buy' ? chalk.green('LONG') : chalk.red('SHORT');
|
|
404
|
+
const sz = String(p.size ?? '');
|
|
405
|
+
const entry = fmt(Number(p.entryPrice ?? 0));
|
|
406
|
+
const pnl = pnlFmt(Number(p.unrealizedPnl ?? 0));
|
|
407
|
+
return {
|
|
408
|
+
name: `${chalk.bold(symbol.padEnd(6))} ${sideLabel} ${sz} @ ${chalk.yellow(entry)} PnL: ${pnl}`,
|
|
409
|
+
value: p,
|
|
410
|
+
};
|
|
324
411
|
});
|
|
412
|
+
// Add "ALL POSITIONS" option at the beginning
|
|
413
|
+
const allOption = { name: chalk.bold.cyan('[ CLOSE ALL POSITIONS ]'), value: '__ALL__' };
|
|
414
|
+
const choices = [allOption, ...positionChoices];
|
|
415
|
+
const selected = await select({
|
|
416
|
+
message: 'Select position to close:',
|
|
417
|
+
choices,
|
|
418
|
+
});
|
|
419
|
+
// Handle "ALL POSITIONS" selection
|
|
420
|
+
if (selected === '__ALL__') {
|
|
421
|
+
await closePositions(positions, 'Close ALL Positions:');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
// Single position close (existing logic)
|
|
325
425
|
const symbol = String(selected.symbol ?? '');
|
|
326
426
|
const side = String(selected.side ?? '').toLowerCase();
|
|
327
427
|
const sz = String(selected.size ?? '');
|