perp-cli 0.11.0 → 0.12.1
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/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 +18 -14
- 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 +756 -28
- 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 +84 -34
- package/dist/exchanges/lighter-spot.js +14 -3
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +199 -27
- 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/perp-guardrail.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +268 -360
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +85 -83
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +67 -3
- package/dist/settings.js +1 -2
- package/dist/signer/ows-evm.d.ts +14 -0
- package/dist/signer/ows-evm.js +28 -9
- 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 +2 -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/ows.d.ts +0 -2
- package/dist/commands/ows.js +0 -887
- 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
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { printJson, jsonOk, makeTable, formatUsd, formatPnl, withJsonErrors } from "../utils.js";
|
|
3
|
-
import { readExecutionLog, getExecutionStats } from "../execution-log.js";
|
|
4
|
-
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
5
|
-
function parseSince(since) {
|
|
6
|
-
if (!since)
|
|
7
|
-
return undefined;
|
|
8
|
-
const match = since.match(/^(\d+)(h|d|w)$/);
|
|
9
|
-
if (match) {
|
|
10
|
-
const [, num, unit] = match;
|
|
11
|
-
const ms = { h: 3600000, d: 86400000, w: 604800000 }[unit] ?? 86400000;
|
|
12
|
-
return new Date(Date.now() - parseInt(num) * ms).toISOString();
|
|
13
|
-
}
|
|
14
|
-
return since;
|
|
15
|
-
}
|
|
16
|
-
export function registerAnalyticsCommands(program, getAdapterForExchange, isJson) {
|
|
17
|
-
const analytics = program.command("analytics").description("Trading performance analytics");
|
|
18
|
-
// ── analytics summary ──
|
|
19
|
-
analytics
|
|
20
|
-
.command("summary")
|
|
21
|
-
.description("Execution log summary statistics")
|
|
22
|
-
.option("--since <period>", "Period: 24h, 7d, 30d, or ISO date")
|
|
23
|
-
.action(async (opts) => {
|
|
24
|
-
await withJsonErrors(isJson(), async () => {
|
|
25
|
-
const since = parseSince(opts.since);
|
|
26
|
-
const stats = getExecutionStats(since);
|
|
27
|
-
const records = readExecutionLog({ since });
|
|
28
|
-
// Calculate volume
|
|
29
|
-
let totalVolume = 0;
|
|
30
|
-
for (const r of records) {
|
|
31
|
-
if (r.notional)
|
|
32
|
-
totalVolume += r.notional;
|
|
33
|
-
else if (r.price && r.size)
|
|
34
|
-
totalVolume += Number(r.price) * Number(r.size);
|
|
35
|
-
}
|
|
36
|
-
const result = { ...stats, totalVolume, period: opts.since ?? "all time" };
|
|
37
|
-
if (isJson())
|
|
38
|
-
return printJson(jsonOk(result));
|
|
39
|
-
console.log(chalk.cyan.bold(`\n Trading Summary ${opts.since ? `(${opts.since})` : "(all time)"}\n`));
|
|
40
|
-
console.log(` Total Trades: ${stats.totalTrades}`);
|
|
41
|
-
console.log(` Success Rate: ${stats.successRate.toFixed(1)}%`);
|
|
42
|
-
console.log(` Total Volume: $${formatUsd(totalVolume)}`);
|
|
43
|
-
if (Object.keys(stats.byExchange).length > 0) {
|
|
44
|
-
console.log(chalk.white.bold("\n By Exchange:"));
|
|
45
|
-
for (const [ex, count] of Object.entries(stats.byExchange)) {
|
|
46
|
-
console.log(` ${ex.padEnd(14)} ${count} trades`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (Object.keys(stats.byType).length > 0) {
|
|
50
|
-
console.log(chalk.white.bold("\n By Type:"));
|
|
51
|
-
for (const [type, count] of Object.entries(stats.byType)) {
|
|
52
|
-
console.log(` ${type.replace(/_/g, " ").padEnd(14)} ${count}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (stats.recentErrors.length > 0) {
|
|
56
|
-
console.log(chalk.red.bold("\n Recent Errors:"));
|
|
57
|
-
for (const err of stats.recentErrors) {
|
|
58
|
-
console.log(` ${chalk.red(err)}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
console.log();
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
// ── analytics pnl ──
|
|
65
|
-
analytics
|
|
66
|
-
.command("pnl")
|
|
67
|
-
.description("Realized P&L from exchange trade history")
|
|
68
|
-
.option("-e, --exchange <exchanges>", "Comma-separated exchanges")
|
|
69
|
-
.option("--since <period>", "Period: 24h, 7d, 30d")
|
|
70
|
-
.option("-n, --limit <n>", "Trade history limit per exchange", "100")
|
|
71
|
-
.action(async (opts) => {
|
|
72
|
-
await withJsonErrors(isJson(), async () => {
|
|
73
|
-
const exchanges = opts.exchange
|
|
74
|
-
? opts.exchange.split(",").map(e => e.trim())
|
|
75
|
-
: [...EXCHANGES];
|
|
76
|
-
const limit = parseInt(opts.limit);
|
|
77
|
-
const sinceMs = opts.since ? new Date(parseSince(opts.since)).getTime() : 0;
|
|
78
|
-
const allTrades = [];
|
|
79
|
-
await Promise.allSettled(exchanges.map(async (ex) => {
|
|
80
|
-
try {
|
|
81
|
-
const adapter = await getAdapterForExchange(ex);
|
|
82
|
-
const trades = await adapter.getTradeHistory(limit);
|
|
83
|
-
for (const t of trades) {
|
|
84
|
-
if (sinceMs && t.time < sinceMs)
|
|
85
|
-
continue;
|
|
86
|
-
allTrades.push({
|
|
87
|
-
exchange: ex,
|
|
88
|
-
symbol: t.symbol,
|
|
89
|
-
side: t.side,
|
|
90
|
-
price: Number(t.price),
|
|
91
|
-
size: Number(t.size),
|
|
92
|
-
fee: Number(t.fee),
|
|
93
|
-
time: t.time,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch { /* skip unavailable exchanges */ }
|
|
98
|
-
}));
|
|
99
|
-
// Aggregate by symbol
|
|
100
|
-
const bySymbol = new Map();
|
|
101
|
-
const byExchange = new Map();
|
|
102
|
-
let totalVolume = 0;
|
|
103
|
-
let totalFees = 0;
|
|
104
|
-
for (const t of allTrades) {
|
|
105
|
-
const notional = t.price * t.size;
|
|
106
|
-
totalVolume += notional;
|
|
107
|
-
totalFees += t.fee;
|
|
108
|
-
const sym = bySymbol.get(t.symbol) ?? { volume: 0, fees: 0, trades: 0 };
|
|
109
|
-
sym.volume += notional;
|
|
110
|
-
sym.fees += t.fee;
|
|
111
|
-
sym.trades++;
|
|
112
|
-
bySymbol.set(t.symbol, sym);
|
|
113
|
-
const ex = byExchange.get(t.exchange) ?? { volume: 0, fees: 0, trades: 0 };
|
|
114
|
-
ex.volume += notional;
|
|
115
|
-
ex.fees += t.fee;
|
|
116
|
-
ex.trades++;
|
|
117
|
-
byExchange.set(t.exchange, ex);
|
|
118
|
-
}
|
|
119
|
-
const result = {
|
|
120
|
-
totalTrades: allTrades.length,
|
|
121
|
-
totalVolume,
|
|
122
|
-
totalFees,
|
|
123
|
-
netAfterFees: -totalFees, // realized PnL would need entry/exit matching; fees are definite cost
|
|
124
|
-
bySymbol: Object.fromEntries(bySymbol),
|
|
125
|
-
byExchange: Object.fromEntries(byExchange),
|
|
126
|
-
};
|
|
127
|
-
if (isJson())
|
|
128
|
-
return printJson(jsonOk(result));
|
|
129
|
-
console.log(chalk.cyan.bold(`\n Realized Trading P&L ${opts.since ? `(${opts.since})` : ""}\n`));
|
|
130
|
-
console.log(` Total Trades: ${allTrades.length}`);
|
|
131
|
-
console.log(` Total Volume: $${formatUsd(totalVolume)}`);
|
|
132
|
-
console.log(` Total Fees: ${chalk.red(`-$${formatUsd(totalFees)}`)}`);
|
|
133
|
-
if (bySymbol.size > 0) {
|
|
134
|
-
console.log(chalk.white.bold("\n By Symbol:"));
|
|
135
|
-
const symRows = [...bySymbol.entries()]
|
|
136
|
-
.sort((a, b) => b[1].volume - a[1].volume)
|
|
137
|
-
.map(([sym, d]) => [
|
|
138
|
-
chalk.white.bold(sym),
|
|
139
|
-
String(d.trades),
|
|
140
|
-
`$${formatUsd(d.volume)}`,
|
|
141
|
-
chalk.red(`-$${formatUsd(d.fees)}`),
|
|
142
|
-
]);
|
|
143
|
-
console.log(makeTable(["Symbol", "Trades", "Volume", "Fees"], symRows));
|
|
144
|
-
}
|
|
145
|
-
if (byExchange.size > 0) {
|
|
146
|
-
console.log(chalk.white.bold(" By Exchange:"));
|
|
147
|
-
const exRows = [...byExchange.entries()].map(([ex, d]) => [
|
|
148
|
-
chalk.white.bold(ex),
|
|
149
|
-
String(d.trades),
|
|
150
|
-
`$${formatUsd(d.volume)}`,
|
|
151
|
-
chalk.red(`-$${formatUsd(d.fees)}`),
|
|
152
|
-
]);
|
|
153
|
-
console.log(makeTable(["Exchange", "Trades", "Volume", "Fees"], exRows));
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
// ── analytics funding ──
|
|
158
|
-
analytics
|
|
159
|
-
.command("funding")
|
|
160
|
-
.description("Funding payment history across exchanges")
|
|
161
|
-
.option("-e, --exchange <exchanges>", "Comma-separated exchanges")
|
|
162
|
-
.option("-s, --symbol <symbol>", "Filter by symbol")
|
|
163
|
-
.option("-n, --limit <n>", "Funding history limit per exchange", "50")
|
|
164
|
-
.action(async (opts) => {
|
|
165
|
-
await withJsonErrors(isJson(), async () => {
|
|
166
|
-
const exchanges = opts.exchange
|
|
167
|
-
? opts.exchange.split(",").map(e => e.trim())
|
|
168
|
-
: [...EXCHANGES];
|
|
169
|
-
const allFunding = [];
|
|
170
|
-
await Promise.allSettled(exchanges.map(async (ex) => {
|
|
171
|
-
try {
|
|
172
|
-
const adapter = await getAdapterForExchange(ex);
|
|
173
|
-
const payments = await adapter.getFundingPayments(parseInt(opts.limit));
|
|
174
|
-
for (const p of payments) {
|
|
175
|
-
if (opts.symbol && !p.symbol.toUpperCase().includes(opts.symbol.toUpperCase()))
|
|
176
|
-
continue;
|
|
177
|
-
allFunding.push({
|
|
178
|
-
exchange: ex,
|
|
179
|
-
symbol: p.symbol,
|
|
180
|
-
payment: Number(p.payment),
|
|
181
|
-
time: p.time,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
catch { /* skip */ }
|
|
186
|
-
}));
|
|
187
|
-
// Aggregate
|
|
188
|
-
const bySymbol = new Map();
|
|
189
|
-
const byExchange = new Map();
|
|
190
|
-
let totalFunding = 0;
|
|
191
|
-
for (const f of allFunding) {
|
|
192
|
-
totalFunding += f.payment;
|
|
193
|
-
bySymbol.set(f.symbol, (bySymbol.get(f.symbol) ?? 0) + f.payment);
|
|
194
|
-
byExchange.set(f.exchange, (byExchange.get(f.exchange) ?? 0) + f.payment);
|
|
195
|
-
}
|
|
196
|
-
const result = {
|
|
197
|
-
totalPayments: allFunding.length,
|
|
198
|
-
totalFunding,
|
|
199
|
-
bySymbol: Object.fromEntries(bySymbol),
|
|
200
|
-
byExchange: Object.fromEntries(byExchange),
|
|
201
|
-
};
|
|
202
|
-
if (isJson())
|
|
203
|
-
return printJson(jsonOk(result));
|
|
204
|
-
console.log(chalk.cyan.bold("\n Funding Payment Summary\n"));
|
|
205
|
-
console.log(` Total Payments: ${allFunding.length}`);
|
|
206
|
-
console.log(` Net Funding: ${formatPnl(totalFunding)}`);
|
|
207
|
-
if (bySymbol.size > 0) {
|
|
208
|
-
console.log(chalk.white.bold("\n By Symbol:"));
|
|
209
|
-
const rows = [...bySymbol.entries()]
|
|
210
|
-
.sort((a, b) => b[1] - a[1])
|
|
211
|
-
.map(([sym, amt]) => [chalk.white.bold(sym), formatPnl(amt)]);
|
|
212
|
-
console.log(makeTable(["Symbol", "Net Funding"], rows));
|
|
213
|
-
}
|
|
214
|
-
if (byExchange.size > 0) {
|
|
215
|
-
console.log(chalk.white.bold(" By Exchange:"));
|
|
216
|
-
const rows = [...byExchange.entries()]
|
|
217
|
-
.map(([ex, amt]) => [chalk.white.bold(ex), formatPnl(amt)]);
|
|
218
|
-
console.log(makeTable(["Exchange", "Net Funding"], rows));
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
// ── analytics report ──
|
|
223
|
-
analytics
|
|
224
|
-
.command("report")
|
|
225
|
-
.description("Full performance report (summary + pnl + funding)")
|
|
226
|
-
.option("-e, --exchange <exchanges>", "Comma-separated exchanges")
|
|
227
|
-
.option("--since <period>", "Period: 24h, 7d, 30d")
|
|
228
|
-
.action(async (opts) => {
|
|
229
|
-
await withJsonErrors(isJson(), async () => {
|
|
230
|
-
const since = parseSince(opts.since);
|
|
231
|
-
const stats = getExecutionStats(since);
|
|
232
|
-
const records = readExecutionLog({ since });
|
|
233
|
-
const exchanges = opts.exchange
|
|
234
|
-
? opts.exchange.split(",").map(e => e.trim())
|
|
235
|
-
: [...EXCHANGES];
|
|
236
|
-
// Volume from log
|
|
237
|
-
let logVolume = 0;
|
|
238
|
-
for (const r of records) {
|
|
239
|
-
if (r.notional)
|
|
240
|
-
logVolume += r.notional;
|
|
241
|
-
}
|
|
242
|
-
// Fetch live data from exchanges
|
|
243
|
-
let totalFunding = 0;
|
|
244
|
-
let totalFees = 0;
|
|
245
|
-
let totalEquity = 0;
|
|
246
|
-
let totalUPnl = 0;
|
|
247
|
-
const symbolSet = new Set();
|
|
248
|
-
await Promise.allSettled(exchanges.map(async (ex) => {
|
|
249
|
-
try {
|
|
250
|
-
const adapter = await getAdapterForExchange(ex);
|
|
251
|
-
const [balance, trades, funding] = await Promise.all([
|
|
252
|
-
adapter.getBalance(),
|
|
253
|
-
adapter.getTradeHistory(50).catch(() => []),
|
|
254
|
-
adapter.getFundingPayments(50).catch(() => []),
|
|
255
|
-
]);
|
|
256
|
-
totalEquity += Number(balance.equity);
|
|
257
|
-
totalUPnl += Number(balance.unrealizedPnl);
|
|
258
|
-
for (const t of trades) {
|
|
259
|
-
totalFees += Number(t.fee);
|
|
260
|
-
symbolSet.add(t.symbol);
|
|
261
|
-
}
|
|
262
|
-
for (const f of funding)
|
|
263
|
-
totalFunding += Number(f.payment);
|
|
264
|
-
}
|
|
265
|
-
catch { /* skip */ }
|
|
266
|
-
}));
|
|
267
|
-
const report = {
|
|
268
|
-
period: opts.since ?? "all time",
|
|
269
|
-
execution: {
|
|
270
|
-
totalTrades: stats.totalTrades,
|
|
271
|
-
successRate: stats.successRate,
|
|
272
|
-
volume: logVolume,
|
|
273
|
-
byExchange: stats.byExchange,
|
|
274
|
-
byType: stats.byType,
|
|
275
|
-
},
|
|
276
|
-
portfolio: {
|
|
277
|
-
totalEquity,
|
|
278
|
-
unrealizedPnl: totalUPnl,
|
|
279
|
-
},
|
|
280
|
-
costs: {
|
|
281
|
-
totalFees,
|
|
282
|
-
totalFunding,
|
|
283
|
-
netFunding: totalFunding,
|
|
284
|
-
},
|
|
285
|
-
insights: {
|
|
286
|
-
uniqueSymbols: symbolSet.size,
|
|
287
|
-
mostActiveExchange: Object.entries(stats.byExchange).sort((a, b) => b[1] - a[1])[0]?.[0] ?? "none",
|
|
288
|
-
avgTradesPerDay: stats.totalTrades > 0
|
|
289
|
-
? (stats.totalTrades / Math.max(1, (Date.now() - new Date(records[records.length - 1]?.timestamp ?? Date.now()).getTime()) / 86400000)).toFixed(1)
|
|
290
|
-
: "0",
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
if (isJson())
|
|
294
|
-
return printJson(jsonOk(report));
|
|
295
|
-
console.log(chalk.cyan.bold(`\n Performance Report ${opts.since ? `(${opts.since})` : ""}\n`));
|
|
296
|
-
console.log(chalk.white.bold(" Execution"));
|
|
297
|
-
console.log(` Trades: ${stats.totalTrades} (${stats.successRate.toFixed(0)}% success)`);
|
|
298
|
-
console.log(` Volume: $${formatUsd(logVolume)}`);
|
|
299
|
-
console.log(` Symbols Traded: ${symbolSet.size}`);
|
|
300
|
-
console.log(` Avg/Day: ${report.insights.avgTradesPerDay}`);
|
|
301
|
-
console.log(chalk.white.bold("\n Portfolio"));
|
|
302
|
-
console.log(` Total Equity: $${formatUsd(totalEquity)}`);
|
|
303
|
-
console.log(` Unrealized PnL: ${formatPnl(totalUPnl)}`);
|
|
304
|
-
console.log(chalk.white.bold("\n Costs & Income"));
|
|
305
|
-
console.log(` Trading Fees: ${chalk.red(`-$${formatUsd(totalFees)}`)}`);
|
|
306
|
-
console.log(` Funding Income: ${formatPnl(totalFunding)}`);
|
|
307
|
-
console.log(` Net: ${formatPnl(totalFunding - totalFees)}`);
|
|
308
|
-
console.log();
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
|
-
export declare function registerDashboardCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getHLAdapterForDex?: (dex: string) => Promise<ExchangeAdapter>): void;
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { startDashboard } from "../dashboard/index.js";
|
|
3
|
-
const KNOWN_HIP3_DEXES = ["xyz", "flx", "hyna", "km", "cash", "vntl"];
|
|
4
|
-
const HL_INFO_URL = "https://api.hyperliquid.xyz/info";
|
|
5
|
-
/** Lightweight check: query HL info API directly for dex positions (no SDK/WebSocket needed) */
|
|
6
|
-
async function checkDexPositions(address, dex) {
|
|
7
|
-
try {
|
|
8
|
-
const res = await fetch(HL_INFO_URL, {
|
|
9
|
-
method: "POST",
|
|
10
|
-
headers: { "Content-Type": "application/json" },
|
|
11
|
-
body: JSON.stringify({ type: "clearinghouseState", user: address, dex }),
|
|
12
|
-
});
|
|
13
|
-
const state = await res.json();
|
|
14
|
-
const positions = (state?.assetPositions ?? []);
|
|
15
|
-
return positions.filter(p => {
|
|
16
|
-
const pos = (p.position ?? p);
|
|
17
|
-
return Number(pos.szi ?? 0) !== 0;
|
|
18
|
-
}).length;
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return 0;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
/** Scan known HIP-3 dexes for active positions, then create adapters only for active ones */
|
|
25
|
-
async function autoDetectHip3Dexes(address, getHLAdapterForDex, verbose) {
|
|
26
|
-
// Phase 1: lightweight parallel scan via raw API (no WebSocket connections)
|
|
27
|
-
const checks = await Promise.all(KNOWN_HIP3_DEXES.map(async (dex) => ({
|
|
28
|
-
dex,
|
|
29
|
-
count: await checkDexPositions(address, dex),
|
|
30
|
-
})));
|
|
31
|
-
const activeDexes = checks.filter(c => c.count > 0);
|
|
32
|
-
// Phase 2: create full adapters only for dexes with positions
|
|
33
|
-
const results = [];
|
|
34
|
-
for (const { dex, count } of activeDexes) {
|
|
35
|
-
try {
|
|
36
|
-
const adapter = await getHLAdapterForDex(dex);
|
|
37
|
-
results.push({ name: `hl:${dex}`, adapter });
|
|
38
|
-
if (verbose)
|
|
39
|
-
console.log(chalk.green(` ✓ hl:${dex} (HIP-3, ${count} position${count > 1 ? "s" : ""})`));
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// skip
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
|
-
export function registerDashboardCommands(program, getAdapterForExchange, isJson, getHLAdapterForDex) {
|
|
48
|
-
program
|
|
49
|
-
.command("dashboard")
|
|
50
|
-
.description("Launch live web dashboard for real-time portfolio monitoring")
|
|
51
|
-
.option("-p, --port <port>", "Port to serve dashboard on (auto-finds free port)", "3456")
|
|
52
|
-
.option("--interval <ms>", "Poll interval in milliseconds", "5000")
|
|
53
|
-
.option("--exchanges <list>", "Comma-separated exchanges to monitor", "pacifica,hyperliquid,lighter,aster")
|
|
54
|
-
.option("--dex <list>", "Comma-separated HIP-3 dexes to monitor (e.g. xyz,flx,hyna)")
|
|
55
|
-
.option("--no-auto-dex", "Disable auto-detection of HIP-3 dexes with active positions")
|
|
56
|
-
.action(async (opts) => {
|
|
57
|
-
const exchangeNames = opts.exchanges.split(",").map((s) => s.trim()).filter(Boolean);
|
|
58
|
-
const dexNames = opts.dex ? opts.dex.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
59
|
-
const autoDex = opts.autoDex !== false;
|
|
60
|
-
const port = parseInt(opts.port, 10);
|
|
61
|
-
const interval = parseInt(opts.interval, 10);
|
|
62
|
-
if (isJson()) {
|
|
63
|
-
const { printJson, jsonOk } = await import("../utils.js");
|
|
64
|
-
const exchanges = [];
|
|
65
|
-
for (const name of exchangeNames) {
|
|
66
|
-
try {
|
|
67
|
-
const adapter = await getAdapterForExchange(name);
|
|
68
|
-
exchanges.push({ name, adapter });
|
|
69
|
-
}
|
|
70
|
-
catch { /* skip */ }
|
|
71
|
-
}
|
|
72
|
-
if (getHLAdapterForDex) {
|
|
73
|
-
for (const dex of dexNames) {
|
|
74
|
-
try {
|
|
75
|
-
const adapter = await getHLAdapterForDex(dex);
|
|
76
|
-
exchanges.push({ name: `hl:${dex}`, adapter });
|
|
77
|
-
}
|
|
78
|
-
catch { /* skip */ }
|
|
79
|
-
}
|
|
80
|
-
if (autoDex && !dexNames.length && exchangeNames.includes("hyperliquid")) {
|
|
81
|
-
const hlEx = exchanges.find(e => e.name === "hyperliquid");
|
|
82
|
-
const hlAddr = hlEx ? hlEx.adapter.address ?? "" : "";
|
|
83
|
-
if (hlAddr) {
|
|
84
|
-
const detected = await autoDetectHip3Dexes(hlAddr, getHLAdapterForDex, false);
|
|
85
|
-
exchanges.push(...detected);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (!exchanges.length) {
|
|
90
|
-
const { jsonError } = await import("../utils.js");
|
|
91
|
-
console.log(JSON.stringify(jsonError("NO_EXCHANGES", "No exchange adapters could be initialized. Check your keys.")));
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
const dashboard = await startDashboard(exchanges, { port, pollInterval: interval });
|
|
95
|
-
printJson(jsonOk({ url: `http://localhost:${dashboard.port}`, port: dashboard.port, exchanges: exchanges.map((e) => e.name) }));
|
|
96
|
-
await new Promise(() => { });
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
console.log(chalk.cyan.bold("\n perp-cli Live Dashboard\n"));
|
|
100
|
-
console.log(chalk.gray(` Initializing exchanges: ${exchangeNames.join(", ")}${dexNames.length ? ` + HIP-3: ${dexNames.join(", ")}` : ""}...\n`));
|
|
101
|
-
const exchanges = [];
|
|
102
|
-
for (const name of exchangeNames) {
|
|
103
|
-
try {
|
|
104
|
-
const adapter = await getAdapterForExchange(name);
|
|
105
|
-
exchanges.push({ name, adapter });
|
|
106
|
-
console.log(chalk.green(` ✓ ${name}`));
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
110
|
-
console.log(chalk.yellow(` ✗ ${name}: ${msg.slice(0, 80)}`));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// Initialize HIP-3 dex adapters (explicit --dex)
|
|
114
|
-
if (getHLAdapterForDex) {
|
|
115
|
-
for (const dex of dexNames) {
|
|
116
|
-
try {
|
|
117
|
-
const adapter = await getHLAdapterForDex(dex);
|
|
118
|
-
exchanges.push({ name: `hl:${dex}`, adapter });
|
|
119
|
-
console.log(chalk.green(` ✓ hl:${dex} (HIP-3)`));
|
|
120
|
-
}
|
|
121
|
-
catch (err) {
|
|
122
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
123
|
-
console.log(chalk.yellow(` ✗ hl:${dex}: ${msg.slice(0, 80)}`));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
// Auto-detect HIP-3 dexes with active positions (when --dex not specified)
|
|
127
|
-
if (autoDex && !dexNames.length && exchangeNames.includes("hyperliquid")) {
|
|
128
|
-
const hlEx = exchanges.find(e => e.name === "hyperliquid");
|
|
129
|
-
const hlAddr = hlEx ? hlEx.adapter.address ?? "" : "";
|
|
130
|
-
if (hlAddr) {
|
|
131
|
-
console.log(chalk.gray(" Scanning HIP-3 dexes for active positions..."));
|
|
132
|
-
const detected = await autoDetectHip3Dexes(hlAddr, getHLAdapterForDex, true);
|
|
133
|
-
exchanges.push(...detected);
|
|
134
|
-
if (!detected.length)
|
|
135
|
-
console.log(chalk.gray(" No active HIP-3 dex positions found"));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (!exchanges.length) {
|
|
140
|
-
console.error(chalk.red("\n No exchanges available. Check your private keys in .env\n"));
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
console.log(chalk.gray(`\n Starting server on port ${port}...`));
|
|
144
|
-
const dashboard = await startDashboard(exchanges, { port, pollInterval: interval });
|
|
145
|
-
console.log(chalk.cyan.bold(`\n Dashboard running at: `) + chalk.white.bold(`http://localhost:${dashboard.port}`));
|
|
146
|
-
console.log(chalk.gray(` Monitoring: ${exchanges.map((e) => e.name).join(", ")}`));
|
|
147
|
-
console.log(chalk.gray(` Poll interval: ${interval}ms`));
|
|
148
|
-
console.log(chalk.gray(` Press Ctrl+C to stop\n`));
|
|
149
|
-
// Open browser automatically (best-effort)
|
|
150
|
-
try {
|
|
151
|
-
const { exec } = await import("child_process");
|
|
152
|
-
const url = `http://localhost:${dashboard.port}`;
|
|
153
|
-
if (process.platform === "darwin")
|
|
154
|
-
exec(`open ${url}`);
|
|
155
|
-
else if (process.platform === "linux")
|
|
156
|
-
exec(`xdg-open ${url}`);
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
// ignore — user can open manually
|
|
160
|
-
}
|
|
161
|
-
// Keep process running
|
|
162
|
-
const ac = new AbortController();
|
|
163
|
-
process.on("SIGINT", () => {
|
|
164
|
-
console.log(chalk.gray("\n Shutting down dashboard..."));
|
|
165
|
-
dashboard.close();
|
|
166
|
-
ac.abort();
|
|
167
|
-
process.exit(0);
|
|
168
|
-
});
|
|
169
|
-
process.on("SIGTERM", () => {
|
|
170
|
-
dashboard.close();
|
|
171
|
-
ac.abort();
|
|
172
|
-
process.exit(0);
|
|
173
|
-
});
|
|
174
|
-
await new Promise(() => { });
|
|
175
|
-
});
|
|
176
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import type { ExchangeAdapter } from "../exchanges/interface.js";
|
|
3
|
-
import type { Network } from "../pacifica/index.js";
|
|
4
|
-
export declare function registerDepositCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network): void;
|