perp-cli 0.9.7 → 0.11.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.
Files changed (39) hide show
  1. package/dist/bot/engine.js +23 -4
  2. package/dist/bot/presets.js +3 -3
  3. package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
  4. package/dist/bot/strategies/funding-arb-strategy.js +41 -176
  5. package/dist/bot/strategies/funding-arb-utils.js +9 -1
  6. package/dist/bot/strategies/funding-arb-v2-strategy.js +20 -11
  7. package/dist/bot/strategies/funding-auto.js +2 -2
  8. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +2 -1
  9. package/dist/bot/strategies/spot-perp-arb-strategy.js +26 -18
  10. package/dist/bot/strategies/spot-perp-executor.js +4 -4
  11. package/dist/commands/bot.js +2 -2
  12. package/dist/commands/ows.d.ts +2 -0
  13. package/dist/commands/ows.js +887 -0
  14. package/dist/commands/wallet.js +378 -160
  15. package/dist/config.d.ts +9 -0
  16. package/dist/config.js +55 -9
  17. package/dist/exchanges/aster.js +2 -1
  18. package/dist/exchanges/hyperliquid.d.ts +1 -0
  19. package/dist/exchanges/hyperliquid.js +5 -0
  20. package/dist/exchanges/interface.d.ts +2 -0
  21. package/dist/exchanges/lighter-spot.js +5 -2
  22. package/dist/exchanges/lighter.js +14 -4
  23. package/dist/guardrail/contracts.d.ts +14 -0
  24. package/dist/guardrail/contracts.js +72 -0
  25. package/dist/guardrail/perp-guardrail.d.ts +21 -0
  26. package/dist/guardrail/perp-guardrail.js +137 -0
  27. package/dist/index.js +54 -4
  28. package/dist/mcp-server.js +11 -9
  29. package/dist/settings.d.ts +2 -0
  30. package/dist/settings.js +2 -0
  31. package/dist/signer/index.d.ts +2 -0
  32. package/dist/signer/index.js +2 -0
  33. package/dist/signer/ows-evm.d.ts +18 -0
  34. package/dist/signer/ows-evm.js +67 -0
  35. package/dist/signer/ows-loader.d.ts +2 -0
  36. package/dist/signer/ows-loader.js +6 -0
  37. package/dist/signer/ows-solana.d.ts +25 -0
  38. package/dist/signer/ows-solana.js +56 -0
  39. package/package.json +4 -2
@@ -125,7 +125,8 @@ export class SpotPerpArbStrategy {
125
125
  availableAdapters.set(name, a);
126
126
  }
127
127
  catch (err) {
128
- ctx.log(` [SPA] getBalance failed for ${name}: ${err instanceof Error ? err.message : String(err)}`);
128
+ const msg = (err instanceof Error ? err.message : String(err)).slice(0, 120);
129
+ ctx.log(` [SPA] getBalance failed for ${name}: ${msg}`);
129
130
  }
130
131
  }
131
132
  const ratesByExchange = new Map();
@@ -221,7 +222,8 @@ export class SpotPerpArbStrategy {
221
222
  const rcd = this._failCooldown.get(`${pos.id}:rotate`) ?? 0;
222
223
  if (Date.now() < rcd)
223
224
  continue;
224
- const best = await this.findBestPerp(pos.symbol, adapters, ratesByExchange);
225
+ const candidates = await this.findPerpCandidates(pos.symbol, adapters, ratesByExchange);
226
+ const best = candidates[0];
225
227
  if (!best || best.exchange === pos.perpExchange)
226
228
  continue;
227
229
  const currentInfo = findRate(ratesByExchange, pos.perpExchange, getPerpSymbol(pos.symbol, pos.perpExchange))
@@ -277,10 +279,12 @@ export class SpotPerpArbStrategy {
277
279
  continue;
278
280
  if (Date.now() < (this._failCooldown.get(`${base}:entry`) ?? 0))
279
281
  continue;
280
- const best = await this.findBestPerp(base, adapters, ratesByExchange);
281
- if (!best || best.score.annualized < p.min_rate || best.score.consistency < p.min_consistency)
282
- continue;
283
- scoredOpps.push({ symbol: base, spotExchange: spotExName, perpExchange: best.exchange, annualized: best.score.annualized, score: best.score });
282
+ const candidates = await this.findPerpCandidates(base, adapters, ratesByExchange);
283
+ for (const cand of candidates) {
284
+ if (cand.score.annualized < p.min_rate || cand.score.consistency < p.min_consistency)
285
+ continue;
286
+ scoredOpps.push({ symbol: base, spotExchange: spotExName, perpExchange: cand.exchange, annualized: cand.score.annualized, score: cand.score });
287
+ }
284
288
  }
285
289
  }
286
290
  catch { /* no spot */ }
