perp-cli 0.7.48 → 0.7.50
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.
|
@@ -201,12 +201,21 @@ export class FundingArbStrategy {
|
|
|
201
201
|
const equity = parseFloat(bal.equity);
|
|
202
202
|
const marginUsed = parseFloat(bal.marginUsed);
|
|
203
203
|
const marginPct = equity > 0 ? (marginUsed / equity) * 100 : 100;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
const available = parseFloat(bal.available);
|
|
205
|
+
exchangeBalances.set(name, { equity, available, marginPct });
|
|
206
|
+
// Skip if: margin > 90%, available < $5, or exchange on liquidation cooldown
|
|
207
|
+
const exchCooldown = this._failCooldown.get(`${name}:exchange`) ?? 0;
|
|
208
|
+
if (Date.now() < exchCooldown) {
|
|
209
|
+
ctx.log(` [ARB] ${name} on liquidation cooldown — skipping`);
|
|
210
|
+
}
|
|
211
|
+
else if (available < 5) {
|
|
212
|
+
ctx.log(` [ARB] ${name} available $${available.toFixed(2)} too low — skipping`);
|
|
213
|
+
}
|
|
214
|
+
else if (marginPct >= 90) {
|
|
215
|
+
ctx.log(` [ARB] ${name} margin ${marginPct.toFixed(0)}% — skipping`);
|
|
207
216
|
}
|
|
208
217
|
else {
|
|
209
|
-
|
|
218
|
+
availableAdapters.set(name, a);
|
|
210
219
|
}
|
|
211
220
|
}
|
|
212
221
|
catch (err) {
|
|
@@ -348,6 +357,8 @@ export class FundingArbStrategy {
|
|
|
348
357
|
// Position already liquidated/closed on this exchange
|
|
349
358
|
longPositionGone = true;
|
|
350
359
|
ctx.log(` [ARB] Long position gone for ${pos.symbol} on ${pos.longExchange} (liquidated?) — force closing short`);
|
|
360
|
+
// Cooldown the exchange for 30 minutes to prevent liquidation-entry cycle
|
|
361
|
+
this._failCooldown.set(`${pos.longExchange}:exchange`, Date.now() + 30 * 60 * 1000);
|
|
351
362
|
}
|
|
352
363
|
else {
|
|
353
364
|
ctx.log(` [ARB] Close long leg failed for ${pos.symbol}: ${msg} (cooldown 5m)`);
|
|
@@ -366,6 +377,7 @@ export class FundingArbStrategy {
|
|
|
366
377
|
if (isPositionGone(msg)) {
|
|
367
378
|
// Short position also gone — both liquidated, just clean up tracking
|
|
368
379
|
ctx.log(` [ARB] Short position also gone for ${pos.symbol} on ${pos.shortExchange} — removing from tracking`);
|
|
380
|
+
this._failCooldown.set(`${pos.shortExchange}:exchange`, Date.now() + 30 * 60 * 1000);
|
|
369
381
|
}
|
|
370
382
|
else if (closeLongOk) {
|
|
371
383
|
// Long was closed but short failed (not liquidated) — rollback long
|
|
@@ -623,20 +635,7 @@ export class FundingArbStrategy {
|
|
|
623
635
|
const rbMsg = rbErr instanceof Error ? rbErr.message : String(rbErr);
|
|
624
636
|
ctx.log(` [ARB] CRITICAL: Rollback failed for ${best.symbol}: ${rbMsg}`);
|
|
625
637
|
logExecution({ type: "multi_leg_rollback", exchange: best.longExchange, symbol: best.symbol, side: "sell", size: matched.size, status: "failed", error: rbMsg, dryRun: false });
|
|
626
|
-
|
|
627
|
-
const currentPositions = ctx.state.get("arbOpenPositions");
|
|
628
|
-
currentPositions.push({
|
|
629
|
-
symbol: best.symbol,
|
|
630
|
-
longExchange: best.longExchange,
|
|
631
|
-
shortExchange: best.shortExchange, // note: short leg doesn't exist
|
|
632
|
-
entrySpread: 0,
|
|
633
|
-
size: matched.size,
|
|
634
|
-
mode: "perp-perp",
|
|
635
|
-
entryTime: 0, // flag as orphaned
|
|
636
|
-
});
|
|
637
|
-
ctx.state.set("arbOpenPositions", currentPositions);
|
|
638
|
-
ctx.state.set("arbPositions", currentPositions.length);
|
|
639
|
-
ctx.log(` [ARB] Tracking orphaned long position for manual cleanup`);
|
|
638
|
+
ctx.log(` [ARB] CRITICAL: Orphaned long position on ${best.longExchange} for ${best.symbol} — operator must close manually`);
|
|
640
639
|
}
|
|
641
640
|
this._failCooldown.set(`${best.symbol}:entry`, Date.now() + 5 * 60 * 1000);
|
|
642
641
|
continue;
|
|
@@ -979,6 +978,11 @@ export class FundingArbStrategy {
|
|
|
979
978
|
const msg = spotErr instanceof Error ? spotErr.message : String(spotErr);
|
|
980
979
|
ctx.log(` [ARB] Open spot-perp failed (spot buy) for ${opp.symbol}: ${msg}`);
|
|
981
980
|
logExecution({ type: "arb_entry", exchange: opp.longExchange, symbol: opp.symbol, side: "buy", size: matched.size, status: "failed", error: msg, dryRun: false, meta: { mode: "spot-perp", leg: "spot" } });
|
|
981
|
+
try {
|
|
982
|
+
await this.transferUsdcToPerp(spotAdapter, exchangeName, transferAmt);
|
|
983
|
+
ctx.log(` [ARB] Transferred $${transferAmt} USDC back to perp after spot buy failure`);
|
|
984
|
+
}
|
|
985
|
+
catch { /* best effort */ }
|
|
982
986
|
return false;
|
|
983
987
|
}
|
|
984
988
|
// Step 3: Short perp
|
|
@@ -1143,9 +1147,12 @@ export class FundingArbStrategy {
|
|
|
1143
1147
|
// Bootstrap aster funding hours lazily (1 API call per unknown symbol, cached permanently)
|
|
1144
1148
|
if (exchangeName === "aster" && "getFundingHours" in adapter) {
|
|
1145
1149
|
const aster = adapter;
|
|
1146
|
-
// Only bootstrap symbols not yet cached (up to
|
|
1147
|
-
const uncached = withRates.filter(m =>
|
|
1148
|
-
|
|
1150
|
+
// Only bootstrap symbols not yet cached (up to 20 per tick to complete faster)
|
|
1151
|
+
const uncached = withRates.filter(m => {
|
|
1152
|
+
const cached = adapter?._fundingHoursCache?.get?.(m.symbol);
|
|
1153
|
+
return cached === undefined; // only truly uncached, not symbols confirmed as 1h
|
|
1154
|
+
});
|
|
1155
|
+
const toBootstrap = uncached.slice(0, 20);
|
|
1149
1156
|
for (const m of toBootstrap) {
|
|
1150
1157
|
const fh = await aster.getFundingHours(m.symbol);
|
|
1151
1158
|
m.fundingHours = fh;
|