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.
@@ -76,10 +76,11 @@ mode = "headless") {
76
76
  tick: 0,
77
77
  log,
78
78
  };
79
- // Pass extra adapters to strategy context for funding-arb
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 === 0)
383
+ if (history.length < 2)
384
384
  return null;
385
385
  const rates = history.map(h => parseFloat(h.rate));
386
- const fH = getFundingHours(exchange);
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 === 0)
357
+ if (history.length < 2)
358
358
  return null;
359
359
  const rates = history.map(h => parseFloat(h.rate));
360
- const fH = getFundingHours(exchangeName);
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
- // Size decision
451
- let targetSizeUsd = p.size_usd;
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();
@@ -18,7 +18,7 @@ const EXCHANGE_FUNDING_HOURS = {
18
18
  hyperliquid: 1,
19
19
  pacifica: 1,
20
20
  lighter: 8,
21
- aster: 1,
21
+ aster: 8,
22
22
  };
23
23
  /**
24
24
  * Get funding period in hours for an exchange.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",