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