perp-cli 0.9.4 → 0.9.6
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
CHANGED
|
@@ -76,10 +76,11 @@ mode = "headless") {
|
|
|
76
76
|
tick: 0,
|
|
77
77
|
log,
|
|
78
78
|
};
|
|
79
|
-
// Pass extra adapters to strategy context
|
|
79
|
+
// Pass extra adapters and risk config to strategy context
|
|
80
80
|
if (extraAdapters) {
|
|
81
81
|
strategyCtx.state.set("extraAdapters", extraAdapters);
|
|
82
82
|
}
|
|
83
|
+
strategyCtx.state.set("riskConfig", config.risk);
|
|
83
84
|
const isDryRun = false; // TODO: wire up from config when dry-run mode is added
|
|
84
85
|
let running = true;
|
|
85
86
|
// ── TUI / JSON mode setup ──
|
|
@@ -380,10 +380,16 @@ export class FundingArbV2Strategy {
|
|
|
380
380
|
return cached.score;
|
|
381
381
|
try {
|
|
382
382
|
const history = await adapter.getFundingHistory(symbol, periods);
|
|
383
|
-
if (history.length
|
|
383
|
+
if (history.length < 2)
|
|
384
384
|
return null;
|
|
385
385
|
const rates = history.map(h => parseFloat(h.rate));
|
|
386
|
-
|
|
386
|
+
// Compute actual funding period from timestamps (no static map dependency)
|
|
387
|
+
const times = history.map(h => h.time).sort((a, b) => a - b);
|
|
388
|
+
let totalInterval = 0;
|
|
389
|
+
for (let i = 1; i < times.length; i++)
|
|
390
|
+
totalInterval += times[i] - times[i - 1];
|
|
391
|
+
const avgIntervalH = totalInterval / (times.length - 1) / 3600000;
|
|
392
|
+
const fH = avgIntervalH < 1.5 ? 1 : avgIntervalH < 5 ? 4 : 8;
|
|
387
393
|
const avgRate = rates.reduce((s, r) => s + r, 0) / rates.length;
|
|
388
394
|
const consistency = rates.filter(r => r > 0).length / rates.length;
|
|
389
395
|
const annualized = (avgRate / fH) * 8760 * 100;
|
|
@@ -354,10 +354,16 @@ export class SpotPerpArbStrategy {
|
|
|
354
354
|
return cached.score;
|
|
355
355
|
try {
|
|
356
356
|
const history = await adapter.getFundingHistory(symbol, p.history_periods);
|
|
357
|
-
if (history.length
|
|
357
|
+
if (history.length < 2)
|
|
358
358
|
return null;
|
|
359
359
|
const rates = history.map(h => parseFloat(h.rate));
|
|
360
|
-
|
|
360
|
+
// Compute actual funding period from timestamps (no static map dependency)
|
|
361
|
+
const times = history.map(h => h.time).sort((a, b) => a - b);
|
|
362
|
+
let totalInterval = 0;
|
|
363
|
+
for (let i = 1; i < times.length; i++)
|
|
364
|
+
totalInterval += times[i] - times[i - 1];
|
|
365
|
+
const avgIntervalH = totalInterval / (times.length - 1) / 3600000;
|
|
366
|
+
const fH = avgIntervalH < 1.5 ? 1 : avgIntervalH < 5 ? 4 : 8;
|
|
361
367
|
const avgRate = rates.reduce((s, r) => s + r, 0) / rates.length;
|
|
362
368
|
const consistency = rates.filter(r => r > 0).length / rates.length;
|
|
363
369
|
const annualized = (avgRate / fH) * 8760 * 100;
|
|
@@ -447,8 +453,24 @@ export class SpotPerpArbStrategy {
|
|
|
447
453
|
return false;
|
|
448
454
|
}
|
|
449
455
|
}
|
|
450
|
-
//
|
|
451
|
-
|
|
456
|
+
// Risk policy: enforce max_position_usd from risk config
|
|
457
|
+
const riskConfig = ctx.state.get("riskConfig");
|
|
458
|
+
const maxTotalPositionUsd = riskConfig?.max_position_usd ?? Infinity;
|
|
459
|
+
const positions = ctx.state.get("spaPositions");
|
|
460
|
+
let currentTotalUsd = 0;
|
|
461
|
+
for (const pos of positions) {
|
|
462
|
+
const ri = findRate(ratesByExchange, pos.perpExchange, getPerpSymbol(pos.symbol, pos.perpExchange))
|
|
463
|
+
?? findRate(ratesByExchange, pos.perpExchange, pos.symbol);
|
|
464
|
+
const price = ri?.price ?? 0;
|
|
465
|
+
currentTotalUsd += price * parseFloat(pos.size);
|
|
466
|
+
}
|
|
467
|
+
const remainingBudget = maxTotalPositionUsd - currentTotalUsd;
|
|
468
|
+
if (remainingBudget < 10) {
|
|
469
|
+
ctx.log(` [SPA] Skip ${symbol}: risk limit — total positions $${currentTotalUsd.toFixed(0)}/$${maxTotalPositionUsd} (no budget)`);
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
// Size decision (capped by risk budget)
|
|
473
|
+
let targetSizeUsd = Math.min(p.size_usd, remainingBudget);
|
|
452
474
|
const sameExchange = spotExchange === perpExchange;
|
|
453
475
|
if (sameExchange) {
|
|
454
476
|
const perpBal = await perpAdapter.getBalance();
|