perp-cli 0.7.20 → 0.7.21
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.
|
@@ -2,6 +2,7 @@ import { registerStrategy } from "../strategy-registry.js";
|
|
|
2
2
|
import { checkArbLiquidity, checkSpotPerpLiquidity } from "../../liquidity.js";
|
|
3
3
|
import { computeMatchedSize, computeSpotPerpMatchedSize, reconcileArbFills } from "../../arb-sizing.js";
|
|
4
4
|
import { computeSignedAnnualSpread, annualizeRate } from "../../funding.js";
|
|
5
|
+
import { logExecution, pruneExecutionLog } from "../../execution-log.js";
|
|
5
6
|
export class FundingArbStrategy {
|
|
6
7
|
name = "funding-arb";
|
|
7
8
|
describe() {
|
|
@@ -22,6 +23,11 @@ export class FundingArbStrategy {
|
|
|
22
23
|
}
|
|
23
24
|
_config = {};
|
|
24
25
|
async init(ctx, _snapshot) {
|
|
26
|
+
// Prune old execution logs (keep 30 days)
|
|
27
|
+
try {
|
|
28
|
+
pruneExecutionLog(30);
|
|
29
|
+
}
|
|
30
|
+
catch { /* non-critical */ }
|
|
25
31
|
this._config = ctx.config;
|
|
26
32
|
const params = this.params;
|
|
27
33
|
ctx.state.set("arbRunning", true);
|
|
@@ -181,7 +187,7 @@ export class FundingArbStrategy {
|
|
|
181
187
|
const rates = await this.fetchRates(a, name);
|
|
182
188
|
const map = new Map();
|
|
183
189
|
for (const r of rates)
|
|
184
|
-
map.set(r.symbol.toUpperCase(), { rate: r.rate, price: r.price });
|
|
190
|
+
map.set(r.symbol.toUpperCase(), { rate: r.rate, price: r.price, sizeDecimals: r.sizeDecimals });
|
|
185
191
|
ratesByExchange.set(name, map);
|
|
186
192
|
}
|
|
187
193
|
// ── Fetch funding income periodically (every 5 minutes) ──
|
|
@@ -271,6 +277,7 @@ export class FundingArbStrategy {
|
|
|
271
277
|
]);
|
|
272
278
|
toClose.push(pos);
|
|
273
279
|
ctx.log(` [ARB] Closed ${pos.symbol} position`);
|
|
280
|
+
logExecution({ type: "arb_close", exchange: `${pos.longExchange}↔${pos.shortExchange}`, symbol: pos.symbol, side: "close", size: pos.size, status: "success", dryRun: false, meta: { mode: pos.mode, signedSpread: signedSpread } });
|
|
274
281
|
}
|
|
275
282
|
catch (err) {
|
|
276
283
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -414,8 +421,10 @@ export class FundingArbStrategy {
|
|
|
414
421
|
const liq = await checkArbLiquidity(longAdapter, shortAdapter, best.symbol, targetSizeUsd, 0.5, (msg) => ctx.log(` ${msg}`));
|
|
415
422
|
if (!liq.viable)
|
|
416
423
|
continue;
|
|
417
|
-
// Compute matched size
|
|
418
|
-
|
|
424
|
+
// Compute matched size (use actual precision from exchange market data)
|
|
425
|
+
const longDec = ratesByExchange.get(best.longExchange)?.get(best.symbol)?.sizeDecimals;
|
|
426
|
+
const shortDec = ratesByExchange.get(best.shortExchange)?.get(best.symbol)?.sizeDecimals;
|
|
427
|
+
let matched = computeMatchedSize(liq.adjustedSizeUsd, price, best.longExchange, best.shortExchange, { longSizeDecimals: longDec, shortSizeDecimals: shortDec });
|
|
419
428
|
if (!matched) {
|
|
420
429
|
ctx.log(` [ARB] Skip ${best.symbol}: can't compute matched size (min notional or precision issue)`);
|
|
421
430
|
continue;
|
|
@@ -434,7 +443,7 @@ export class FundingArbStrategy {
|
|
|
434
443
|
if (lotMatch) {
|
|
435
444
|
const lotSize = parseFloat(lotMatch[1]);
|
|
436
445
|
ctx.log(` [ARB] Lot size ${lotSize} detected, recalculating...`);
|
|
437
|
-
matched = computeMatchedSize(liq.adjustedSizeUsd, price, best.longExchange, best.shortExchange, { lotSize });
|
|
446
|
+
matched = computeMatchedSize(liq.adjustedSizeUsd, price, best.longExchange, best.shortExchange, { lotSize, longSizeDecimals: longDec, shortSizeDecimals: shortDec });
|
|
438
447
|
if (!matched) {
|
|
439
448
|
ctx.log(` [ARB] Skip ${best.symbol}: can't meet lot size ${lotSize}`);
|
|
440
449
|
continue;
|
|
@@ -473,11 +482,13 @@ export class FundingArbStrategy {
|
|
|
473
482
|
ctx.state.set("arbOpenPositions", currentPositions);
|
|
474
483
|
ctx.state.set("arbPositions", currentPositions.length);
|
|
475
484
|
ctx.log(` [ARB] Position opened! (${currentPositions.length}/${params.max_positions})`);
|
|
485
|
+
logExecution({ type: "arb_entry", exchange: `${best.longExchange}↔${best.shortExchange}`, symbol: best.symbol, side: "long/short", size: matched.size, notional: matched.notional, status: "success", dryRun: false, meta: { mode: "perp-perp", spread: best.spread } });
|
|
476
486
|
return [{ type: "noop" }];
|
|
477
487
|
}
|
|
478
488
|
catch (err) {
|
|
479
489
|
const msg = err instanceof Error ? err.message : String(err);
|
|
480
490
|
ctx.log(` [ARB] Execution failed for ${best.symbol}: ${msg}`);
|
|
491
|
+
logExecution({ type: "arb_entry", exchange: `${best.longExchange}↔${best.shortExchange}`, symbol: best.symbol, side: "long/short", size: matched?.size ?? "0", status: "failed", error: msg, dryRun: false, meta: { mode: "perp-perp" } });
|
|
481
492
|
continue;
|
|
482
493
|
}
|
|
483
494
|
}
|
|
@@ -662,11 +673,13 @@ export class FundingArbStrategy {
|
|
|
662
673
|
ctx.state.set("arbOpenPositions", currentPositions);
|
|
663
674
|
ctx.state.set("arbPositions", currentPositions.length);
|
|
664
675
|
ctx.log(` [ARB] Spot-perp position opened! (${currentPositions.length}/${params.max_positions})`);
|
|
676
|
+
logExecution({ type: "arb_entry", exchange: `${opp.longExchange}↔${opp.shortExchange}`, symbol: opp.symbol, side: "spot-long/perp-short", size: matched.size, notional: matched.notional, status: "success", dryRun: false, meta: { mode: "spot-perp", spread: opp.spread } });
|
|
665
677
|
return true;
|
|
666
678
|
}
|
|
667
679
|
catch (err) {
|
|
668
680
|
const msg = err instanceof Error ? err.message : String(err);
|
|
669
681
|
ctx.log(` [ARB] Open spot-perp failed for ${opp.symbol}: ${msg}`);
|
|
682
|
+
logExecution({ type: "arb_entry", exchange: opp.shortExchange, symbol: opp.symbol, side: "spot-long/perp-short", size: "0", status: "failed", error: msg, dryRun: false, meta: { mode: "spot-perp" } });
|
|
670
683
|
return false;
|
|
671
684
|
}
|
|
672
685
|
}
|
|
@@ -765,11 +778,12 @@ export class FundingArbStrategy {
|
|
|
765
778
|
try {
|
|
766
779
|
const markets = await adapter.getMarkets();
|
|
767
780
|
return markets
|
|
768
|
-
.filter(m => m.fundingRate != null)
|
|
781
|
+
.filter(m => m.fundingRate != null)
|
|
769
782
|
.map(m => ({
|
|
770
783
|
symbol: m.symbol,
|
|
771
784
|
rate: parseFloat(m.fundingRate),
|
|
772
785
|
price: parseFloat(m.markPrice),
|
|
786
|
+
sizeDecimals: m.sizeDecimals,
|
|
773
787
|
}));
|
|
774
788
|
}
|
|
775
789
|
catch {
|
package/dist/exchanges/aster.js
CHANGED
|
@@ -75,6 +75,8 @@ export class AsterAdapter {
|
|
|
75
75
|
const ticker = tickerMap.get(sym);
|
|
76
76
|
const premium = premiumMap.get(sym);
|
|
77
77
|
const maxLev = Number(s.maxLeverage ?? 50);
|
|
78
|
+
const lotFilter = s.filters
|
|
79
|
+
?.find(f => f.filterType === "LOT_SIZE");
|
|
78
80
|
return {
|
|
79
81
|
symbol: this._fromApi(sym),
|
|
80
82
|
markPrice: String(premium?.markPrice ?? ticker?.lastPrice ?? "0"),
|
|
@@ -83,6 +85,8 @@ export class AsterAdapter {
|
|
|
83
85
|
volume24h: String(ticker?.quoteVolume ?? ticker?.volume ?? "0"),
|
|
84
86
|
openInterest: "0",
|
|
85
87
|
maxLeverage: maxLev,
|
|
88
|
+
sizeDecimals: s.quantityPrecision != null ? Number(s.quantityPrecision) : undefined,
|
|
89
|
+
stepSize: lotFilter?.stepSize != null ? String(lotFilter.stepSize) : undefined,
|
|
86
90
|
};
|
|
87
91
|
});
|
|
88
92
|
this._marketsCache = result;
|