minara 0.4.0 → 0.4.2
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 +142 -30
- package/package.json +1 -1
package/dist/commands/perps.js
CHANGED
|
@@ -455,11 +455,18 @@ const positionsCmd = new Command('positions')
|
|
|
455
455
|
console.log(chalk.dim(` Total positions: ${totalPositions}`));
|
|
456
456
|
console.log('');
|
|
457
457
|
}));
|
|
458
|
-
// ─── order ───────────────────────────────────────────────────────────────
|
|
459
458
|
const orderCmd = new Command('order')
|
|
460
459
|
.description('Place a perps order')
|
|
461
460
|
.option(WALLET_OPT[0], WALLET_OPT[1])
|
|
462
461
|
.option('-y, --yes', 'Skip confirmation')
|
|
462
|
+
.option('-S, --side <side>', 'Order side: long/buy or short/sell')
|
|
463
|
+
.option('-s, --symbol <symbol>', 'Asset symbol (e.g. BTC, ETH)')
|
|
464
|
+
.option('-T, --type <type>', 'Order type: market or limit', 'market')
|
|
465
|
+
.option('-p, --price <price>', 'Limit price (required for limit orders)')
|
|
466
|
+
.option('-z, --size <size>', 'Position size in contracts')
|
|
467
|
+
.option('-r, --reduce-only', 'Reduce-only order')
|
|
468
|
+
.option('-g, --grouping <grouping>', 'TP/SL grouping: na, normalTpsl, positionTpsl', 'na')
|
|
469
|
+
.option('--tpsl <type>', 'Trigger type for market orders: tp (take profit) or sl (stop loss)', 'tp')
|
|
463
470
|
.action(wrapAction(async (opts) => {
|
|
464
471
|
const creds = requireAuth();
|
|
465
472
|
const resolved = await resolveWallet(creds.accessToken, opts.wallet, 'Place order on which wallet?');
|
|
@@ -481,6 +488,8 @@ const orderCmd = new Command('order')
|
|
|
481
488
|
console.log('');
|
|
482
489
|
return;
|
|
483
490
|
}
|
|
491
|
+
// Determine if running in non-interactive mode
|
|
492
|
+
const nonInteractive = opts.side && opts.symbol && opts.size;
|
|
484
493
|
info('Building a Hyperliquid perps order…');
|
|
485
494
|
const dataSpin = spinner('Fetching market data…');
|
|
486
495
|
const address = await perpsApi.getPerpsAddress(creds.accessToken);
|
|
@@ -493,15 +502,36 @@ const orderCmd = new Command('order')
|
|
|
493
502
|
for (const l of leverages) {
|
|
494
503
|
leverageMap.set(l.coin.toUpperCase(), { value: l.leverageValue, type: l.leverageType });
|
|
495
504
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
505
|
+
// ── Side ─────────────────────────────────────────────────────────────
|
|
506
|
+
let isBuy;
|
|
507
|
+
if (opts.side) {
|
|
508
|
+
const sideLower = opts.side.toLowerCase();
|
|
509
|
+
if (sideLower === 'long' || sideLower === 'buy') {
|
|
510
|
+
isBuy = true;
|
|
511
|
+
}
|
|
512
|
+
else if (sideLower === 'short' || sideLower === 'sell') {
|
|
513
|
+
isBuy = false;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
console.error(chalk.red('✖'), `Invalid side: ${opts.side}. Use 'long', 'buy', 'short', or 'sell'.`);
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
isBuy = await select({
|
|
522
|
+
message: 'Side:',
|
|
523
|
+
choices: [
|
|
524
|
+
{ name: 'Long (buy)', value: true },
|
|
525
|
+
{ name: 'Short (sell)', value: false },
|
|
526
|
+
],
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
// ── Asset ────────────────────────────────────────────────────────────
|
|
503
530
|
let asset;
|
|
504
|
-
if (
|
|
531
|
+
if (opts.symbol) {
|
|
532
|
+
asset = opts.symbol.toUpperCase();
|
|
533
|
+
}
|
|
534
|
+
else if (assets.length > 0) {
|
|
505
535
|
asset = await select({
|
|
506
536
|
message: 'Asset:',
|
|
507
537
|
choices: assets.map((a) => {
|
|
@@ -525,42 +555,124 @@ const orderCmd = new Command('order')
|
|
|
525
555
|
else {
|
|
526
556
|
info(`No leverage set for ${asset} — use 'minara perps leverage' to configure`);
|
|
527
557
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
558
|
+
// ── Order Type ───────────────────────────────────────────────────────
|
|
559
|
+
let orderType;
|
|
560
|
+
if (opts.type) {
|
|
561
|
+
const typeLower = opts.type.toLowerCase();
|
|
562
|
+
if (typeLower === 'market') {
|
|
563
|
+
orderType = 'market';
|
|
564
|
+
}
|
|
565
|
+
else if (typeLower === 'limit') {
|
|
566
|
+
orderType = 'limit';
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
console.error(chalk.red('✖'), `Invalid order type: ${opts.type}. Use 'market' or 'limit'.`);
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
orderType = await select({
|
|
575
|
+
message: 'Order type:',
|
|
576
|
+
choices: [
|
|
577
|
+
{ name: 'Market', value: 'market' },
|
|
578
|
+
{ name: 'Limit', value: 'limit' },
|
|
579
|
+
],
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
// ── Price ────────────────────────────────────────────────────────────
|
|
535
583
|
const assetMeta = assets.find((a) => a.name.toUpperCase() === asset.toUpperCase());
|
|
536
584
|
let limitPx;
|
|
537
585
|
let marketPx;
|
|
538
586
|
if (orderType === 'limit') {
|
|
539
|
-
|
|
587
|
+
if (opts.price) {
|
|
588
|
+
limitPx = opts.price;
|
|
589
|
+
}
|
|
590
|
+
else if (nonInteractive) {
|
|
591
|
+
console.error(chalk.red('✖'), 'Limit orders require --price');
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
limitPx = await input({ message: 'Limit price:' });
|
|
596
|
+
}
|
|
540
597
|
}
|
|
541
598
|
else {
|
|
599
|
+
// Market order
|
|
542
600
|
marketPx = assetMeta?.markPx;
|
|
543
|
-
if (
|
|
601
|
+
if (opts.price) {
|
|
602
|
+
// User specified price for market order (use as trigger)
|
|
603
|
+
limitPx = opts.price;
|
|
604
|
+
marketPx = Number(opts.price);
|
|
605
|
+
info(`Market order at ~$${marketPx}`);
|
|
606
|
+
}
|
|
607
|
+
else if (marketPx && marketPx > 0) {
|
|
544
608
|
const slippagePx = isBuy ? marketPx * 1.01 : marketPx * 0.99;
|
|
545
609
|
limitPx = slippagePx.toPrecision(5);
|
|
546
610
|
info(`Market order at ~$${marketPx}`);
|
|
547
611
|
}
|
|
612
|
+
else if (nonInteractive) {
|
|
613
|
+
console.error(chalk.red('✖'), `Could not fetch current price for ${asset}. Use --price to specify.`);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
548
616
|
else {
|
|
549
617
|
warn(`Could not fetch current price for ${asset}. Enter the approximate market price.`);
|
|
550
618
|
limitPx = await input({ message: 'Price:' });
|
|
551
619
|
marketPx = Number(limitPx);
|
|
552
620
|
}
|
|
553
621
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
622
|
+
// ── Size ─────────────────────────────────────────────────────────────
|
|
623
|
+
let sz;
|
|
624
|
+
if (opts.size) {
|
|
625
|
+
sz = opts.size;
|
|
626
|
+
}
|
|
627
|
+
else if (nonInteractive) {
|
|
628
|
+
console.error(chalk.red('✖'), 'Size is required. Use --size');
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
sz = await input({ message: 'Size (in contracts):' });
|
|
633
|
+
}
|
|
634
|
+
// ── Reduce Only ──────────────────────────────────────────────────────
|
|
635
|
+
let reduceOnly;
|
|
636
|
+
if (opts.reduceOnly !== undefined) {
|
|
637
|
+
reduceOnly = opts.reduceOnly;
|
|
638
|
+
}
|
|
639
|
+
else if (nonInteractive) {
|
|
640
|
+
reduceOnly = false;
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
reduceOnly = await confirm({ message: 'Reduce only?', default: false });
|
|
644
|
+
}
|
|
645
|
+
// ── Grouping ─────────────────────────────────────────────────────────
|
|
646
|
+
let grouping;
|
|
647
|
+
if (opts.grouping) {
|
|
648
|
+
const groupingLower = opts.grouping.toLowerCase();
|
|
649
|
+
if (groupingLower === 'na' || groupingLower === 'none') {
|
|
650
|
+
grouping = 'na';
|
|
651
|
+
}
|
|
652
|
+
else if (groupingLower === 'normaltpsl' || groupingLower === 'normal_tpsl') {
|
|
653
|
+
grouping = 'normalTpsl';
|
|
654
|
+
}
|
|
655
|
+
else if (groupingLower === 'positiontpsl' || groupingLower === 'position_tpsl') {
|
|
656
|
+
grouping = 'positionTpsl';
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
console.error(chalk.red('✖'), `Invalid grouping: ${opts.grouping}. Use 'na', 'normalTpsl', or 'positionTpsl'.`);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
else if (nonInteractive) {
|
|
664
|
+
grouping = 'na';
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
grouping = await select({
|
|
668
|
+
message: 'Grouping (TP/SL):',
|
|
669
|
+
choices: [
|
|
670
|
+
{ name: 'None', value: 'na' },
|
|
671
|
+
{ name: 'Normal TP/SL', value: 'normalTpsl' },
|
|
672
|
+
{ name: 'Position TP/SL', value: 'positionTpsl' },
|
|
673
|
+
],
|
|
674
|
+
});
|
|
675
|
+
}
|
|
564
676
|
const order = {
|
|
565
677
|
a: asset,
|
|
566
678
|
b: isBuy,
|
|
@@ -569,7 +681,7 @@ const orderCmd = new Command('order')
|
|
|
569
681
|
r: reduceOnly,
|
|
570
682
|
t: orderType === 'limit'
|
|
571
683
|
? { limit: { tif: 'Gtc' } }
|
|
572
|
-
: { trigger: { triggerPx: String(marketPx ?? limitPx), tpsl: 'tp', isMarket: true } },
|
|
684
|
+
: { trigger: { triggerPx: String(marketPx ?? limitPx), tpsl: opts.tpsl ?? 'tp', isMarket: true } },
|
|
573
685
|
};
|
|
574
686
|
const priceLabel = orderType === 'market' ? `Market (~$${marketPx ?? limitPx})` : `$${limitPx}`;
|
|
575
687
|
const levLabel = currentLev ? `${currentLev.value}x (${currentLev.type})` : '—';
|
|
@@ -578,7 +690,7 @@ const orderCmd = new Command('order')
|
|
|
578
690
|
console.log(` Asset : ${chalk.bold(order.a)}`);
|
|
579
691
|
console.log(` Side : ${formatOrderSide(order.b ? 'buy' : 'sell')}`);
|
|
580
692
|
console.log(` Leverage : ${chalk.cyan(levLabel)}`);
|
|
581
|
-
console.log(` Type : ${orderType === 'market' ?
|
|
693
|
+
console.log(` Type : ${orderType === 'market' ? `Market (${opts.tpsl === 'sl' ? 'Stop Loss' : 'Take Profit'})` : 'Limit (GTC)'}`);
|
|
582
694
|
console.log(` Price : ${chalk.yellow(priceLabel)}`);
|
|
583
695
|
console.log(` Size : ${chalk.bold(order.s)}`);
|
|
584
696
|
console.log(` Reduce Only : ${order.r ? chalk.yellow('Yes') : 'No'}`);
|