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.
- package/dist/bot/engine.js +23 -4
- package/dist/bot/presets.js +3 -3
- package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
- package/dist/bot/strategies/funding-arb-strategy.js +41 -176
- package/dist/bot/strategies/funding-arb-utils.js +9 -1
- package/dist/bot/strategies/funding-arb-v2-strategy.js +20 -11
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +2 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +26 -18
- package/dist/bot/strategies/spot-perp-executor.js +4 -4
- package/dist/commands/bot.js +2 -2
- package/dist/commands/ows.d.ts +2 -0
- package/dist/commands/ows.js +887 -0
- package/dist/commands/wallet.js +378 -160
- package/dist/config.d.ts +9 -0
- package/dist/config.js +55 -9
- package/dist/exchanges/aster.js +2 -1
- package/dist/exchanges/hyperliquid.d.ts +1 -0
- package/dist/exchanges/hyperliquid.js +5 -0
- package/dist/exchanges/interface.d.ts +2 -0
- package/dist/exchanges/lighter-spot.js +5 -2
- package/dist/exchanges/lighter.js +14 -4
- package/dist/guardrail/contracts.d.ts +14 -0
- package/dist/guardrail/contracts.js +72 -0
- package/dist/guardrail/perp-guardrail.d.ts +21 -0
- package/dist/guardrail/perp-guardrail.js +137 -0
- package/dist/index.js +54 -4
- package/dist/mcp-server.js +11 -9
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +2 -0
- package/dist/signer/index.d.ts +2 -0
- package/dist/signer/index.js +2 -0
- package/dist/signer/ows-evm.d.ts +18 -0
- package/dist/signer/ows-evm.js +67 -0
- package/dist/signer/ows-loader.d.ts +2 -0
- package/dist/signer/ows-loader.js +6 -0
- package/dist/signer/ows-solana.d.ts +25 -0
- package/dist/signer/ows-solana.js +56 -0
- 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
|
-
|
|
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
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
354
|
-
|
|
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
|
-
|
|
365
|
-
best = { exchange: exName, rate: ri.rate, score };
|
|
369
|
+
candidates.push({ exchange: exName, rate: ri.rate, score });
|
|
366
370
|
}
|
|
367
|
-
|
|
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
|
|
474
|
+
// Check spot USDC balance (only add if separate from perp — skip for unified accounts like HL)
|
|
470
475
|
let spotAvailable = sBal.available;
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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.
|
|
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.
|
|
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
|
}
|
package/dist/commands/bot.js
CHANGED
|
@@ -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: "
|
|
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: "
|
|
246
|
+
symbol: "ALL",
|
|
247
247
|
strategy: {
|
|
248
248
|
type: "spot-perp-arb",
|
|
249
249
|
exchanges: exchangeNames,
|