perp-cli 0.7.39 → 0.7.43

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.
@@ -30,7 +30,7 @@ function parseBotConfig(raw) {
30
30
  const name = String(raw.name ?? `bot-${Date.now()}`);
31
31
  const exchange = String(raw.exchange ?? "hyperliquid");
32
32
  const symbol = String(raw.symbol ?? "ETH");
33
- const monitorInterval = Number(raw.monitor_interval_sec ?? raw.interval ?? 10);
33
+ const monitorInterval = Number(raw.monitor_interval_sec ?? raw.interval ?? 30);
34
34
  // Strategy
35
35
  const stratRaw = (raw.strategy ?? raw.params ?? {});
36
36
  const stratType = String(stratRaw.type ?? raw.strategy_type ?? "grid");
@@ -131,7 +131,7 @@ export function quickGridConfig(opts) {
131
131
  ...(opts.maxRuntime ? [{ type: "time_after", value: opts.maxRuntime }] : []),
132
132
  ],
133
133
  risk: { ...DEFAULT_RISK, max_drawdown: opts.maxDrawdown },
134
- monitor_interval_sec: 10,
134
+ monitor_interval_sec: 30,
135
135
  };
136
136
  }
137
137
  export function quickDCAConfig(opts) {
@@ -58,7 +58,7 @@ const gridStandard = {
58
58
  { type: "volatility_above", value: 12 },
59
59
  ],
60
60
  risk: { ...DEFAULT_RISK, max_drawdown: 100, max_daily_loss: 50 },
61
- monitor_interval_sec: 10,
61
+ monitor_interval_sec: 30,
62
62
  }),
63
63
  };
64
64
  const gridAggressive = {
@@ -111,7 +111,7 @@ const gridLongBias = {
111
111
  entry_conditions: [{ type: "always", value: 0 }],
112
112
  exit_conditions: [],
113
113
  risk: { ...DEFAULT_RISK, max_drawdown: 150, max_daily_loss: 75 },
114
- monitor_interval_sec: 10,
114
+ monitor_interval_sec: 30,
115
115
  }),
116
116
  };
117
117
  // ── DCA Presets ──
@@ -190,9 +190,44 @@ export class FundingArbStrategy {
190
190
  ctx.log(` [ARB] Need 2+ exchanges, have ${adapters.size}. Skipping.`);
191
191
  return [];
192
192
  }
