perp-cli 0.9.8 → 0.12.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/README.md +30 -25
- package/dist/_stderr-filter.d.ts +0 -0
- package/dist/_stderr-filter.js +30 -0
- package/dist/agent-wallet/expiry.d.ts +15 -0
- package/dist/agent-wallet/expiry.js +26 -0
- package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
- package/dist/agent-wallet/lighter-keystore.js +232 -0
- package/dist/agent-wallet/passphrase.d.ts +13 -0
- package/dist/agent-wallet/passphrase.js +54 -0
- package/dist/agent-wallet/signing-strategy.d.ts +67 -0
- package/dist/agent-wallet/signing-strategy.js +51 -0
- package/dist/agent-wallet/store.d.ts +37 -0
- package/dist/agent-wallet/store.js +172 -0
- package/dist/api/public/_http.d.ts +15 -0
- package/dist/api/public/_http.js +24 -0
- package/dist/api/public/hyperliquid.js +36 -24
- package/dist/api/public/lighter.d.ts +7 -1
- package/dist/api/public/lighter.js +85 -37
- package/dist/api/public/pacifica.js +47 -18
- 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-auto.js +2 -2
- package/dist/bridge-engine.js +102 -49
- package/dist/cli-spec.js +1 -2
- package/dist/commands/account.js +2 -500
- package/dist/commands/agent.d.ts +8 -2
- package/dist/commands/agent.js +1903 -366
- package/dist/commands/alerts.js +3 -1
- package/dist/commands/arb/index.js +37 -7
- package/dist/commands/arb-auto.js +44 -11
- package/dist/commands/arb.js +83 -34
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +20 -16
- package/dist/commands/bridge.d.ts +1 -1
- package/dist/commands/bridge.js +4 -4
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +18 -68
- package/dist/commands/jobs.d.ts +1 -1
- package/dist/commands/jobs.js +2 -2
- package/dist/commands/manage.d.ts +1 -1
- package/dist/commands/manage.js +12 -84
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/portfolio.d.ts +19 -3
- package/dist/commands/portfolio.js +747 -163
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +18 -12
- package/dist/commands/risk.js +2 -6
- package/dist/commands/settings.js +30 -53
- package/dist/commands/trade.js +16 -15
- package/dist/commands/wallet.d.ts +2 -1
- package/dist/commands/wallet.js +1122 -176
- package/dist/config.d.ts +9 -0
- package/dist/config.js +55 -9
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +82 -2
- package/dist/event-stream.js +12 -1
- package/dist/exchanges/aster-typed-data.d.ts +156 -0
- package/dist/exchanges/aster-typed-data.js +189 -0
- package/dist/exchanges/aster.d.ts +60 -11
- package/dist/exchanges/aster.js +278 -136
- package/dist/exchanges/hyperliquid.d.ts +14 -0
- package/dist/exchanges/hyperliquid.js +88 -34
- package/dist/exchanges/lighter-spot.js +19 -5
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +213 -31
- package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
- package/dist/exchanges/pacifica-typed-data.js +74 -0
- package/dist/exchanges/pacifica.d.ts +29 -1
- package/dist/exchanges/pacifica.js +94 -6
- package/dist/funding/rates.js +59 -21
- 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 +145 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +307 -362
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +92 -88
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +69 -3
- package/dist/settings.js +3 -2
- package/dist/signer/index.d.ts +2 -0
- package/dist/signer/index.js +2 -0
- package/dist/signer/ows-evm.d.ts +32 -0
- package/dist/signer/ows-evm.js +86 -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/dist/smart-order.d.ts +8 -2
- package/dist/smart-order.js +46 -11
- package/dist/strategies/funding-arb.js +29 -8
- package/dist/trade-validator.js +30 -5
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +19 -0
- package/package.json +4 -2
- package/skills/perp-cli/SKILL.md +23 -14
- package/skills/perp-cli/references/agent-operations.md +6 -4
- package/skills/perp-cli/references/commands.md +55 -34
- package/skills/perp-cli/scripts/preflight.sh +4 -4
- package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
- package/dist/__tests__/alert-logic.test.d.ts +0 -1
- package/dist/__tests__/alert-logic.test.js +0 -107
- package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
- package/dist/__tests__/arb-auto-3dex.test.js +0 -397
- package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
- package/dist/__tests__/arb-history-stats.test.js +0 -176
- package/dist/__tests__/arb-logic.test.d.ts +0 -1
- package/dist/__tests__/arb-logic.test.js +0 -84
- package/dist/__tests__/arb-manage.test.d.ts +0 -1
- package/dist/__tests__/arb-manage.test.js +0 -253
- package/dist/__tests__/arb-new-features.test.d.ts +0 -1
- package/dist/__tests__/arb-new-features.test.js +0 -457
- package/dist/__tests__/arb-sizing.test.d.ts +0 -1
- package/dist/__tests__/arb-sizing.test.js +0 -48
- package/dist/__tests__/arb-state.test.d.ts +0 -1
- package/dist/__tests__/arb-state.test.js +0 -284
- package/dist/__tests__/arb-userflow.test.d.ts +0 -1
- package/dist/__tests__/arb-userflow.test.js +0 -945
- package/dist/__tests__/arb-utils.test.d.ts +0 -1
- package/dist/__tests__/arb-utils.test.js +0 -264
- package/dist/__tests__/bot-conditions.test.d.ts +0 -1
- package/dist/__tests__/bot-conditions.test.js +0 -341
- package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
- package/dist/__tests__/client-id-tracker.test.js +0 -137
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
- package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
- package/dist/__tests__/commands/order-intent.test.js +0 -600
- package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/trade-commands.test.js +0 -821
- package/dist/__tests__/config.test.d.ts +0 -1
- package/dist/__tests__/config.test.js +0 -86
- package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
- package/dist/__tests__/cross-chain-margin.test.js +0 -287
- package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
- package/dist/__tests__/dex-asset-map.test.js +0 -191
- package/dist/__tests__/errors.test.d.ts +0 -1
- package/dist/__tests__/errors.test.js +0 -110
- package/dist/__tests__/event-stream.test.d.ts +0 -1
- package/dist/__tests__/event-stream.test.js +0 -276
- package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
- package/dist/__tests__/exchanges/interface.test.js +0 -132
- package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
- package/dist/__tests__/exchanges/mock-adapter.js +0 -137
- package/dist/__tests__/execution-log.test.d.ts +0 -1
- package/dist/__tests__/execution-log.test.js +0 -106
- package/dist/__tests__/funding-calc.test.d.ts +0 -1
- package/dist/__tests__/funding-calc.test.js +0 -71
- package/dist/__tests__/funding-history.test.d.ts +0 -1
- package/dist/__tests__/funding-history.test.js +0 -343
- package/dist/__tests__/funding-rates.test.d.ts +0 -1
- package/dist/__tests__/funding-rates.test.js +0 -342
- package/dist/__tests__/funding.test.d.ts +0 -1
- package/dist/__tests__/funding.test.js +0 -173
- package/dist/__tests__/gap-logic.test.d.ts +0 -1
- package/dist/__tests__/gap-logic.test.js +0 -43
- package/dist/__tests__/hip3-dex.test.d.ts +0 -1
- package/dist/__tests__/hip3-dex.test.js +0 -234
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
- package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge.integration.test.js +0 -309
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
- package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/lighter.integration.test.js +0 -53
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
- package/dist/__tests__/liquidity.test.d.ts +0 -1
- package/dist/__tests__/liquidity.test.js +0 -225
- package/dist/__tests__/plan-executor.test.d.ts +0 -1
- package/dist/__tests__/plan-executor.test.js +0 -314
- package/dist/__tests__/position-history.test.d.ts +0 -1
- package/dist/__tests__/position-history.test.js +0 -367
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -310
- package/dist/__tests__/risk-assessment.test.d.ts +0 -1
- package/dist/__tests__/risk-assessment.test.js +0 -288
- package/dist/__tests__/security-adversarial.test.d.ts +0 -1
- package/dist/__tests__/security-adversarial.test.js +0 -574
- package/dist/__tests__/strategies.test.d.ts +0 -1
- package/dist/__tests__/strategies.test.js +0 -539
- package/dist/__tests__/trade-execution.test.d.ts +0 -1
- package/dist/__tests__/trade-execution.test.js +0 -129
- package/dist/__tests__/trade-validator.test.d.ts +0 -1
- package/dist/__tests__/trade-validator.test.js +0 -655
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -76
- package/dist/arb/reconcile.d.ts +0 -28
- package/dist/arb/reconcile.js +0 -162
- package/dist/commands/alert.d.ts +0 -2
- package/dist/commands/alert.js +0 -421
- package/dist/commands/analytics.d.ts +0 -3
- package/dist/commands/analytics.js +0 -311
- package/dist/commands/arb-manage.d.ts +0 -5
- package/dist/commands/arb-manage.js +0 -5
- package/dist/commands/dashboard.d.ts +0 -3
- package/dist/commands/dashboard.js +0 -176
- package/dist/commands/deposit.d.ts +0 -4
- package/dist/commands/deposit.js +0 -573
- package/dist/commands/dex.d.ts +0 -3
- package/dist/commands/dex.js +0 -120
- package/dist/commands/env.d.ts +0 -2
- package/dist/commands/env.js +0 -136
- package/dist/commands/funding.d.ts +0 -3
- package/dist/commands/funding.js +0 -554
- package/dist/commands/gap.d.ts +0 -2
- package/dist/commands/gap.js +0 -305
- package/dist/commands/health.d.ts +0 -2
- package/dist/commands/health.js +0 -67
- package/dist/commands/multileg.d.ts +0 -16
- package/dist/commands/multileg.js +0 -174
- package/dist/commands/pnl.d.ts +0 -12
- package/dist/commands/pnl.js +0 -243
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -202
- package/dist/commands/stream.d.ts +0 -5
- package/dist/commands/stream.js +0 -200
- package/dist/commands/withdraw.d.ts +0 -3
- package/dist/commands/withdraw.js +0 -187
- package/dist/exchanges/edgex.d.ts +0 -62
- package/dist/exchanges/edgex.js +0 -552
- package/dist/exchanges/nado.d.ts +0 -52
- package/dist/exchanges/nado.js +0 -223
- package/dist/funding/index.d.ts +0 -3
- package/dist/funding/index.js +0 -3
- package/dist/retry.d.ts +0 -74
- package/dist/retry.js +0 -129
- package/dist/ws/market-feed.d.ts +0 -74
- package/dist/ws/market-feed.js +0 -227
package/dist/commands/funding.js
DELETED
|
@@ -1,554 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { makeTable, formatPercent, formatUsd, formatPnl, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
|
|
3
|
-
import { fetchAllFundingRates, fetchSymbolFundingRates, TOP_SYMBOLS, } from "../funding-rates.js";
|
|
4
|
-
import { estimateHourlyFunding, annualizeHourlyRate, annualizeRate, toHourlyRate } from "../funding.js";
|
|
5
|
-
import { saveFundingSnapshot, getHistoricalRates, getCompoundedAnnualReturn, getExchangeCompoundingHours, } from "../funding-history.js";
|
|
6
|
-
const ALL_EXCHANGES = ["hyperliquid", "pacifica", "lighter"];
|
|
7
|
-
export function registerFundingCommands(program, isJson, getAdapterForExchange) {
|
|
8
|
-
const funding = program.command("funding").description("Funding rate comparison across exchanges");
|
|
9
|
-
// ── perp funding rates ── (default: show top symbols)
|
|
10
|
-
funding
|
|
11
|
-
.command("rates")
|
|
12
|
-
.description("Show funding rates across all 3 DEXs for top symbols")
|
|
13
|
-
.option("-s, --symbol <symbol>", "Filter to a specific symbol")
|
|
14
|
-
.option("--symbols <list>", "Comma-separated list of symbols")
|
|
15
|
-
.option("--all", "Show all available symbols (not just top ones)")
|
|
16
|
-
.option("--min-spread <pct>", "Minimum annual spread % to show", "0")
|
|
17
|
-
.action(async (opts) => {
|
|
18
|
-
const minSpread = parseFloat(opts.minSpread);
|
|
19
|
-
let filterSymbols;
|
|
20
|
-
if (opts.symbol) {
|
|
21
|
-
filterSymbols = [opts.symbol.toUpperCase()];
|
|
22
|
-
}
|
|
23
|
-
else if (opts.symbols) {
|
|
24
|
-
filterSymbols = opts.symbols.split(",").map(s => s.trim().toUpperCase());
|
|
25
|
-
}
|
|
26
|
-
else if (!opts.all) {
|
|
27
|
-
filterSymbols = TOP_SYMBOLS;
|
|
28
|
-
}
|
|
29
|
-
if (!isJson())
|
|
30
|
-
console.log(chalk.cyan(" Fetching funding rates from all exchanges...\n"));
|
|
31
|
-
const snapshot = await fetchAllFundingRates({
|
|
32
|
-
symbols: filterSymbols,
|
|
33
|
-
minSpread,
|
|
34
|
-
});
|
|
35
|
-
// Persist snapshot for historical tracking (moved from fetchAllFundingRates to avoid side effect)
|
|
36
|
-
try {
|
|
37
|
-
const allRates = snapshot.symbols.flatMap(s => s.rates);
|
|
38
|
-
if (allRates.length > 0)
|
|
39
|
-
saveFundingSnapshot(allRates);
|
|
40
|
-
}
|
|
41
|
-
catch { /* non-critical */ }
|
|
42
|
-
if (isJson()) {
|
|
43
|
-
// When --ndjson or --fields is used, return the symbols array directly
|
|
44
|
-
// so each symbol gets its own line / fields can be picked per element.
|
|
45
|
-
// Otherwise return the full snapshot (includes exchangeStatus metadata).
|
|
46
|
-
if (process.argv.includes("--ndjson") || process.argv.includes("--fields")) {
|
|
47
|
-
return printJson(jsonOk(snapshot.symbols));
|
|
48
|
-
}
|
|
49
|
-
return printJson(jsonOk(snapshot));
|
|
50
|
-
}
|
|
51
|
-
printSnapshotTable(snapshot);
|
|
52
|
-
printExchangeStatus(snapshot);
|
|
53
|
-
console.log(chalk.gray(" * Rates shown are current predictions. Actual settled rates may differ.\n"));
|
|
54
|
-
});
|
|
55
|
-
// ── perp funding compare <symbol> ── (detailed single-symbol view)
|
|
56
|
-
funding
|
|
57
|
-
.command("compare <symbol>")
|
|
58
|
-
.description("Detailed funding rate comparison for a single symbol")
|
|
59
|
-
.action(async (symbol) => {
|
|
60
|
-
if (!isJson())
|
|
61
|
-
console.log(chalk.cyan(` Fetching funding rates for ${symbol.toUpperCase()}...\n`));
|
|
62
|
-
const comparison = await fetchSymbolFundingRates(symbol);
|
|
63
|
-
if (!comparison) {
|
|
64
|
-
if (isJson())
|
|
65
|
-
return printJson(jsonOk({ symbol: symbol.toUpperCase(), available: false }));
|
|
66
|
-
console.log(chalk.gray(` ${symbol.toUpperCase()} not found on at least 2 exchanges.\n`));
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (isJson())
|
|
70
|
-
return printJson(jsonOk(comparison));
|
|
71
|
-
printDetailedComparison(comparison);
|
|
72
|
-
});
|
|
73
|
-
// ── perp funding spread ── (sorted by spread opportunities)
|
|
74
|
-
funding
|
|
75
|
-
.command("spread")
|
|
76
|
-
.description("Show best funding rate arb opportunities sorted by spread")
|
|
77
|
-
.option("--min <pct>", "Min annual spread to show", "10")
|
|
78
|
-
.option("--top <n>", "Show top N opportunities", "20")
|
|
79
|
-
.action(async (opts) => {
|
|
80
|
-
const minSpread = parseFloat(opts.min);
|
|
81
|
-
const topN = parseInt(opts.top);
|
|
82
|
-
if (!isJson())
|
|
83
|
-
console.log(chalk.cyan(" Scanning funding rate spreads across all exchanges...\n"));
|
|
84
|
-
const snapshot = await fetchAllFundingRates({ minSpread });
|
|
85
|
-
const shown = snapshot.symbols.slice(0, topN);
|
|
86
|
-
if (isJson())
|
|
87
|
-
return printJson(jsonOk(shown));
|
|
88
|
-
if (shown.length === 0) {
|
|
89
|
-
console.log(chalk.gray(` No opportunities above ${minSpread}% annual spread.\n`));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
console.log(chalk.cyan.bold(" Funding Rate Arb Opportunities\n"));
|
|
93
|
-
console.log(chalk.gray(" Strategy: Long on low-funding exchange, Short on high-funding exchange\n"));
|
|
94
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
95
|
-
const rows = shown.map(s => {
|
|
96
|
-
const pacRate = s.rates.find(r => r.exchange === "pacifica");
|
|
97
|
-
const hlRate = s.rates.find(r => r.exchange === "hyperliquid");
|
|
98
|
-
const ltRate = s.rates.find(r => r.exchange === "lighter");
|
|
99
|
-
const spreadColor = s.maxSpreadAnnual >= 50 ? chalk.green.bold
|
|
100
|
-
: s.maxSpreadAnnual >= 20 ? chalk.green
|
|
101
|
-
: chalk.yellow;
|
|
102
|
-
return [
|
|
103
|
-
chalk.white.bold(s.symbol),
|
|
104
|
-
`$${formatUsd(s.bestMarkPrice)}`,
|
|
105
|
-
pacRate ? formatPercent(pacRate.fundingRate) : chalk.gray("-"),
|
|
106
|
-
hlRate ? formatPercent(hlRate.fundingRate) : chalk.gray("-"),
|
|
107
|
-
ltRate ? formatPercent(ltRate.fundingRate) : chalk.gray("-"),
|
|
108
|
-
spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`),
|
|
109
|
-
getAvgSpread(s, "avg24h"),
|
|
110
|
-
`${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}`,
|
|
111
|
-
`$${s.estHourlyIncomeUsd.toFixed(4)}/hr`,
|
|
112
|
-
];
|
|
113
|
-
});
|
|
114
|
-
console.log(makeTable(["Symbol", "Price", "Pacifica", "Hyperliquid", "Lighter", "Ann.Spread", "Avg Spread(24h)", "Direction", "Est.Income/$1K"], rows));
|
|
115
|
-
console.log(chalk.gray(`\n ${shown.length} opportunities above ${minSpread}% annual spread`));
|
|
116
|
-
console.log(chalk.gray(` Income estimated for $1,000 notional per leg`));
|
|
117
|
-
console.log(chalk.gray(` Use 'perp arb auto --min-spread ${minSpread}' to auto-trade`));
|
|
118
|
-
console.log(chalk.gray("\n * Rates shown are current predictions. Actual settled rates may differ.\n"));
|
|
119
|
-
});
|
|
120
|
-
// ── perp funding history ── (rate trend over time)
|
|
121
|
-
funding
|
|
122
|
-
.command("history")
|
|
123
|
-
.description("Show funding rate trend over time for a symbol")
|
|
124
|
-
.requiredOption("-s, --symbol <symbol>", "Symbol to show history for")
|
|
125
|
-
.option("--hours <n>", "Number of hours to look back", "24")
|
|
126
|
-
.option("--exchange <ex>", "Filter to a specific exchange")
|
|
127
|
-
.action(async (opts) => {
|
|
128
|
-
const symbol = opts.symbol.toUpperCase();
|
|
129
|
-
const hours = parseInt(opts.hours);
|
|
130
|
-
const exchanges = opts.exchange
|
|
131
|
-
? [opts.exchange.toLowerCase()]
|
|
132
|
-
: ["hyperliquid", "pacifica", "lighter"];
|
|
133
|
-
const endTime = new Date();
|
|
134
|
-
const startTime = new Date(endTime.getTime() - hours * 60 * 60 * 1000);
|
|
135
|
-
if (isJson()) {
|
|
136
|
-
const data = {};
|
|
137
|
-
for (const ex of exchanges) {
|
|
138
|
-
const rates = getHistoricalRates(symbol, ex, startTime, endTime);
|
|
139
|
-
if (rates.length > 0)
|
|
140
|
-
data[ex] = rates;
|
|
141
|
-
}
|
|
142
|
-
return printJson(jsonOk({ symbol, hours, startTime: startTime.toISOString(), endTime: endTime.toISOString(), rates: data }));
|
|
143
|
-
}
|
|
144
|
-
console.log(chalk.cyan.bold(` ${symbol} Funding Rate History (last ${hours}h)\n`));
|
|
145
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
146
|
-
let hasData = false;
|
|
147
|
-
for (const ex of exchanges) {
|
|
148
|
-
const rates = getHistoricalRates(symbol, ex, startTime, endTime);
|
|
149
|
-
if (rates.length === 0)
|
|
150
|
-
continue;
|
|
151
|
-
hasData = true;
|
|
152
|
-
console.log(chalk.white.bold(` ${exAbbr(ex)} (${rates.length} snapshots):`));
|
|
153
|
-
const rows = rates.map(r => {
|
|
154
|
-
const date = new Date(r.ts);
|
|
155
|
-
const timeStr = date.toLocaleString();
|
|
156
|
-
const hourlyPct = (r.hourlyRate * 100).toFixed(6);
|
|
157
|
-
const annualPct = annualizeHourlyRate(r.hourlyRate).toFixed(2);
|
|
158
|
-
const color = r.rate > 0 ? chalk.red : r.rate < 0 ? chalk.green : chalk.white;
|
|
159
|
-
return [
|
|
160
|
-
chalk.gray(timeStr),
|
|
161
|
-
color(formatPercent(r.rate)),
|
|
162
|
-
`${hourlyPct}%/h`,
|
|
163
|
-
`${annualPct}%/yr`,
|
|
164
|
-
];
|
|
165
|
-
});
|
|
166
|
-
console.log(makeTable(["Time", "Raw Rate", "Hourly", "Annualized"], rows));
|
|
167
|
-
console.log();
|
|
168
|
-
}
|
|
169
|
-
if (!hasData) {
|
|
170
|
-
console.log(chalk.gray(` No historical data found for ${symbol} in the last ${hours}h.`));
|
|
171
|
-
console.log(chalk.gray(` Run 'perp funding rates' to start collecting data.\n`));
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
// ── perp funding monitor ── (live refreshing)
|
|
175
|
-
funding
|
|
176
|
-
.command("monitor")
|
|
177
|
-
.description("Live-monitor funding rates with auto-refresh")
|
|
178
|
-
.option("--min <pct>", "Min annual spread to show", "10")
|
|
179
|
-
.option("--interval <sec>", "Refresh interval in seconds", "30")
|
|
180
|
-
.option("--top <n>", "Show top N", "15")
|
|
181
|
-
.option("--symbols <list>", "Comma-separated symbols to watch")
|
|
182
|
-
.action(async (opts) => {
|
|
183
|
-
const minSpread = parseFloat(opts.min);
|
|
184
|
-
const intervalSec = parseInt(opts.interval);
|
|
185
|
-
const topN = parseInt(opts.top);
|
|
186
|
-
const filterSymbols = opts.symbols?.split(",").map(s => s.trim().toUpperCase());
|
|
187
|
-
let cycle = 0;
|
|
188
|
-
if (!isJson()) {
|
|
189
|
-
console.log(chalk.cyan.bold("\n Funding Rate Monitor"));
|
|
190
|
-
console.log(chalk.gray(` Min spread: ${minSpread}% | Refresh: ${intervalSec}s | Top: ${topN}`));
|
|
191
|
-
if (filterSymbols)
|
|
192
|
-
console.log(chalk.gray(` Symbols: ${filterSymbols.join(", ")}`));
|
|
193
|
-
console.log(chalk.gray(` Press Ctrl+C to stop\n`));
|
|
194
|
-
}
|
|
195
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
196
|
-
while (true) {
|
|
197
|
-
cycle++;
|
|
198
|
-
const ts = new Date().toLocaleTimeString();
|
|
199
|
-
try {
|
|
200
|
-
const snapshot = await fetchAllFundingRates({
|
|
201
|
-
symbols: filterSymbols,
|
|
202
|
-
minSpread,
|
|
203
|
-
});
|
|
204
|
-
const shown = snapshot.symbols.slice(0, topN);
|
|
205
|
-
if (isJson()) {
|
|
206
|
-
printJson(jsonOk({ cycle, timestamp: ts, opportunities: shown }));
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
// Clear previous output
|
|
210
|
-
if (cycle > 1) {
|
|
211
|
-
const linesToClear = shown.length + 4;
|
|
212
|
-
process.stdout.write(`\x1b[${linesToClear}A\x1b[J`);
|
|
213
|
-
}
|
|
214
|
-
console.log(chalk.gray(` ${ts} -- Cycle ${cycle} | ${shown.length} opportunities >= ${minSpread}%\n`));
|
|
215
|
-
if (shown.length === 0) {
|
|
216
|
-
console.log(chalk.gray(` No opportunities found.\n`));
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
for (const s of shown) {
|
|
220
|
-
const direction = `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}`;
|
|
221
|
-
const spreadColor = s.maxSpreadAnnual >= 50 ? chalk.green.bold
|
|
222
|
-
: s.maxSpreadAnnual >= 30 ? chalk.green
|
|
223
|
-
: chalk.yellow;
|
|
224
|
-
const rateStrs = [];
|
|
225
|
-
for (const r of s.rates) {
|
|
226
|
-
rateStrs.push(`${exAbbr(r.exchange)}:${(r.fundingRate * 100).toFixed(4)}%`);
|
|
227
|
-
}
|
|
228
|
-
console.log(` ${chalk.white.bold(s.symbol.padEnd(8))} ` +
|
|
229
|
-
`${spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`.padEnd(8))} ` +
|
|
230
|
-
`${direction.padEnd(7)} ` +
|
|
231
|
-
rateStrs.join(" "));
|
|
232
|
-
}
|
|
233
|
-
console.log();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
catch (err) {
|
|
238
|
-
if (!isJson()) {
|
|
239
|
-
console.log(chalk.red(` ${ts} Error: ${err instanceof Error ? err.message : err}\n`));
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
await new Promise(r => setTimeout(r, intervalSec * 1000));
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
// ── perp funding positions ── (funding impact on open positions)
|
|
246
|
-
funding
|
|
247
|
-
.command("positions")
|
|
248
|
-
.description("Show funding rate impact on your current positions")
|
|
249
|
-
.option("--exchanges <list>", "Comma-separated exchanges to check (default: all with keys)")
|
|
250
|
-
.action(async (opts) => {
|
|
251
|
-
if (!getAdapterForExchange) {
|
|
252
|
-
const msg = "funding positions requires adapter access";
|
|
253
|
-
if (isJson())
|
|
254
|
-
return printJson(jsonError("INTERNAL", msg));
|
|
255
|
-
console.log(chalk.red(` ${msg}\n`));
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
await withJsonErrors(isJson(), async () => {
|
|
259
|
-
const targetExchanges = opts.exchanges
|
|
260
|
-
? opts.exchanges.split(",").map(s => s.trim().toLowerCase())
|
|
261
|
-
: [...ALL_EXCHANGES];
|
|
262
|
-
if (!isJson())
|
|
263
|
-
console.log(chalk.cyan(" Fetching positions and funding rates...\n"));
|
|
264
|
-
const results = [];
|
|
265
|
-
const exchangeErrors = {};
|
|
266
|
-
await Promise.all(targetExchanges.map(async (exchange) => {
|
|
267
|
-
try {
|
|
268
|
-
const adapter = await getAdapterForExchange(exchange);
|
|
269
|
-
const [positions, markets, fundingPayments] = await Promise.all([
|
|
270
|
-
adapter.getPositions(),
|
|
271
|
-
adapter.getMarkets(),
|
|
272
|
-
adapter.getFundingPayments(100).catch(() => []),
|
|
273
|
-
]);
|
|
274
|
-
if (positions.length === 0)
|
|
275
|
-
return;
|
|
276
|
-
// Build symbol → funding rate map
|
|
277
|
-
const fundingMap = new Map();
|
|
278
|
-
for (const m of markets) {
|
|
279
|
-
const sym = m.symbol.toUpperCase().replace(/-PERP$/, "");
|
|
280
|
-
fundingMap.set(sym, {
|
|
281
|
-
rate: Number(m.fundingRate) || 0,
|
|
282
|
-
markPrice: Number(m.markPrice) || 0,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
// Aggregate actual funding payments per symbol (last 24h)
|
|
286
|
-
const now = Date.now();
|
|
287
|
-
const dayAgo = now - 24 * 60 * 60 * 1000;
|
|
288
|
-
const actualFunding = new Map();
|
|
289
|
-
for (const fp of fundingPayments) {
|
|
290
|
-
if (fp.time < dayAgo)
|
|
291
|
-
continue;
|
|
292
|
-
const sym = fp.symbol.toUpperCase().replace(/-PERP$/, "");
|
|
293
|
-
const amt = Number(fp.payment) || 0;
|
|
294
|
-
if (!actualFunding.has(sym))
|
|
295
|
-
actualFunding.set(sym, { received: 0, paid: 0 });
|
|
296
|
-
const entry = actualFunding.get(sym);
|
|
297
|
-
if (amt > 0)
|
|
298
|
-
entry.received += amt;
|
|
299
|
-
else
|
|
300
|
-
entry.paid += Math.abs(amt);
|
|
301
|
-
}
|
|
302
|
-
for (const pos of positions) {
|
|
303
|
-
const sym = pos.symbol.toUpperCase().replace(/-PERP$/, "");
|
|
304
|
-
const fdata = fundingMap.get(sym);
|
|
305
|
-
const fundingRate = fdata?.rate ?? 0;
|
|
306
|
-
const mark = Number(pos.markPrice) || fdata?.markPrice || 0;
|
|
307
|
-
const size = Math.abs(Number(pos.size));
|
|
308
|
-
const notionalUsd = size * mark;
|
|
309
|
-
const hourlyRate = toHourlyRate(fundingRate, exchange);
|
|
310
|
-
const annualPct = annualizeRate(fundingRate, exchange);
|
|
311
|
-
const hourlyPayment = estimateHourlyFunding(fundingRate, exchange, notionalUsd, pos.side);
|
|
312
|
-
const dailyPayment = hourlyPayment * 24;
|
|
313
|
-
const actual = actualFunding.get(sym);
|
|
314
|
-
const actualReceived24h = actual?.received ?? 0;
|
|
315
|
-
const actualPaid24h = actual?.paid ?? 0;
|
|
316
|
-
const actualNet24h = actualReceived24h - actualPaid24h;
|
|
317
|
-
results.push({
|
|
318
|
-
exchange,
|
|
319
|
-
symbol: sym,
|
|
320
|
-
side: pos.side,
|
|
321
|
-
size: pos.size,
|
|
322
|
-
entryPrice: pos.entryPrice,
|
|
323
|
-
markPrice: pos.markPrice,
|
|
324
|
-
unrealizedPnl: pos.unrealizedPnl,
|
|
325
|
-
leverage: pos.leverage,
|
|
326
|
-
notionalUsd,
|
|
327
|
-
fundingRate,
|
|
328
|
-
hourlyRate,
|
|
329
|
-
annualPct,
|
|
330
|
-
hourlyPayment,
|
|
331
|
-
dailyPayment,
|
|
332
|
-
actualReceived24h,
|
|
333
|
-
actualPaid24h,
|
|
334
|
-
actualNet24h,
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
catch (err) {
|
|
339
|
-
exchangeErrors[exchange] = err instanceof Error ? err.message : String(err);
|
|
340
|
-
}
|
|
341
|
-
}));
|
|
342
|
-
if (results.length === 0) {
|
|
343
|
-
if (isJson()) {
|
|
344
|
-
return printJson(jsonOk({
|
|
345
|
-
positions: [],
|
|
346
|
-
totals: {
|
|
347
|
-
predicted: { hourly: 0, daily: 0 },
|
|
348
|
-
actual24h: { net: 0 },
|
|
349
|
-
notionalUsd: 0,
|
|
350
|
-
},
|
|
351
|
-
errors: exchangeErrors,
|
|
352
|
-
}));
|
|
353
|
-
}
|
|
354
|
-
console.log(chalk.gray(" No open positions found on any exchange.\n"));
|
|
355
|
-
if (Object.keys(exchangeErrors).length > 0) {
|
|
356
|
-
for (const [ex, err] of Object.entries(exchangeErrors)) {
|
|
357
|
-
console.log(chalk.yellow(` ${ex}: ${err}`));
|
|
358
|
-
}
|
|
359
|
-
console.log();
|
|
360
|
-
}
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
// Sort: biggest daily payment impact first
|
|
364
|
-
results.sort((a, b) => Math.abs(b.dailyPayment) - Math.abs(a.dailyPayment));
|
|
365
|
-
const totalHourly = results.reduce((s, r) => s + r.hourlyPayment, 0);
|
|
366
|
-
const totalDaily = results.reduce((s, r) => s + r.dailyPayment, 0);
|
|
367
|
-
const totalNotional = results.reduce((s, r) => s + r.notionalUsd, 0);
|
|
368
|
-
const totalActualNet = results.reduce((s, r) => s + r.actualNet24h, 0);
|
|
369
|
-
if (isJson()) {
|
|
370
|
-
return printJson(jsonOk({
|
|
371
|
-
positions: results.map(r => ({
|
|
372
|
-
exchange: r.exchange,
|
|
373
|
-
symbol: r.symbol,
|
|
374
|
-
side: r.side,
|
|
375
|
-
size: r.size,
|
|
376
|
-
notionalUsd: Number(r.notionalUsd.toFixed(2)),
|
|
377
|
-
fundingRate: r.fundingRate,
|
|
378
|
-
annualPct: Number(r.annualPct.toFixed(2)),
|
|
379
|
-
predicted: {
|
|
380
|
-
hourly: Number(r.hourlyPayment.toFixed(6)),
|
|
381
|
-
daily: Number(r.dailyPayment.toFixed(4)),
|
|
382
|
-
},
|
|
383
|
-
actual24h: {
|
|
384
|
-
received: Number(r.actualReceived24h.toFixed(6)),
|
|
385
|
-
paid: Number(r.actualPaid24h.toFixed(6)),
|
|
386
|
-
net: Number(r.actualNet24h.toFixed(6)),
|
|
387
|
-
},
|
|
388
|
-
unrealizedPnl: r.unrealizedPnl,
|
|
389
|
-
})),
|
|
390
|
-
totals: {
|
|
391
|
-
predicted: {
|
|
392
|
-
hourly: Number(totalHourly.toFixed(6)),
|
|
393
|
-
daily: Number(totalDaily.toFixed(4)),
|
|
394
|
-
},
|
|
395
|
-
actual24h: {
|
|
396
|
-
net: Number(totalActualNet.toFixed(6)),
|
|
397
|
-
},
|
|
398
|
-
notionalUsd: Number(totalNotional.toFixed(2)),
|
|
399
|
-
},
|
|
400
|
-
errors: Object.keys(exchangeErrors).length > 0 ? exchangeErrors : undefined,
|
|
401
|
-
}));
|
|
402
|
-
}
|
|
403
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e.toUpperCase();
|
|
404
|
-
console.log(chalk.cyan.bold(" Position Funding Impact\n"));
|
|
405
|
-
const rows = results.map(r => {
|
|
406
|
-
const sideColor = r.side === "long" ? chalk.green : chalk.red;
|
|
407
|
-
const rateColor = r.fundingRate > 0 ? chalk.red : r.fundingRate < 0 ? chalk.green : chalk.white;
|
|
408
|
-
// actual net: positive = received, negative = paid
|
|
409
|
-
const actualColor = r.actualNet24h > 0 ? chalk.green : r.actualNet24h < 0 ? chalk.red : chalk.gray;
|
|
410
|
-
const actualStr = r.actualNet24h !== 0
|
|
411
|
-
? actualColor(`${r.actualNet24h > 0 ? "+" : ""}$${r.actualNet24h.toFixed(4)}`)
|
|
412
|
-
: chalk.gray("-");
|
|
413
|
-
return [
|
|
414
|
-
chalk.white.bold(exAbbr(r.exchange)),
|
|
415
|
-
chalk.white.bold(r.symbol),
|
|
416
|
-
sideColor(r.side.toUpperCase()),
|
|
417
|
-
`$${formatUsd(r.notionalUsd)}`,
|
|
418
|
-
rateColor(formatPercent(r.fundingRate)),
|
|
419
|
-
rateColor(`${r.annualPct.toFixed(1)}%`),
|
|
420
|
-
actualStr,
|
|
421
|
-
formatPnl(r.unrealizedPnl),
|
|
422
|
-
];
|
|
423
|
-
});
|
|
424
|
-
console.log(makeTable(["Ex", "Symbol", "Side", "Notional", "Rate(now)", "Annual(now)", "Actual 24h", "uPnL"], rows));
|
|
425
|
-
// Summary
|
|
426
|
-
const actualNetColor = totalActualNet >= 0 ? chalk.green : chalk.red;
|
|
427
|
-
const predictColor = totalDaily >= 0 ? chalk.red : chalk.green;
|
|
428
|
-
const predictSign = totalDaily >= 0 ? "-" : "+";
|
|
429
|
-
console.log();
|
|
430
|
-
console.log(` Total Notional: $${formatUsd(totalNotional)}`);
|
|
431
|
-
console.log(` Actual Net (24h): ${actualNetColor(`${totalActualNet >= 0 ? "+" : ""}$${totalActualNet.toFixed(4)}`)}`);
|
|
432
|
-
console.log(` Predicted (now rate): ${predictColor(`${predictSign}$${Math.abs(totalDaily).toFixed(4)}/d`)}`);
|
|
433
|
-
if (Object.keys(exchangeErrors).length > 0) {
|
|
434
|
-
console.log();
|
|
435
|
-
for (const [ex, err] of Object.entries(exchangeErrors)) {
|
|
436
|
-
console.log(chalk.yellow(` ${exAbbr(ex)}: ${err}`));
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
console.log(chalk.gray("\n Actual 24h: real funding received/paid in the last 24 hours."));
|
|
440
|
-
console.log(chalk.gray(" Predicted: based on current rate only (changes every hour)."));
|
|
441
|
-
console.log(chalk.gray(" + = received, - = paid\n"));
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
// ── Display helpers ──
|
|
446
|
-
function formatAvgRate(rate) {
|
|
447
|
-
if (rate == null)
|
|
448
|
-
return chalk.gray("-");
|
|
449
|
-
const annualPct = annualizeHourlyRate(rate);
|
|
450
|
-
const color = annualPct > 0 ? chalk.red : annualPct < 0 ? chalk.green : chalk.white;
|
|
451
|
-
return color(`${annualPct.toFixed(1)}%`);
|
|
452
|
-
}
|
|
453
|
-
/** Compute an "average spread" across rates that have avg24h data. */
|
|
454
|
-
function getAvgSpread(s, windowKey) {
|
|
455
|
-
const avgs = s.rates
|
|
456
|
-
.filter(r => r.historicalAvg?.[windowKey] != null)
|
|
457
|
-
.map(r => r.historicalAvg[windowKey]);
|
|
458
|
-
if (avgs.length < 2)
|
|
459
|
-
return chalk.gray("-");
|
|
460
|
-
const maxH = Math.max(...avgs);
|
|
461
|
-
const minH = Math.min(...avgs);
|
|
462
|
-
const spreadPct = annualizeHourlyRate(maxH - minH);
|
|
463
|
-
return `${spreadPct.toFixed(1)}%`;
|
|
464
|
-
}
|
|
465
|
-
function printSnapshotTable(snapshot) {
|
|
466
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
467
|
-
if (snapshot.symbols.length === 0) {
|
|
468
|
-
console.log(chalk.gray(" No funding rate data available.\n"));
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
const rows = snapshot.symbols.map(s => {
|
|
472
|
-
const pacRate = s.rates.find(r => r.exchange === "pacifica");
|
|
473
|
-
const hlRate = s.rates.find(r => r.exchange === "hyperliquid");
|
|
474
|
-
const ltRate = s.rates.find(r => r.exchange === "lighter");
|
|
475
|
-
const spreadColor = s.maxSpreadAnnual >= 30 ? chalk.green
|
|
476
|
-
: s.maxSpreadAnnual >= 10 ? chalk.yellow
|
|
477
|
-
: chalk.white;
|
|
478
|
-
// Use the highest-spread pair's historical averages for the avg columns
|
|
479
|
-
// Show the best exchange's avg for quick reference
|
|
480
|
-
const bestRate = hlRate ?? pacRate ?? ltRate;
|
|
481
|
-
const avg8h = bestRate?.historicalAvg?.avg8h;
|
|
482
|
-
const avg24h = bestRate?.historicalAvg?.avg24h;
|
|
483
|
-
const avg7d = bestRate?.historicalAvg?.avg7d;
|
|
484
|
-
return [
|
|
485
|
-
chalk.white.bold(s.symbol),
|
|
486
|
-
pacRate ? formatPercent(pacRate.fundingRate) : chalk.gray("-"),
|
|
487
|
-
hlRate ? formatPercent(hlRate.fundingRate) : chalk.gray("-"),
|
|
488
|
-
ltRate ? formatPercent(ltRate.fundingRate) : chalk.gray("-"),
|
|
489
|
-
spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`),
|
|
490
|
-
s.maxSpreadAnnual >= 5 ? `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}` : chalk.gray("-"),
|
|
491
|
-
formatAvgRate(avg8h),
|
|
492
|
-
formatAvgRate(avg24h),
|
|
493
|
-
formatAvgRate(avg7d),
|
|
494
|
-
];
|
|
495
|
-
});
|
|
496
|
-
console.log(makeTable(["Symbol", "Pacifica", "Hyperliquid", "Lighter", "Ann. Spread", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
|
|
497
|
-
console.log(chalk.gray(`\n ${snapshot.symbols.length} symbols compared across exchanges.`));
|
|
498
|
-
console.log(chalk.gray(` Rates: All exchanges per 1h. Spread is normalized.`));
|
|
499
|
-
console.log(chalk.gray(` Avg columns show best exchange's historical hourly rate annualized.\n`));
|
|
500
|
-
}
|
|
501
|
-
function printExchangeStatus(snapshot) {
|
|
502
|
-
const statuses = Object.entries(snapshot.exchangeStatus).map(([ex, status]) => {
|
|
503
|
-
const indicator = status === "ok" ? chalk.green("OK") : chalk.red("ERR");
|
|
504
|
-
return `${ex}: ${indicator}`;
|
|
505
|
-
});
|
|
506
|
-
console.log(chalk.gray(` Exchange status: ${statuses.join(" ")}\n`));
|
|
507
|
-
}
|
|
508
|
-
function printDetailedComparison(comparison) {
|
|
509
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
510
|
-
console.log(chalk.cyan.bold(` ${comparison.symbol} Funding Rate Comparison\n`));
|
|
511
|
-
console.log(` Mark Price: $${formatUsd(comparison.bestMarkPrice)}\n`);
|
|
512
|
-
for (const r of comparison.rates) {
|
|
513
|
-
const hourlyPct = (r.hourlyRate * 100).toFixed(6);
|
|
514
|
-
const annualPct = r.annualizedPct.toFixed(2);
|
|
515
|
-
const compHours = getExchangeCompoundingHours(r.exchange);
|
|
516
|
-
const compoundedReturn = getCompoundedAnnualReturn(r.hourlyRate, compHours);
|
|
517
|
-
const compoundedPct = (compoundedReturn * 100).toFixed(2);
|
|
518
|
-
const color = r.fundingRate > 0 ? chalk.red : r.fundingRate < 0 ? chalk.green : chalk.white;
|
|
519
|
-
console.log(` ${chalk.white.bold(exAbbr(r.exchange).padEnd(4))} ` +
|
|
520
|
-
`Raw: ${color(formatPercent(r.fundingRate).padEnd(14))} ` +
|
|
521
|
-
`Hourly: ${hourlyPct}% ` +
|
|
522
|
-
`Annual: ${annualPct}% ` +
|
|
523
|
-
`APY(compounded): ${compoundedPct}%`);
|
|
524
|
-
}
|
|
525
|
-
// Show historical averages if available
|
|
526
|
-
const hasHistorical = comparison.rates.some(r => r.historicalAvg != null);
|
|
527
|
-
if (hasHistorical) {
|
|
528
|
-
console.log(chalk.cyan("\n Historical Averages (annualized):"));
|
|
529
|
-
for (const r of comparison.rates) {
|
|
530
|
-
if (!r.historicalAvg)
|
|
531
|
-
continue;
|
|
532
|
-
const avg8h = r.historicalAvg.avg8h != null ? `${annualizeHourlyRate(r.historicalAvg.avg8h).toFixed(1)}%` : "-";
|
|
533
|
-
const avg24h = r.historicalAvg.avg24h != null ? `${annualizeHourlyRate(r.historicalAvg.avg24h).toFixed(1)}%` : "-";
|
|
534
|
-
const avg7d = r.historicalAvg.avg7d != null ? `${annualizeHourlyRate(r.historicalAvg.avg7d).toFixed(1)}%` : "-";
|
|
535
|
-
console.log(` ${chalk.white.bold(exAbbr(r.exchange).padEnd(4))} ` +
|
|
536
|
-
`8h: ${avg8h.padEnd(10)} 24h: ${avg24h.padEnd(10)} 7d: ${avg7d}`);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
console.log();
|
|
540
|
-
const spreadColor = comparison.maxSpreadAnnual >= 30 ? chalk.green.bold
|
|
541
|
-
: comparison.maxSpreadAnnual >= 10 ? chalk.yellow
|
|
542
|
-
: chalk.white;
|
|
543
|
-
console.log(` Max Spread: ${spreadColor(`${comparison.maxSpreadAnnual.toFixed(1)}%`)} annual`);
|
|
544
|
-
console.log(` Direction: Long ${exAbbr(comparison.longExchange)} / Short ${exAbbr(comparison.shortExchange)}`);
|
|
545
|
-
console.log(` Est. Income: $${comparison.estHourlyIncomeUsd.toFixed(4)}/hr per $1K notional`);
|
|
546
|
-
// Show income at different position sizes
|
|
547
|
-
const sizes = [1000, 5000, 10000, 50000];
|
|
548
|
-
console.log(chalk.gray("\n Estimated daily income by position size:"));
|
|
549
|
-
for (const size of sizes) {
|
|
550
|
-
const daily = (comparison.estHourlyIncomeUsd / 1000) * size * 24;
|
|
551
|
-
console.log(chalk.gray(` $${formatUsd(size)} notional -> $${daily.toFixed(2)}/day`));
|
|
552
|
-
}
|
|
553
|
-
console.log(chalk.gray("\n * Rates shown are current predictions. Actual settled rates may differ.\n"));
|
|
554
|
-
}
|
package/dist/commands/gap.d.ts
DELETED