perp-cli 0.15.0 → 0.16.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.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +129 -535
- package/README.zh.md +243 -0
- package/dist/api/public/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +2 -2
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- package/dist/bot/quote-rules.js +1 -1
- package/dist/bot/strategies/funding-arb-strategy.js +10 -3
- package/dist/bot/strategies/funding-arb-utils.d.ts +14 -2
- package/dist/bot/strategies/funding-arb-utils.js +48 -15
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +4 -2
- package/dist/bot/strategies/spot-perp-executor.js +2 -2
- package/dist/cli-program.js +1 -1
- package/dist/commands/account.js +168 -24
- package/dist/commands/arb/index.js +19 -77
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +155 -81
- package/dist/commands/arb.d.ts +8 -0
- package/dist/commands/arb.js +49 -23
- package/dist/commands/backtest.js +23 -21
- package/dist/commands/market.d.ts +48 -0
- package/dist/commands/market.js +241 -78
- package/dist/commands/options.d.ts +5 -0
- package/dist/commands/options.js +20 -3
- package/dist/commands/pacifica-earn.js +8 -21
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +51 -8
- package/dist/commands/trade.js +4 -4
- package/dist/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +2 -2
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +10 -0
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +10 -6
- package/dist/exchanges/arcus/adapter.js +33 -6
- package/dist/exchanges/arcus/public.js +3 -2
- package/dist/exchanges/aster.d.ts +27 -7
- package/dist/exchanges/aster.js +124 -42
- package/dist/exchanges/canonical-symbols.d.ts +7 -0
- package/dist/exchanges/canonical-symbols.js +20 -0
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/options.d.ts +5 -0
- package/dist/exchanges/derive/options.js +20 -1
- package/dist/exchanges/extended/adapter.d.ts +16 -2
- package/dist/exchanges/extended/adapter.js +96 -34
- package/dist/exchanges/extended/public.d.ts +3 -0
- package/dist/exchanges/extended/public.js +9 -2
- package/dist/exchanges/grvt/adapter.d.ts +16 -2
- package/dist/exchanges/grvt/adapter.js +98 -21
- package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +8 -0
- package/dist/exchanges/hyperliquid-spot.js +20 -12
- package/dist/exchanges/hyperliquid.d.ts +180 -13
- package/dist/exchanges/hyperliquid.js +487 -185
- package/dist/exchanges/interface.d.ts +64 -8
- package/dist/exchanges/interface.js +31 -0
- package/dist/exchanges/lighter-public.d.ts +66 -11
- package/dist/exchanges/lighter-public.js +249 -81
- package/dist/exchanges/lighter-spot.d.ts +7 -1
- package/dist/exchanges/lighter-spot.js +99 -90
- package/dist/exchanges/lighter.d.ts +70 -11
- package/dist/exchanges/lighter.js +285 -92
- package/dist/exchanges/ondo/adapter.d.ts +34 -8
- package/dist/exchanges/ondo/adapter.js +73 -16
- package/dist/exchanges/ondo/public.d.ts +27 -2
- package/dist/exchanges/ondo/public.js +63 -31
- package/dist/exchanges/outcome-interface.d.ts +4 -0
- package/dist/exchanges/pacifica-spot.d.ts +6 -0
- package/dist/exchanges/pacifica-spot.js +23 -15
- package/dist/exchanges/pacifica-vaults.d.ts +9 -0
- package/dist/exchanges/pacifica-vaults.js +14 -1
- package/dist/exchanges/pacifica.d.ts +77 -6
- package/dist/exchanges/pacifica.js +321 -130
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +52 -9
- package/dist/exchanges/qfex/adapter.js +74 -9
- package/dist/exchanges/qfex/public.d.ts +62 -9
- package/dist/exchanges/qfex/public.js +128 -22
- package/dist/exchanges/qfex/ws-transport.d.ts +1 -1
- package/dist/exchanges/risex/adapter.d.ts +19 -7
- package/dist/exchanges/risex/adapter.js +58 -12
- package/dist/exchanges/spot-factory.js +1 -1
- package/dist/exchanges/txflow/adapter.d.ts +14 -7
- package/dist/exchanges/txflow/adapter.js +51 -19
- package/dist/exchanges/txflow/client.js +3 -1
- package/dist/exchanges/txflow/public.d.ts +12 -1
- package/dist/exchanges/txflow/public.js +82 -11
- package/dist/exchanges/unknown-symbol.d.ts +6 -1
- package/dist/exchanges/unknown-symbol.js +7 -2
- package/dist/exchanges/variational/adapter.d.ts +2 -6
- package/dist/exchanges/venue-types.d.ts +46 -3
- package/dist/exchanges/venue-types.js +80 -9
- package/dist/exchanges/venue.d.ts +99 -3
- package/dist/exchanges/venue.js +72 -0
- package/dist/exchanges/venues/hyperliquid.js +9 -0
- package/dist/exchanges/venues/lighter-rh.js +13 -3
- package/dist/exchanges/venues/lighter.js +13 -3
- package/dist/exchanges/venues/pacifica.js +9 -0
- package/dist/exchanges/venues/risex.js +15 -0
- package/dist/funding/legacy-public.d.ts +5 -3
- package/dist/funding/legacy-public.js +33 -37
- package/dist/funding/rates.d.ts +54 -5
- package/dist/funding/rates.js +49 -13
- package/dist/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.js +11 -5
- package/dist/mcp-server.js +104 -15
- package/dist/pacifica/client.d.ts +2 -1
- package/dist/pacifica/client.js +11 -0
- package/dist/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- package/dist/strategies/funding-arb.js +35 -62
- package/dist/utils.d.ts +11 -1
- package/dist/venue-health.js +1 -1
- package/dist/ws/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +30 -9
- package/dist/ws/index.js +180 -15
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +30 -9
- package/dist/ws/node.js +204 -16
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/package.json +15 -2
- package/skills/perp-cli/SKILL.md +97 -33
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +15 -12
- package/skills/perp-cli/references/commands.md +67 -12
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +55 -24
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- package/dist/cli-spec.js +0 -81
package/dist/commands/account.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { listConfiguredAccountVenues } from "../config.js";
|
|
2
|
-
import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, symbolMatch } from "../utils.js";
|
|
2
|
+
import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, symbolMatch, withJsonErrors } from "../utils.js";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { hasPacificaSdk, isDexCapable } from "../exchanges/capabilities.js";
|
|
5
5
|
import { PerpError } from "../errors.js";
|
|
6
6
|
import { unsupported } from "../exchanges/trading-v2.js";
|
|
7
|
+
/** The configured Pacifica account of an account read; keyless → NO_SIGNER_AVAILABLE (never the throwaway keypair's). */
|
|
8
|
+
function pacificaAccount(adapter) {
|
|
9
|
+
const read = adapter.accountForReads;
|
|
10
|
+
return typeof read === "function" ? read.call(adapter) : adapter.publicKey;
|
|
11
|
+
}
|
|
7
12
|
function pac(adapter) {
|
|
8
13
|
if (!hasPacificaSdk(adapter))
|
|
9
14
|
throw new Error("Market settings are only available on Pacifica.");
|
|
@@ -28,8 +33,14 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
28
33
|
// user what to run and exit non-zero so scripts can detect the rename.
|
|
29
34
|
account
|
|
30
35
|
.command("balance")
|
|
31
|
-
.description("
|
|
32
|
-
.
|
|
36
|
+
.description("Any account's balances by public address, keyless (--address). Your own balance: 'perp portfolio' (renamed in v0.12)")
|
|
37
|
+
.option("--address <address>", "Read this public address — keyless (hyperliquid, lighter, lighter-rh, risex: 0x address; pacifica: Solana address). Without -e: every venue that supports it")
|
|
38
|
+
.option("--account-index <n>", "With --address on Lighter: only this account (sub-account) of the address")
|
|
39
|
+
.action(async (opts) => {
|
|
40
|
+
if (opts.address !== undefined) {
|
|
41
|
+
const explicitExchange = program.getOptionValueSource?.("exchange") === "cli";
|
|
42
|
+
return void await withJsonErrors(isJson(), () => balanceByAddress(opts.address, explicitExchange ? String(program.opts().exchange) : null, opts));
|
|
43
|
+
}
|
|
33
44
|
const msg = "'account balance' was renamed in v0.12 — use 'portfolio' instead.";
|
|
34
45
|
const remediation = "perp portfolio # all exchanges\nperp portfolio -e <exchange> # single exchange detail";
|
|
35
46
|
if (isJson()) {
|
|
@@ -47,9 +58,8 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
47
58
|
async function fetchHip3Data(hlAdapter, fetcher) {
|
|
48
59
|
const dexes = await hlAdapter.listDeployedDexes();
|
|
49
60
|
const results = await Promise.allSettled(dexes.map(async (d) => {
|
|
50
|
-
//
|
|
51
|
-
const dexAdapter =
|
|
52
|
-
dexAdapter.setDex(d.name);
|
|
61
|
+
// An independent per-dex adapter (a prototype copy + setDex emptied the native adapter's maps)
|
|
62
|
+
const dexAdapter = hlAdapter.forDex(d.name);
|
|
53
63
|
const items = await fetcher(dexAdapter);
|
|
54
64
|
return { dex: d.name, items };
|
|
55
65
|
}));
|
|
@@ -58,12 +68,158 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
58
68
|
.map(r => r.value)
|
|
59
69
|
.filter(r => r.items.length > 0);
|
|
60
70
|
}
|
|
71
|
+
/** `account balance --address`: keyless public read (registry: VenuePublicData.fetchBalanceByAddress). */
|
|
72
|
+
async function balanceByAddress(address, venue, opts) {
|
|
73
|
+
const { readBalanceByAddress, readBalanceByAddressAll } = await import("../balance-by-address.js");
|
|
74
|
+
const { networkOf } = await import("../cli-flags.js");
|
|
75
|
+
let accountIndex;
|
|
76
|
+
if (opts.accountIndex !== undefined) {
|
|
77
|
+
if (!/^\d+$/.test(opts.accountIndex.trim()))
|
|
78
|
+
throw new PerpError("INVALID_PARAMS", `--account-index must be a whole number, got "${opts.accountIndex}"`);
|
|
79
|
+
accountIndex = Number(opts.accountIndex);
|
|
80
|
+
}
|
|
81
|
+
const net = networkOf(program);
|
|
82
|
+
const balanceRows = (d) => {
|
|
83
|
+
const row = (label, b) => [
|
|
84
|
+
label, `$${formatUsd(b.equity)}`, `$${formatUsd(b.available)}`, `$${formatUsd(b.marginUsed)}`, formatPnl(b.unrealizedPnl),
|
|
85
|
+
];
|
|
86
|
+
return [
|
|
87
|
+
...(d.balance !== null ? [row(d.accountMode !== undefined ? `native (${d.accountMode})` : "account", d.balance)] : []),
|
|
88
|
+
...(d.accounts ?? []).map(a => row(`account ${a.accountIndex}`, a.balance)),
|
|
89
|
+
...Object.entries(d.dexes ?? {}).map(([dex, b]) => row(chalk.magenta(`dex ${dex}`), b)),
|
|
90
|
+
];
|
|
91
|
+
};
|
|
92
|
+
const spotRows = (d) => [
|
|
93
|
+
...(d.spot ?? []).map(s => ["-", s.token, s.total, s.available, s.held]),
|
|
94
|
+
...(d.accounts ?? []).flatMap(a => a.spot.map(s => [String(a.accountIndex), s.token, s.total, s.available, s.held])),
|
|
95
|
+
];
|
|
96
|
+
const extras = (d) => {
|
|
97
|
+
const b = d.balance;
|
|
98
|
+
if (b === null)
|
|
99
|
+
return [];
|
|
100
|
+
return ["usdcBalance", "borrowedUsdc", "pendingInterest", "availableToWithdraw"].filter(k => k in b).map(k => `${k}: ${b[k] === null ? "-" : String(b[k])}`);
|
|
101
|
+
};
|
|
102
|
+
const printVenue = (ex, d) => {
|
|
103
|
+
console.log(chalk.cyan.bold(`\n ${ex}`));
|
|
104
|
+
const rows = balanceRows(d);
|
|
105
|
+
if (rows.length > 0)
|
|
106
|
+
console.log(makeTable(["Account", "Equity", "Available", "Margin Used", "uPnL"], rows));
|
|
107
|
+
const x = extras(d);
|
|
108
|
+
if (x.length > 0)
|
|
109
|
+
console.log(chalk.gray(` ${x.join(" ")}`));
|
|
110
|
+
const sr = spotRows(d);
|
|
111
|
+
if (sr.length > 0)
|
|
112
|
+
console.log(makeTable(["Acct", "Spot token", "Total", "Available", "Held"], sr));
|
|
113
|
+
if (rows.length === 0 && sr.length === 0)
|
|
114
|
+
console.log(chalk.gray(" no balance (see notes)"));
|
|
115
|
+
};
|
|
116
|
+
if (venue !== null) {
|
|
117
|
+
const r = await readBalanceByAddress(venue, net, address, { accountIndex });
|
|
118
|
+
if (isJson()) {
|
|
119
|
+
return printJson(jsonOk({ exchange: r.venue, address, ...r.data }, {
|
|
120
|
+
exchange: r.venue, address, source: "public",
|
|
121
|
+
...(r.warnings.length > 0 ? { warnings: r.warnings, errors: r.errors } : {}),
|
|
122
|
+
...(r.notes.length > 0 ? { notes: r.notes } : {}),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
for (const w of r.warnings)
|
|
126
|
+
console.log(chalk.yellow(` ${w}`));
|
|
127
|
+
printVenue(r.venue, r.data);
|
|
128
|
+
for (const n of r.notes)
|
|
129
|
+
console.log(chalk.gray(` ${n}`));
|
|
130
|
+
console.log();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const all = await readBalanceByAddressAll(net, address, { accountIndex });
|
|
134
|
+
if (isJson()) {
|
|
135
|
+
return printJson(jsonOk({
|
|
136
|
+
venues: all.venues,
|
|
137
|
+
...(Object.keys(all.errors).length > 0 ? { errors: all.errors } : {}),
|
|
138
|
+
...(Object.keys(all.notes).length > 0 ? { notes: all.notes } : {}),
|
|
139
|
+
}, { address, source: "public", ...(all.warnings.length > 0 ? { warnings: all.warnings } : {}) }));
|
|
140
|
+
}
|
|
141
|
+
for (const [ex, d] of Object.entries(all.venues))
|
|
142
|
+
printVenue(ex, d);
|
|
143
|
+
console.log();
|
|
144
|
+
for (const [ex, e] of Object.entries(all.errors))
|
|
145
|
+
console.log(chalk.gray(` ${ex}: ${e.code} ${e.message}`));
|
|
146
|
+
for (const [ex, n] of Object.entries(all.notes))
|
|
147
|
+
console.log(chalk.gray(` ${ex}: ${n.join("; ")}`));
|
|
148
|
+
}
|
|
149
|
+
/** `account positions --address`: keyless public read (registry: VenuePublicData.fetchPositionsByAddress). */
|
|
150
|
+
async function positionsByAddress(address, venue, opts) {
|
|
151
|
+
const { readPositionsByAddress, readPositionsByAddressAll } = await import("../positions-by-address.js");
|
|
152
|
+
const { networkOf } = await import("../cli-flags.js");
|
|
153
|
+
let accountIndex;
|
|
154
|
+
if (opts.accountIndex !== undefined) {
|
|
155
|
+
if (!/^\d+$/.test(opts.accountIndex.trim()))
|
|
156
|
+
throw new PerpError("INVALID_PARAMS", `--account-index must be a whole number, got "${opts.accountIndex}"`);
|
|
157
|
+
accountIndex = Number(opts.accountIndex);
|
|
158
|
+
}
|
|
159
|
+
const net = networkOf(program);
|
|
160
|
+
const row = (p, ex) => [
|
|
161
|
+
chalk.white.bold(p.symbol),
|
|
162
|
+
...(ex !== null ? [chalk.gray(ex)] : []),
|
|
163
|
+
p.dex != null ? chalk.magenta(p.dex) : p.accountIndex !== undefined ? chalk.gray(String(p.accountIndex)) : chalk.gray("-"),
|
|
164
|
+
p.side === "long" ? chalk.green("LONG") : chalk.red("SHORT"),
|
|
165
|
+
p.size,
|
|
166
|
+
`$${formatUsd(p.entryPrice)}`,
|
|
167
|
+
`$${formatUsd(p.markPrice)}`,
|
|
168
|
+
p.liquidationPrice === null ? chalk.gray("-") : `$${formatUsd(p.liquidationPrice)}`,
|
|
169
|
+
formatPnl(p.unrealizedPnl),
|
|
170
|
+
`${p.leverage}x`,
|
|
171
|
+
];
|
|
172
|
+
if (venue !== null) {
|
|
173
|
+
const r = await readPositionsByAddress(venue, net, address, { accountIndex, symbol: opts.symbol });
|
|
174
|
+
if (isJson()) {
|
|
175
|
+
return printJson(jsonOk(r.positions, {
|
|
176
|
+
exchange: r.venue, address, source: "public",
|
|
177
|
+
...(r.warnings.length > 0 ? { warnings: r.warnings, errors: r.errors } : {}),
|
|
178
|
+
...(r.notes.length > 0 ? { notes: r.notes } : {}),
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
for (const w of r.warnings)
|
|
182
|
+
console.log(chalk.yellow(` ${w}`));
|
|
183
|
+
if (r.positions.length === 0) {
|
|
184
|
+
console.log(chalk.gray(`\n No open positions for ${address} on ${r.venue}${r.notes.length > 0 ? ` (${r.notes.join("; ")})` : ""}.\n`));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
console.log(makeTable(["Symbol", "Dex/Acct", "Side", "Size", "Entry", "Mark", "Liq", "PnL", "Lev"], r.positions.map(p => row(p, null))));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const all = await readPositionsByAddressAll(net, address, { accountIndex, symbol: opts.symbol });
|
|
191
|
+
if (isJson()) {
|
|
192
|
+
return printJson(jsonOk({
|
|
193
|
+
venues: all.venues,
|
|
194
|
+
...(Object.keys(all.errors).length > 0 ? { errors: all.errors } : {}),
|
|
195
|
+
...(Object.keys(all.notes).length > 0 ? { notes: all.notes } : {}),
|
|
196
|
+
}, { address, source: "public", ...(all.warnings.length > 0 ? { warnings: all.warnings } : {}) }));
|
|
197
|
+
}
|
|
198
|
+
const rows = Object.entries(all.venues).flatMap(([ex, ps]) => ps.map(p => row(p, ex)));
|
|
199
|
+
if (rows.length > 0)
|
|
200
|
+
console.log(makeTable(["Symbol", "Exch", "Dex/Acct", "Side", "Size", "Entry", "Mark", "Liq", "PnL", "Lev"], rows));
|
|
201
|
+
else
|
|
202
|
+
console.log(chalk.gray(`\n No open positions for ${address} on ${Object.keys(all.venues).join(", ") || "any venue"}.\n`));
|
|
203
|
+
for (const [ex, e] of Object.entries(all.errors))
|
|
204
|
+
console.log(chalk.gray(` ${ex}: ${e.code} ${e.message}`));
|
|
205
|
+
for (const [ex, n] of Object.entries(all.notes))
|
|
206
|
+
console.log(chalk.gray(` ${ex}: ${n.join("; ")}`));
|
|
207
|
+
}
|
|
61
208
|
account
|
|
62
209
|
.command("positions")
|
|
63
|
-
.description("Show open positions")
|
|
210
|
+
.description("Show open positions (yours; or any account's, keyless, with --address)")
|
|
64
211
|
.option("--hip3", "Include HIP-3 dex positions (Hyperliquid)")
|
|
212
|
+
.option("--address <address>", "Read this public address instead of your account — keyless (hyperliquid, lighter, lighter-rh, risex: 0x address; pacifica: Solana address). Without -e: every venue that supports it")
|
|
213
|
+
.option("--account-index <n>", "With --address on Lighter: only this account (sub-account) of the address")
|
|
214
|
+
.option("--symbol <symbol>", "With --address: only this market (plain asset, e.g. BTC)")
|
|
65
215
|
.action(async (opts) => {
|
|
66
216
|
const explicitExchange = program.getOptionValueSource?.("exchange") === "cli";
|
|
217
|
+
if (opts.address !== undefined) {
|
|
218
|
+
return void await withJsonErrors(isJson(), () => positionsByAddress(opts.address, explicitExchange ? String(program.opts().exchange) : null, opts));
|
|
219
|
+
}
|
|
220
|
+
if (opts.accountIndex !== undefined || opts.symbol !== undefined) {
|
|
221
|
+
throw new PerpError("INVALID_PARAMS", "--account-index / --symbol apply to --address reads", { remediation: "Add --address <address>, or drop the option" });
|
|
222
|
+
}
|
|
67
223
|
if (!explicitExchange && getAdapterForExchange) {
|
|
68
224
|
const EXCHANGES = await listConfiguredAccountVenues();
|
|
69
225
|
const allRows = [];
|
|
@@ -316,7 +472,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
316
472
|
// Pacifica: use dedicated SDK endpoint
|
|
317
473
|
if (hasPacificaSdk(adapter)) {
|
|
318
474
|
const sdk = adapter.sdk;
|
|
319
|
-
const settings = await sdk.getAccountSettings(adapter
|
|
475
|
+
const settings = await sdk.getAccountSettings(pacificaAccount(adapter));
|
|
320
476
|
if (isJson())
|
|
321
477
|
return printJson(jsonOk(settings));
|
|
322
478
|
// GET /account/settings returns an object; per-market entries are in `margin_settings`
|
|
@@ -404,7 +560,8 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
404
560
|
.description("Margin details for a specific symbol position")
|
|
405
561
|
.action(async (symbol) => {
|
|
406
562
|
const sym = symbol.toUpperCase();
|
|
407
|
-
|
|
563
|
+
// Errors: the one envelope (code, status, remediation) and exit 1 via withJsonErrors.
|
|
564
|
+
return void await withJsonErrors(isJson(), async () => {
|
|
408
565
|
const adapter = await getAdapter();
|
|
409
566
|
const [balance, positions] = await Promise.all([
|
|
410
567
|
adapter.getBalance(),
|
|
@@ -452,20 +609,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
452
609
|
console.log(` Liquidation: ${pos.liquidationPrice === null ? chalk.gray("-") : `$${formatUsd(pos.liquidationPrice)}`}`);
|
|
453
610
|
console.log(` Unrealized PnL: ${formatPnl(pos.unrealizedPnl)}`);
|
|
454
611
|
console.log();
|
|
455
|
-
}
|
|
456
|
-
catch (err) {
|
|
457
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
458
|
-
if (isJson()) {
|
|
459
|
-
const { classifyError } = await import("../errors.js");
|
|
460
|
-
const classified = classifyError(err);
|
|
461
|
-
return printJson(jsonError(classified.code, classified.message, {
|
|
462
|
-
status: classified.status,
|
|
463
|
-
retryable: classified.retryable,
|
|
464
|
-
}));
|
|
465
|
-
}
|
|
466
|
-
console.error(chalk.red(`Error: ${msg}`));
|
|
467
|
-
process.exitCode = 1;
|
|
468
|
-
}
|
|
612
|
+
});
|
|
469
613
|
});
|
|
470
614
|
// ── PnL Report ──
|
|
471
615
|
account
|
|
@@ -609,7 +753,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
609
753
|
}
|
|
610
754
|
const p = pac(adapter);
|
|
611
755
|
const sdk = p.sdk;
|
|
612
|
-
const orders = await sdk.getTWAPOrders(p
|
|
756
|
+
const orders = await sdk.getTWAPOrders(pacificaAccount(p));
|
|
613
757
|
if (isJson())
|
|
614
758
|
return printJson(jsonOk(orders));
|
|
615
759
|
if (!orders || orders.length === 0) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { isDryRun, networkOf, supportsDryRun } from "../../cli-flags.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { makeTable, formatUsd, formatPnl, printJson, jsonOk
|
|
3
|
+
import { makeTable, formatUsd, formatPnl, printJson, jsonOk } from "../../utils.js";
|
|
4
4
|
import { SPOT_PERP_TOKEN_MAP } from "../../exchanges/index.js";
|
|
5
5
|
import { readExecutionLog, logExecution } from "../../execution-log.js";
|
|
6
6
|
import { toHourlyRate } from "../../funding.js";
|
|
7
|
-
import {
|
|
7
|
+
import { fetchVenuesRates } from "../../funding/rates.js";
|
|
8
|
+
import { sharedApiNetwork } from "../../api/public/urls.js";
|
|
8
9
|
import { computeBasisRisk } from "../../arb-utils.js";
|
|
9
10
|
import { removePosition as persistRemovePosition, getPositions as getArbStatePositions, loadArbState } from "../../arb-state.js";
|
|
10
11
|
import { fetchBalancesWithFailures, computeRebalancePlan } from "../../rebalance.js";
|
|
@@ -15,7 +16,7 @@ import { listConfiguredAccountVenues } from "../../config.js";
|
|
|
15
16
|
import { closePerpPairVerified, handleSpotClose } from "../arb-auto.js";
|
|
16
17
|
import { quarantineEntryFor } from "../../bot/quarantine.js";
|
|
17
18
|
import { PerpError, classifyError, parsePositiveDecimal } from "../../errors.js";
|
|
18
|
-
import { listVenuesWithRole, resolveVenueId } from "../../exchanges/venue.js";
|
|
19
|
+
import { getVenue, listVenuesWithRole, resolveVenueId } from "../../exchanges/venue.js";
|
|
19
20
|
// ── Helpers ──
|
|
20
21
|
import { DEFAULT_TAKER_FEE as TAKER_FEE } from "../../constants.js";
|
|
21
22
|
/**
|
|
@@ -108,81 +109,22 @@ function entryHourly(e) {
|
|
|
108
109
|
return e.fundingHours !== null ? e.rate / e.fundingHours : toHourlyRate(e.rate, e.exchange);
|
|
109
110
|
}
|
|
110
111
|
async function fetchFundingRatesMap() {
|
|
112
|
+
// The SAME rate rows as `arb scan --rates` (fetchVenuesRates): canonical-keyed (unclassified /
|
|
113
|
+
// ambiguous tickers have no row), each with its own basis (fundingHours) and rateKind. A failed
|
|
114
|
+
// venue is reported on stderr (explicit, never silent).
|
|
115
|
+
const venues = ["pacifica", "hyperliquid", "lighter", "aster"].map(id => getVenue(id));
|
|
116
|
+
const { rates: fetched, exchangeErrors } = await fetchVenuesRates(venues, sharedApiNetwork(), false);
|
|
117
|
+
// Never a leg on an unverified rate (row label; the venue filter already drops them).
|
|
118
|
+
const rates = fetched.filter(r => r.fundingVerified);
|
|
119
|
+
for (const [ex, msg] of Object.entries(exchangeErrors))
|
|
120
|
+
process.stderr.write(`[arb] ${ex}: funding rates unavailable — ${msg}\n`);
|
|
111
121
|
const rateMap = new Map();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
fetchLighterOrderBookDetails(),
|
|
119
|
-
fetchLighterFundingRates(),
|
|
120
|
-
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()),
|
|
121
|
-
fetch("https://fapi.asterdex.com/fapi/v3/fundingInfo").then(r => r.json()),
|
|
122
|
-
]);
|
|
123
|
-
logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter:orderbook", "lighter:funding", "aster:premiumIndex", "aster:fundingInfo"], "arb");
|
|
124
|
-
const pacAssets = settled[0].status === "fulfilled" ? settled[0].value : [];
|
|
125
|
-
const hlAssets = settled[1].status === "fulfilled" ? settled[1].value : [];
|
|
126
|
-
const ltDetails = settled[2].status === "fulfilled" ? settled[2].value : [];
|
|
127
|
-
const ltFunding = settled[3].status === "fulfilled" ? settled[3].value : [];
|
|
128
|
-
// Aster funding basis is per symbol: a rate is usable only with its fundingInfo interval.
|
|
129
|
-
const asterPremiums = settled[4].status === "fulfilled" && settled[5].status === "fulfilled" ? settled[4].value : [];
|
|
130
|
-
const asterInfo = settled[5].status === "fulfilled" ? settled[5].value : [];
|
|
131
|
-
const asterHours = new Map();
|
|
132
|
-
for (const row of Array.isArray(asterInfo) ? asterInfo : []) {
|
|
133
|
-
const h = Number(row.fundingIntervalHours);
|
|
134
|
-
if (typeof row.symbol === "string" && Number.isFinite(h) && h > 0)
|
|
135
|
-
asterHours.set(row.symbol, h);
|
|
136
|
-
}
|
|
137
|
-
const addRate = (sym, exchange, rate, markPrice, fundingHours = null) => {
|
|
138
|
-
if (!sym)
|
|
139
|
-
return;
|
|
140
|
-
if (!rateMap.has(sym))
|
|
141
|
-
rateMap.set(sym, []);
|
|
142
|
-
rateMap.get(sym).push({ exchange, rate, markPrice, fundingHours });
|
|
143
|
-
};
|
|
144
|
-
for (const p of pacAssets)
|
|
145
|
-
addRate(p.symbol, "pacifica", p.funding, p.mark);
|
|
146
|
-
for (const h of hlAssets)
|
|
147
|
-
addRate(h.symbol, "hyperliquid", h.funding, h.markPx);
|
|
148
|
-
// Lighter: join details + funding by marketId
|
|
149
|
-
// SSOT rule #2: skip rows whose mark price cannot be resolved (no 0 fallback).
|
|
150
|
-
// Prefer fr.markPrice; fall back to orderBook last-trade as documented
|
|
151
|
-
// price-source preference (NOT error fallback).
|
|
152
|
-
const ltPriceMap = new Map(ltDetails.map(d => [d.marketId, d.lastTradePrice]));
|
|
153
|
-
const ltSymMap = new Map(ltDetails.map(d => [d.marketId, d.symbol]));
|
|
154
|
-
for (const fr of ltFunding) {
|
|
155
|
-
const sym = fr.symbol || ltSymMap.get(fr.marketId) || "";
|
|
156
|
-
if (!sym)
|
|
157
|
-
continue;
|
|
158
|
-
const directMark = fr.markPrice;
|
|
159
|
-
const fallbackMark = ltPriceMap.get(fr.marketId);
|
|
160
|
-
let mp;
|
|
161
|
-
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
162
|
-
mp = directMark;
|
|
163
|
-
}
|
|
164
|
-
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
165
|
-
mp = fallbackMark;
|
|
166
|
-
}
|
|
167
|
-
if (mp === undefined || !Number.isFinite(fr.rate))
|
|
168
|
-
continue;
|
|
169
|
-
addRate(sym, "lighter", fr.rate, mp);
|
|
170
|
-
}
|
|
171
|
-
// Aster: premiumIndex
|
|
172
|
-
// SSOT rule #2: skip rows missing markPrice / lastFundingRate instead of
|
|
173
|
-
// 0-coercing — a 0 markPrice would corrupt downstream sizing.
|
|
174
|
-
for (const p of asterPremiums) {
|
|
175
|
-
const rawSym = String(p.symbol ?? "");
|
|
176
|
-
if (!rawSym.endsWith("USDT"))
|
|
177
|
-
continue;
|
|
178
|
-
const fundingRate = Number(p.lastFundingRate);
|
|
179
|
-
const markPrice = Number(p.markPrice);
|
|
180
|
-
if (!Number.isFinite(fundingRate) || !Number.isFinite(markPrice) || markPrice <= 0)
|
|
181
|
-
continue;
|
|
182
|
-
const hours = asterHours.get(rawSym);
|
|
183
|
-
if (hours === undefined)
|
|
184
|
-
continue; // not in /fapi/v3/fundingInfo → cannot normalize
|
|
185
|
-
addRate(rawSym.replace(/USDT$/, ""), "aster", fundingRate, markPrice, hours);
|
|
122
|
+
for (const r of rates) {
|
|
123
|
+
if (r.basis.kind !== "perHours")
|
|
124
|
+
continue; // these four venues quote per hours
|
|
125
|
+
if (!rateMap.has(r.canonical))
|
|
126
|
+
rateMap.set(r.canonical, []);
|
|
127
|
+
rateMap.get(r.canonical).push({ exchange: r.exchange, rate: r.fundingRate, markPrice: r.markPrice, fundingHours: r.basis.hours });
|
|
186
128
|
}
|
|
187
129
|
return rateMap;
|
|
188
130
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Network } from "../exchanges/venue-types.js";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
4
|
+
import type { FundingRateKind } from "../exchanges/interface.js";
|
|
4
5
|
interface FundingSnapshot {
|
|
5
6
|
symbol: string;
|
|
6
7
|
pacRate: number;
|
|
@@ -17,7 +18,24 @@ interface FundingSnapshot {
|
|
|
17
18
|
astMarkPrice: number;
|
|
18
19
|
/** Aster per-symbol funding interval (= basis and settlement interval) from /fapi/v3/fundingInfo; null when Aster has no rate. */
|
|
19
20
|
astHours: number | null;
|
|
21
|
+
/** Which rate each paired venue's value is (arb scan rateKind): "lastSettled" | "accruing" | "predicted" | null. */
|
|
22
|
+
rateKinds?: Record<string, FundingRateKind | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Each venue's OWN symbol for this canonical row (Hyperliquid kPEPE, Lighter 1000PEPE): orders,
|
|
25
|
+
* position matching and closes use it, never the canonical key. Absent = the venue trades `symbol`.
|
|
26
|
+
*/
|
|
27
|
+
venueSymbols?: Record<string, string>;
|
|
20
28
|
}
|
|
29
|
+
/** The venue symbol a snapshot / position leg trades on `exchange` (the canonical key when the venue uses it). */
|
|
30
|
+
export declare function legSymbol(row: {
|
|
31
|
+
symbol: string;
|
|
32
|
+
venueSymbols?: Record<string, string>;
|
|
33
|
+
}, exchange: string): string;
|
|
34
|
+
/** --symbols matches a row by its canonical key or any venue's own symbol (kPEPE or 1000PEPE). */
|
|
35
|
+
export declare function matchesSymbolFilter(row: {
|
|
36
|
+
symbol: string;
|
|
37
|
+
venueSymbols?: Record<string, string>;
|
|
38
|
+
}, filter: readonly string[]): boolean;
|
|
21
39
|
export interface LegOrderMinimums {
|
|
22
40
|
exchange: string;
|
|
23
41
|
minSize: string | null;
|
|
@@ -216,10 +234,16 @@ export declare function dexCloseBlockedBy(pos: {
|
|
|
216
234
|
longDex: string;
|
|
217
235
|
shortDex: string;
|
|
218
236
|
}, failedDexes: Record<string, string>): string | null;
|
|
237
|
+
export declare function fetchFundingSpreads(): Promise<FundingSnapshot[]>;
|
|
219
238
|
export declare let handleSpotClose: ((sym: string, opts: {
|
|
220
239
|
spotExch?: string;
|
|
221
240
|
perpExch?: string;
|
|
222
241
|
}, getAdapterForExchange: (exchange: string) => Promise<import("../exchanges/interface.js").ExchangeAdapter>, isJson: () => boolean) => Promise<void>) | undefined;
|
|
242
|
+
/**
|
|
243
|
+
* `arb auto` flag validation: every numeric flag must be a finite number in its range, `--size` a
|
|
244
|
+
* number or "auto", `--settle-strategy` one of block | aggressive | off. INVALID_PARAMS otherwise.
|
|
245
|
+
*/
|
|
246
|
+
export declare function assertArbAutoFlags(opts: Record<string, unknown>): void;
|
|
223
247
|
export declare function registerArbAutoCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getHLAdapterForDex?: (dex: string) => Promise<import("../exchanges/hyperliquid.js").HyperliquidAdapter>): void;
|
|
224
248
|
export declare let handleArbPnlRef: (() => Promise<void>) | undefined;
|
|
225
249
|
/**
|