193
- // Fetch rates from all exchanges
194
- const ratesByExchange = new Map();
193
+ // ── Pre-filter exchanges by margin availability ──
194
+ const exchangeBalances = new Map();
195
+ const availableAdapters = new Map();
195
196
  for (const [name, a] of adapters) {
197
+ try {
198
+ const bal = await a.getBalance();
199
+ const equity = parseFloat(bal.equity);
200
+ const marginUsed = parseFloat(bal.marginUsed);
201
+ const marginPct = equity > 0 ? (marginUsed / equity) * 100 : 100;
202
+ exchangeBalances.set(name, { equity, available: parseFloat(bal.available), marginPct });
203
+ if (marginPct < 70) {
204
+ availableAdapters.set(name, a);
205
+ }
206
+ else {
207
+ ctx.log(` [ARB] ${name} margin ${marginPct.toFixed(0)}% — skipping for this tick`);
208
+ }
209
+ }
210
+ catch {
211
+ availableAdapters.set(name, a); // on error, include it
212
+ }
213
+ }
214
+ // Determine which exchanges need rate fetching.
215
+ // When at max_positions we only need rates for exchanges that hold open positions
216
+ // (to evaluate close conditions). Exchanges with no positions can be skipped.
217
+ const atMaxPositions = openPositions.length >= (params.max_positions ?? 3);
218
+ const positionExchanges = new Set();
219
+ if (atMaxPositions) {
220
+ for (const p of openPositions) {
221
+ positionExchanges.add(p.longExchange.replace(/-spot$/i, ""));
222
+ positionExchanges.add(p.shortExchange);
223
+ }
224
+ }
225
+ const exchangesToScan = atMaxPositions
226
+ ? [...adapters].filter(([name]) => positionExchanges.has(name))
227
+ : [...adapters];
228
+ // Fetch rates — all exchanges if room for new positions, only position exchanges if full
229
+ const ratesByExchange = new Map();
230
+ for (const [name, a] of exchangesToScan) {
196
231
  const rates = await this.fetchRates(a, name);
197
232
  const map = new Map();
198
233
  for (const r of rates)
@@ -296,7 +331,7 @@ export class FundingArbStrategy {
296
331
  if (longAdapter && shortAdapter) {
297
332
  let closeLongOk = false;
298
333
  try {
299
- const closeLongResult = await longAdapter.marketOrder(pos.symbol, "sell", pos.size);
334
+ const closeLongResult = await longAdapter.marketOrder(pos.symbol, "sell", pos.size, { reduceOnly: true });
300
335
  closeLongOk = true;
301
336
  logExecution({ type: "arb_close", exchange: pos.longExchange, symbol: pos.symbol, side: "sell", size: pos.size, status: "success", dryRun: false, meta: { mode: pos.mode, leg: "long", signedSpread: signedSpread, response: closeLongResult } });
302
337
  }
@@ -307,9 +342,9 @@ export class FundingArbStrategy {
307
342
  this._failCooldown.set(`${pos.symbol}:close`, Date.now() + 5 * 60 * 1000);
308
343
  }
309
344
  if (!closeLongOk)
310
- continue; // don't close short if long failed — keep hedge intact
345
+ continue;
311
346
  try {
312
- const closeShortResult = await shortAdapter.marketOrder(pos.symbol, "buy", pos.size);
347
+ const closeShortResult = await shortAdapter.marketOrder(pos.symbol, "buy", pos.size, { reduceOnly: true });
313
348
  logExecution({ type: "arb_close", exchange: pos.shortExchange, symbol: pos.symbol, side: "buy", size: pos.size, status: "success", dryRun: false, meta: { mode: pos.mode, leg: "short", signedSpread: signedSpread, response: closeShortResult } });
314
349
  }
315
350
  catch (err) {
@@ -320,6 +355,7 @@ export class FundingArbStrategy {
320
355
  try {
321
356
  await longAdapter.marketOrder(pos.symbol, "buy", pos.size);
322
357
  ctx.log(` [ARB] Rollback: re-opened long ${pos.size} ${pos.symbol} on ${pos.longExchange}`);
358
+ // Note: re-open is NOT reduceOnly (it's a new position)
323
359
  logExecution({ type: "multi_leg_rollback", exchange: pos.longExchange, symbol: pos.symbol, side: "buy", size: pos.size, status: "success", dryRun: false });
324
360
  }
325
361
  catch (rbErr) {
@@ -345,6 +381,7 @@ export class FundingArbStrategy {
345
381
  // ── Find new opportunities ──
346
382
  const arbPositions = ctx.state.get("arbOpenPositions").length;
347
383
  const exchangeNames = [...ratesByExchange.keys()];
384
+ const scanExchangeNames = exchangeNames.filter(n => availableAdapters.has(n));
348
385
  const opportunities = [];
349
386
  const allSymbols = new Set();
350
387
  for (const [, map] of ratesByExchange) {
@@ -356,7 +393,7 @@ export class FundingArbStrategy {
356
393
  let minHourly = Infinity, maxHourly = -Infinity;
357
394
  let minExchange = "", maxExchange = "";
358
395
  let minRate = 0, maxRate = 0;
359
- for (const exName of exchangeNames) {
396
+ for (const exName of scanExchangeNames) {
360
397
  const rateInfo = this.findRate(ratesByExchange, exName, sym);
361
398
  if (!rateInfo)
362
399
  continue;
@@ -442,7 +479,6 @@ export class FundingArbStrategy {
442
479
  if (arbPositions < params.max_positions) {
443
480
  // Skip symbols already in open positions
444
481
  const openSymbols = new Set(ctx.state.get("arbOpenPositions").map(p => p.symbol.toUpperCase()));
445
- let skipMargin = 0;
446
482
  let skipCooldown = 0;
447
483
  for (const best of opportunities) {
448
484
  if (openSymbols.has(best.symbol.toUpperCase()))
@@ -480,39 +516,24 @@ export class FundingArbStrategy {
480
516
  ]);
481
517
  }
482
518
  catch { /* non-critical, some exchanges set leverage on order */ }
483
- // Balance + margin check
519
+ // Balance + margin check (use pre-fetched balances from tick start)
484
520
  let targetSizeUsd = params.size_usd;
485
- try {
486
- const [longBal, shortBal] = await Promise.all([
487
- longAdapter.getBalance(),
488
- shortAdapter.getBalance(),
489
- ]);
490
- // Check margin usage (skip if > 70%)
491
- const longEquity = parseFloat(longBal.equity);
492
- const shortEquity = parseFloat(shortBal.equity);
493
- const longMarginPct = longEquity > 0 ? parseFloat(longBal.marginUsed) / longEquity * 100 : 0;
494
- const shortMarginPct = shortEquity > 0 ? parseFloat(shortBal.marginUsed) / shortEquity * 100 : 0;
495
- if (longMarginPct > 70 || shortMarginPct > 70) {
496
- skipMargin++;
521
+ const longBal = exchangeBalances.get(best.longExchange);
522
+ const shortBal = exchangeBalances.get(best.shortExchange);
523
+ if (!longBal || !shortBal)
524
+ continue;
525
+ const minAvail = Math.min(longBal.available, shortBal.available);
526
+ const requiredMarginPerLeg = targetSizeUsd / leverage;
527
+ if (minAvail < requiredMarginPerLeg) {
528
+ // Try reducing size to what's affordable
529
+ const maxAffordableNotional = minAvail * leverage * 0.8;
530
+ if (maxAffordableNotional < 10) {
531
+ ctx.log(` [ARB] Skip ${best.symbol}: insufficient margin ($${minAvail.toFixed(2)} < $${requiredMarginPerLeg.toFixed(2)} at ${leverage}x)`);
497
532
  continue;
498
533
  }
499
- // Required margin per leg = sizeUsd / leverage
500
- const longAvail = parseFloat(longBal.available);
501
- const shortAvail = parseFloat(shortBal.available);
502
- const minAvail = Math.min(longAvail, shortAvail);
503
- const requiredMarginPerLeg = targetSizeUsd / leverage;
504
- if (minAvail < requiredMarginPerLeg) {
505
- // Try reducing size to what's affordable
506
- const maxAffordableNotional = minAvail * leverage * 0.8;
507
- if (maxAffordableNotional < 10) {
508
- ctx.log(` [ARB] Skip ${best.symbol}: insufficient margin ($${minAvail.toFixed(2)} < $${requiredMarginPerLeg.toFixed(2)} needed at ${leverage}x)`);
509
- continue;
510
- }
511
- ctx.log(` [ARB] Size reduced $${targetSizeUsd} → $${maxAffordableNotional.toFixed(0)} (limited by margin $${minAvail.toFixed(2)} at ${leverage}x)`);
512
- targetSizeUsd = Math.floor(maxAffordableNotional);
513
- }
534
+ ctx.log(` [ARB] Size reduced $${targetSizeUsd} → $${maxAffordableNotional.toFixed(0)} (limited by margin $${minAvail.toFixed(2)} at ${leverage}x)`);
535
+ targetSizeUsd = Math.floor(maxAffordableNotional);
514
536
  }
515
- catch { /* non-critical, proceed with original size */ }
516
537
  // Liquidity check
517
538
  const liq = await checkArbLiquidity(longAdapter, shortAdapter, best.symbol, targetSizeUsd, 0.5, (msg) => ctx.log(` ${msg}`));
518
539
  if (!liq.viable)
@@ -550,7 +571,7 @@ export class FundingArbStrategy {
550
571
  logExecution({ type: "arb_entry", exchange: best.shortExchange, symbol: best.symbol, side: "sell", size: matched.size, status: "failed", error: msg, dryRun: false, meta: { mode: "perp-perp", leg: "short" } });
551
572
  // Rollback: close the long position
552
573
  try {
553
- await longAdapter.marketOrder(best.symbol, "sell", matched.size);
574
+ await longAdapter.marketOrder(best.symbol, "sell", matched.size, { reduceOnly: true });
554
575
  ctx.log(` [ARB] Rollback success: closed long ${matched.size} ${best.symbol} on ${best.longExchange}`);
555
576
  logExecution({ type: "multi_leg_rollback", exchange: best.longExchange, symbol: best.symbol, side: "sell", size: matched.size, status: "success", dryRun: false });
556
577
  }
@@ -574,8 +595,10 @@ export class FundingArbStrategy {
574
595
  ctx.log(` [ARB] WARNING: Fill reconciliation failed: ${reconMsg}`);
575
596
  }
576
597
  // Verify both positions actually exist (catches instant liquidation)
577
- // Wait for cache to expire before checking (exchanges cache positions for ~5s)
578
- await new Promise(resolve => setTimeout(resolve, 6000));
598
+ // Invalidate cache so getPositions returns fresh data
599
+ const { invalidateCache } = await import("../../cache.js");
600
+ invalidateCache("acct");
601
+ await new Promise(resolve => setTimeout(resolve, 2000)); // brief wait for exchange settlement
579
602
  try {
580
603
  const [longPositions, shortPositions] = await Promise.all([
581
604
  longAdapter.getPositions(),
@@ -595,14 +618,14 @@ export class FundingArbStrategy {
595
618
  logExecution({ type: "arb_entry", exchange: `${best.longExchange}↔${best.shortExchange}`, symbol: best.symbol, side: "verify", size: matched.size, status: "failed", error: `Position missing after fill: long=${longExists} short=${shortExists}`, dryRun: false });
596
619
  if (longExists && !shortExists) {
597
620
  try {
598
- await longAdapter.marketOrder(best.symbol, "sell", matched.size);
621
+ await longAdapter.marketOrder(best.symbol, "sell", matched.size, { reduceOnly: true });
599
622
  ctx.log(` [ARB] Rollback: closed phantom long ${best.symbol}`);
600
623
  }
601
624
  catch { /* best effort */ }
602
625
  }
603
626
  if (shortExists && !longExists) {
604
627
  try {
605
- await shortAdapter.marketOrder(best.symbol, "buy", matched.size);
628
+ await shortAdapter.marketOrder(best.symbol, "buy", matched.size, { reduceOnly: true });
606
629
  ctx.log(` [ARB] Rollback: closed phantom short ${best.symbol}`);
607
630
  }
608
631
  catch { /* best effort */ }
@@ -631,13 +654,8 @@ export class FundingArbStrategy {
631
654
  return [{ type: "noop" }];
632
655
  }
633
656
  // Log skip summary only when there were skips (avoids noise when nothing happened)
634
- if (skipMargin > 0 || skipCooldown > 0) {
635
- const parts = [];
636
- if (skipMargin > 0)
637
- parts.push(`${skipMargin} margin`);
638
- if (skipCooldown > 0)
639
- parts.push(`${skipCooldown} cooldown`);
640
- ctx.log(` [ARB] Skipped ${skipMargin + skipCooldown} opportunities (${parts.join(", ")})`);
657
+ if (skipCooldown > 0) {
658
+ ctx.log(` [ARB] Skipped ${skipCooldown} opportunities (${skipCooldown} cooldown)`);
641
659
  }
642
660
  }
643
661
  }
@@ -666,7 +684,7 @@ export class FundingArbStrategy {
666
684
  if (longAdapter && shortAdapter) {
667
685
  ctx.log(` [ARB] Closing ${pos.symbol} on stop (${pos.size})`);
668
686
  try {
669
- const closeLongResult = await longAdapter.marketOrder(pos.symbol, "sell", pos.size);
687
+ const closeLongResult = await longAdapter.marketOrder(pos.symbol, "sell", pos.size, { reduceOnly: true });
670
688
  logExecution({ type: "arb_close", exchange: pos.longExchange, symbol: pos.symbol, side: "sell", size: pos.size, status: "success", dryRun: false, meta: { mode: pos.mode, leg: "long", stop: true, response: closeLongResult } });
671
689
  }
672
690
  catch (err) {
@@ -675,7 +693,7 @@ export class FundingArbStrategy {
675
693
  logExecution({ type: "arb_close", exchange: pos.longExchange, symbol: pos.symbol, side: "sell", size: pos.size, status: "failed", error: msg, dryRun: false, meta: { mode: pos.mode, leg: "long", stop: true } });
676
694
  }
677
695
  try {
678
- const closeShortResult = await shortAdapter.marketOrder(pos.symbol, "buy", pos.size);
696
+ const closeShortResult = await shortAdapter.marketOrder(pos.symbol, "buy", pos.size, { reduceOnly: true });
679
697
  logExecution({ type: "arb_close", exchange: pos.shortExchange, symbol: pos.symbol, side: "buy", size: pos.size, status: "success", dryRun: false, meta: { mode: pos.mode, leg: "short", stop: true, response: closeShortResult } });
680
698
  }
681
699
  catch (err) {
@@ -741,7 +759,7 @@ export class FundingArbStrategy {
741
759
  // Step 3: Close perp short
742
760
  try {
743
761
  const perpSymbol = this.getPerpSymbol(pos.symbol, pos.shortExchange);
744
- await perpAdapter.marketOrder(perpSymbol, "buy", pos.size);
762
+ await perpAdapter.marketOrder(perpSymbol, "buy", pos.size, { reduceOnly: true });
745
763
  ctx.log(` [ARB] Closed perp short ${pos.size} ${perpSymbol}`);
746
764
  }
747
765
  catch (err) {
package/dist/cache.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Dashboard and CLI share cached data via /tmp/perp-cli-cache/.
5
5
  * Writes are atomic (write to .tmp then rename) to prevent partial reads.
6
6
  */
7
- export declare const TTL_ACCOUNT = 5000;
7
+ export declare const TTL_ACCOUNT = 15000;
8
8
  export declare const TTL_MARKET = 30000;
9
9
  export declare function getCached<T>(key: string): T | null;
10
10
  export declare function setCached<T>(key: string, data: T, ttlMs: number): void;
package/dist/cache.js CHANGED
@@ -9,7 +9,7 @@ import { join } from "path";
9
9
  import { tmpdir } from "os";
10
10
  const CACHE_DIR = join(tmpdir(), "perp-cli-cache");
11
11
  // TTL presets
12
- export const TTL_ACCOUNT = 5_000; // 5s — balance, positions, orders
12
+ export const TTL_ACCOUNT = 15_000; // 15s — balance, positions, orders (ticks are 30s; 15s ensures same-tick dedup)
13
13
  export const TTL_MARKET = 30_000; // 30s — funding rates, prices, markets
14
14
  let dirReady = false;
15
15
  function ensureDir() {
@@ -44,7 +44,9 @@ export declare class AsterAdapter implements ExchangeAdapter {
44
44
  getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
45
45
  getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
46
46
  getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
47
- marketOrder(symbol: string, side: "buy" | "sell", size: string): Promise<unknown>;
47
+ marketOrder(symbol: string, side: "buy" | "sell", size: string, opts?: {
48
+ reduceOnly?: boolean;
49
+ }): Promise<unknown>;
48
50
  limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
49
51
  reduceOnly?: boolean;
50
52
  tif?: string;
@@ -283,14 +283,17 @@ export class AsterAdapter {
283
283
  }));
284
284
  }
285
285
  // ── Trading ──
286
- async marketOrder(symbol, side, size) {
286
+ async marketOrder(symbol, side, size, opts) {
287
287
  const apiSymbol = this._toApi(symbol);
288
- const result = await this._signedPost("/fapi/v1/order", {
288
+ const params = {
289
289
  symbol: apiSymbol,
290
290
  side: side.toUpperCase(),
291
291
  type: "MARKET",
292
292
  quantity: size,
293
- });
293
+ };
294
+ if (opts?.reduceOnly)
295
+ params.reduceOnly = "true";
296
+ const result = await this._signedPost("/fapi/v1/order", params);
294
297
  const r = result;
295
298
  const executedQty = Number(r.executedQty ?? 0);
296
299
  const orderId = String(r.orderId ?? "");
@@ -55,7 +55,9 @@ export declare class HyperliquidAdapter implements ExchangeAdapter {
55
55
  getPositions(): Promise<ExchangePosition[]>;
56
56
  getOpenOrders(): Promise<ExchangeOrder[]>;
57
57
  private _invalidateAccountCache;
58
- marketOrder(symbol: string, side: "buy" | "sell", size: string): Promise<unknown>;
58
+ marketOrder(symbol: string, side: "buy" | "sell", size: string, opts?: {
59
+ reduceOnly?: boolean;
60
+ }): Promise<unknown>;
59
61
  /**
60
62
  * Place a market order on a HIP-3 deployed dex.
61
63
  * Bypasses SDK's symbolConversion (which only knows native perps)
@@ -393,7 +393,7 @@ export class HyperliquidAdapter {
393
393
  }
394
394
  catch { /* ignore */ }
395
395
  }
396
- async marketOrder(symbol, side, size) {
396
+ async marketOrder(symbol, side, size, opts) {
397
397
  this.ensureSigner();
398
398
  if (this._dex) {
399
399
  const r = await this._dexMarketOrder(symbol, side, size);
@@ -85,7 +85,9 @@ export interface ExchangeAdapter {
85
85
  getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
86
86
  getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
87
87
  getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
88
- marketOrder(symbol: string, side: "buy" | "sell", size: string): Promise<unknown>;
88
+ marketOrder(symbol: string, side: "buy" | "sell", size: string, opts?: {
89
+ reduceOnly?: boolean;
90
+ }): Promise<unknown>;
89
91
  limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
90
92
  reduceOnly?: boolean;
91
93
  tif?: string;
@@ -62,7 +62,9 @@ export declare class LighterAdapter implements ExchangeAdapter {
62
62
  private getAuthToken;
63
63
  private restGetAuth;
64
64
  getOpenOrders(): Promise<ExchangeOrder[]>;
65
- marketOrder(symbol: string, side: "buy" | "sell", size: string): Promise<unknown>;
65
+ marketOrder(symbol: string, side: "buy" | "sell", size: string, opts?: {
66
+ reduceOnly?: boolean;
67
+ }): Promise<unknown>;
66
68
  limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
67
69
  reduceOnly?: boolean;
68
70
  tif?: string;
@@ -454,8 +454,9 @@ export class LighterAdapter {
454
454
  return [];
455
455
  }
456
456
  }
457
- async marketOrder(symbol, side, size) {
457
+ async marketOrder(symbol, side, size, opts) {
458
458
  this.ensureSigner();
459
+ void opts; // lighter doesn't support reduceOnly on market orders
459
460
  await this.ensureMarketMap();
460
461
  const nonce = await this.getNextNonce();
461
462
  const marketIndex = this.getMarketIndex(symbol);
@@ -33,7 +33,9 @@ export declare class PacificaAdapter implements ExchangeAdapter {
33
33
  getBalance(): Promise<ExchangeBalance>;
34
34
  getPositions(): Promise<ExchangePosition[]>;
35
35
  getOpenOrders(): Promise<ExchangeOrder[]>;
36
- marketOrder(symbol: string, side: "buy" | "sell", size: string): Promise<unknown>;
36
+ marketOrder(symbol: string, side: "buy" | "sell", size: string, opts?: {
37
+ reduceOnly?: boolean;
38
+ }): Promise<unknown>;
37
39
  limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
38
40
  reduceOnly?: boolean;
39
41
  tif?: string;
@@ -180,9 +180,9 @@ export class PacificaAdapter {
180
180
  };
181
181
  });
182
182
  }
183
- async marketOrder(symbol, side, size) {
183
+ async marketOrder(symbol, side, size, opts) {
184
184
  this.ensureSigner();
185
- return this.client.createMarketOrder({ symbol, amount: size, side: side === "buy" ? "bid" : "ask", reduce_only: false, slippage_percent: "1" }, this.account, this.signMessage);
185
+ return this.client.createMarketOrder({ symbol, amount: size, side: side === "buy" ? "bid" : "ask", reduce_only: opts?.reduceOnly ?? false, slippage_percent: "1" }, this.account, this.signMessage);
186
186
  }
187
187
  async limitOrder(symbol, side, price, size, opts) {
188
188
  this.ensureSigner();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.7.39",
3
+ "version": "0.7.43",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",