@@ -350,8 +354,9 @@ export class SpotPerpArbStrategy {
350
354
  scoreFundingLocal(adapter, symbol, exchangeName) {
351
355
  return scoreFunding(adapter, symbol, exchangeName, this.params.history_periods, this._fundingScoreCache);
352
356
  }
353
- async findBestPerp(symbol, adapters, ratesByExchange) {
354
- let best = null;
357
+ /** Find all perp exchanges for a symbol, sorted by annualized rate (highest first). */
358
+ async findPerpCandidates(symbol, adapters, ratesByExchange) {
359
+ const candidates = [];
355
360
  for (const [exName, adapter] of adapters) {
356
361
  const perpSym = getPerpSymbol(symbol, exName);
357
362
  const ri = findRate(ratesByExchange, exName, perpSym) ?? findRate(ratesByExchange, exName, symbol);
@@ -361,10 +366,10 @@ export class SpotPerpArbStrategy {
361
366
  ?? await this.scoreFundingLocal(adapter, symbol, exName);
362
367
  if (!score)
363
368
  continue;
364
- if (!best || score.annualized > best.score.annualized)
365
- best = { exchange: exName, rate: ri.rate, score };
369
+ candidates.push({ exchange: exName, rate: ri.rate, score });
366
370
  }
367
- return best;
371
+ candidates.sort((a, b) => b.score.annualized - a.score.annualized);
372
+ return candidates;
368
373
  }
369
374
  calcAnnualized(pos, ratesByExchange) {
370
375
  const ri = findRate(ratesByExchange, pos.perpExchange, getPerpSymbol(pos.symbol, pos.perpExchange))
@@ -466,15 +471,18 @@ export class SpotPerpArbStrategy {
466
471
  const pBal = exchangeBalances.get(perpExchange);
467
472
  if (!sBal || !pBal)
468
473
  return false;
469
- // Check spot USDC balance too (already in spot account, no transfer needed)
474
+ // Check spot USDC balance (only add if separate from perp — skip for unified accounts like HL)
470
475
  let spotAvailable = sBal.available;
471
- try {
472
- const spotBals = await spotAdapter.getSpotBalances();
473
- const spotUsdc = spotBals.find(b => b.token.toUpperCase().startsWith("USDC"));
474
- if (spotUsdc)
475
- spotAvailable += parseFloat(spotUsdc.available);
476
+ const isUnified = spotExAdapter?.isUnifiedAccount ?? false;
477
+ if (!isUnified) {
478
+ try {
479
+ const spotBals = await spotAdapter.getSpotBalances();
480
+ const spotUsdc = spotBals.find(b => b.token.toUpperCase().startsWith("USDC"));
481
+ if (spotUsdc)
482
+ spotAvailable += parseFloat(spotUsdc.available);
483
+ }
484
+ catch { /* non-critical */ }
476
485
  }
477
- catch { /* non-critical */ }
478
486
  const maxNtl = Math.min(spotAvailable, pBal.available * leverage) * 0.8;
479
487
  if (maxNtl < targetSizeUsd) {
480
488
  if (maxNtl < 10) {
@@ -121,8 +121,8 @@ export class SpotPerpExecutor {
121
121
  const postBals = await spotAdapter.getSpotBalances();
122
122
  const bought = postBals.find(b => b.token.toUpperCase().replace(/-SPOT$/, "") === symbol.toUpperCase());
123
123
  const boughtAmt = bought ? parseFloat(bought.total) : 0;
124
- if (boughtAmt < parseFloat(matched.size) * 0.5) {
125
- log(` [SPA] Spot buy returned OK but ${symbol} balance too low (${boughtAmt.toFixed(4)}) — aborting`);
124
+ if (boughtAmt < parseFloat(matched.size) * 0.8) {
125
+ log(` [SPA] Spot buy returned OK but ${symbol} balance too low (${boughtAmt.toFixed(4)} < ${matched.size} × 80%) — aborting`);
126
126
  if (actualTransferred > 0) {
127
127
  try {
128
128
  await transferUsdcToPerp(spotAdapter, spotExchange, actualTransferred);
@@ -223,8 +223,8 @@ export class SpotPerpExecutor {
223
223
  const postBals = await spotAdapter.getSpotBalances();
224
224
  const remaining = postBals.find(b => b.token.toUpperCase().replace(/-SPOT$/, "") === symbol.toUpperCase());
225
225
  const remainingAmt = remaining ? parseFloat(remaining.total) : 0;
226
- if (remainingAmt >= parseFloat(size) * 0.5) {
227
- log(` [SPA] CRITICAL: Spot sell returned OK but ${symbol} balance unchanged (${remainingAmt.toFixed(4)}) — re-hedging perp`);
226
+ if (remainingAmt >= parseFloat(size) * 0.8) {
227
+ log(` [SPA] CRITICAL: Spot sell returned OK but ${symbol} balance unchanged (${remainingAmt.toFixed(4)} >= ${size} × 80%) — re-hedging perp`);
228
228
  try {
229
229
  await perpAdapter.marketOrder(perpSym, "sell", size);
230
230
  }
@@ -165,7 +165,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
165
165
  const config = {
166
166
  name: `arb-funding-${Date.now().toString(36)}`,
167
167
  exchange: primaryAdapter.name,
168
- symbol: "ETH", // multi-symbol scanning
168
+ symbol: "ALL", // multi-symbol scanning
169
169
  strategy: {
170
170
  type: "funding-arb",
171
171
  min_spread: parseFloat(opts.minSpread),
@@ -243,7 +243,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
243
243
  const config = {
244
244
  name: `delta-neutral-${Date.now().toString(36)}`,
245
245
  exchange: primaryAdapter.name,
246
- symbol: "ETH",
246
+ symbol: "ALL",
247
247
  strategy: {
248
248
  type: "spot-perp-arb",
249
249
  exchanges: exchangeNames,
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerOwsCommands(program: Command, isJson: () => boolean): void;