perp-cli 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/arb/sizing.d.ts +1 -0
- package/dist/arb/sizing.js +25 -6
- package/dist/arb/utils.js +1 -0
- package/dist/bot/conditions.js +1 -2
- package/dist/bot/config.d.ts +3 -1
- package/dist/bot/config.js +4 -2
- package/dist/bot/engine.js +63 -18
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/presets.js +1 -0
- package/dist/bot/strategies/dca-strategy.js +8 -4
- package/dist/bot/strategies/funding-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/funding-arb-strategy.js +126 -14
- package/dist/bot/strategies/grid-strategy.js +56 -51
- package/dist/bot/tui/BotDashboard.d.ts +7 -0
- package/dist/bot/tui/BotDashboard.js +3 -2
- package/dist/bot/tui/index.d.ts +1 -1
- package/dist/cli-spec.js +1 -1
- package/dist/commands/account.js +1 -1
- package/dist/commands/arb/index.js +12 -4
- package/dist/commands/arb-auto.d.ts +2 -0
- package/dist/commands/arb-auto.js +41 -18
- package/dist/commands/arb.d.ts +1 -0
- package/dist/commands/arb.js +26 -5
- package/dist/commands/bot.js +47 -10
- package/dist/commands/bridge.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/funds.js +1 -1
- package/dist/commands/history.js +6 -2
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +5 -0
- package/dist/commands/market.js +20 -11
- package/dist/commands/rebalance.js +3 -3
- package/dist/commands/risk.js +1 -1
- package/dist/commands/settings.js +1 -1
- package/dist/commands/trade.js +3 -3
- package/dist/commands/wallet.js +2 -2
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -0
- package/dist/constants.js +1 -0
- package/dist/exchanges/aster.d.ts +4 -0
- package/dist/exchanges/aster.js +39 -28
- package/dist/exchanges/edgex.d.ts +62 -0
- package/dist/exchanges/edgex.js +552 -0
- package/dist/exchanges/index.d.ts +1 -0
- package/dist/exchanges/index.js +1 -0
- package/dist/exchanges/registry.js +5 -0
- package/dist/funding/normalize.js +1 -0
- package/dist/funding/rates.d.ts +1 -1
- package/dist/funding/rates.js +36 -3
- package/dist/index.js +19 -6
- package/dist/strategies/funding-arb.js +28 -5
- package/package.json +1 -1
package/dist/funding/rates.js
CHANGED
|
@@ -81,6 +81,38 @@ async function fetchLighterRates() {
|
|
|
81
81
|
return [];
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
async function fetchAsterRates() {
|
|
85
|
+
try {
|
|
86
|
+
const [premiums, tickers] = await Promise.all([
|
|
87
|
+
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()),
|
|
88
|
+
fetch("https://fapi.asterdex.com/fapi/v1/ticker/24hr").then(r => r.json()),
|
|
89
|
+
]);
|
|
90
|
+
const priceMap = new Map();
|
|
91
|
+
for (const t of tickers ?? []) {
|
|
92
|
+
priceMap.set(String(t.symbol), Number(t.lastPrice ?? 0));
|
|
93
|
+
}
|
|
94
|
+
return (premiums ?? [])
|
|
95
|
+
.filter(p => String(p.symbol ?? "").endsWith("USDT"))
|
|
96
|
+
.map(p => {
|
|
97
|
+
const rawSymbol = String(p.symbol ?? "");
|
|
98
|
+
const symbol = rawSymbol.replace(/USDT$/, "");
|
|
99
|
+
const rate = Number(p.lastFundingRate ?? 0);
|
|
100
|
+
const hourly = toHourlyRate(rate, "aster");
|
|
101
|
+
return {
|
|
102
|
+
exchange: "aster",
|
|
103
|
+
symbol,
|
|
104
|
+
fundingRate: rate,
|
|
105
|
+
hourlyRate: hourly,
|
|
106
|
+
annualizedPct: annualizeRate(rate, "aster"),
|
|
107
|
+
markPrice: Number(p.markPrice ?? priceMap.get(rawSymbol) ?? 0),
|
|
108
|
+
nextFundingTime: Number(p.nextFundingTime ?? 0),
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
84
116
|
// ── Core comparison logic ──
|
|
85
117
|
/**
|
|
86
118
|
* Fetch funding rates from all 3 DEXs in parallel, normalize, and compare.
|
|
@@ -88,12 +120,13 @@ async function fetchLighterRates() {
|
|
|
88
120
|
*/
|
|
89
121
|
export async function fetchAllFundingRates(opts) {
|
|
90
122
|
const exchangeStatus = {};
|
|
91
|
-
const [pacRates, hlRates, ltRates] = await Promise.all([
|
|
123
|
+
const [pacRates, hlRates, ltRates, astRates] = await Promise.all([
|
|
92
124
|
fetchPacificaRates().then(r => { exchangeStatus.pacifica = r.length > 0 ? "ok" : "error"; return r; }),
|
|
93
125
|
fetchHyperliquidRates().then(r => { exchangeStatus.hyperliquid = r.length > 0 ? "ok" : "error"; return r; }),
|
|
94
126
|
fetchLighterRates().then(r => { exchangeStatus.lighter = r.length > 0 ? "ok" : "error"; return r; }),
|
|
127
|
+
fetchAsterRates().then(r => { exchangeStatus.aster = r.length > 0 ? "ok" : "error"; return r; }),
|
|
95
128
|
]);
|
|
96
|
-
const allRates = [...pacRates, ...hlRates, ...ltRates];
|
|
129
|
+
const allRates = [...pacRates, ...hlRates, ...ltRates, ...astRates];
|
|
97
130
|
// Build per-symbol rate map
|
|
98
131
|
const rateMap = new Map();
|
|
99
132
|
for (const r of allRates) {
|
|
@@ -109,7 +142,7 @@ export async function fetchAllFundingRates(opts) {
|
|
|
109
142
|
// Attach historical averages when available
|
|
110
143
|
try {
|
|
111
144
|
const symbols = Array.from(rateMap.keys());
|
|
112
|
-
const exchanges = ["pacifica", "hyperliquid", "lighter"];
|
|
145
|
+
const exchanges = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
113
146
|
const historicals = getHistoricalAverages(symbols, exchanges);
|
|
114
147
|
for (const [, rates] of rateMap) {
|
|
115
148
|
for (const r of rates) {
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ program
|
|
|
50
50
|
.name("perp")
|
|
51
51
|
.description("Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)")
|
|
52
52
|
.version(_pkg.version)
|
|
53
|
-
.option("-e, --exchange <exchange>", `Exchange: pacifica, hyperliquid, lighter (default: ${_defaultExchange})`, _defaultExchange)
|
|
53
|
+
.option("-e, --exchange <exchange>", `Exchange: pacifica, hyperliquid, lighter, aster (default: ${_defaultExchange})`, _defaultExchange)
|
|
54
54
|
.option("-n, --network <network>", "Network: mainnet or testnet", "mainnet")
|
|
55
55
|
.option("-k, --private-key <key>", "Private key")
|
|
56
56
|
.option("--json", "Output raw JSON (for piping)")
|
|
@@ -156,6 +156,13 @@ async function getAdapter() {
|
|
|
156
156
|
_adapter = _lighterAdapter;
|
|
157
157
|
break;
|
|
158
158
|
}
|
|
159
|
+
case "aster": {
|
|
160
|
+
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
161
|
+
const ast = new AsterAdapter(undefined, undefined, isTestnet);
|
|
162
|
+
await ast.init();
|
|
163
|
+
_adapter = ast;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
159
166
|
default:
|
|
160
167
|
throw new Error(`Unknown exchange: ${exchange}`);
|
|
161
168
|
}
|
|
@@ -271,6 +278,12 @@ async function getAdapterForExchange(rawExchange) {
|
|
|
271
278
|
_adapter = _lighterAdapter;
|
|
272
279
|
return _lighterAdapter;
|
|
273
280
|
}
|
|
281
|
+
case "aster": {
|
|
282
|
+
const { AsterAdapter } = await import("./exchanges/aster.js");
|
|
283
|
+
const ast = new AsterAdapter(undefined, undefined, isTestnet);
|
|
284
|
+
await ast.init();
|
|
285
|
+
return ast;
|
|
286
|
+
}
|
|
274
287
|
default:
|
|
275
288
|
throw new Error(`Unknown exchange: ${exchange}`);
|
|
276
289
|
}
|
|
@@ -334,8 +347,8 @@ program.command("status")
|
|
|
334
347
|
const { fetchAllFundingRates, TOP_SYMBOLS } = await import("./funding-rates.js");
|
|
335
348
|
const { saveFundingSnapshot, getHistoricalRates } = await import("./funding-history.js");
|
|
336
349
|
await withJsonErrors(json, async () => {
|
|
337
|
-
const EX_LIST = ["pacifica", "hyperliquid", "lighter"];
|
|
338
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
350
|
+
const EX_LIST = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
351
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : "AST";
|
|
339
352
|
const snapshotPromises = EX_LIST.map(async (ex) => {
|
|
340
353
|
try {
|
|
341
354
|
const adapter = await getAdapterForExchange(ex);
|
|
@@ -606,7 +619,7 @@ program.hook("preAction", () => {
|
|
|
606
619
|
});
|
|
607
620
|
// Smart landing page: `perp` with no subcommand
|
|
608
621
|
const rawArgs = process.argv.slice(2);
|
|
609
|
-
const hasSubcommand = rawArgs.some((a) => !a.startsWith("-") && !["pacifica", "hyperliquid", "lighter", "hl", "lt", "pac", "mainnet", "testnet"].includes(a));
|
|
622
|
+
const hasSubcommand = rawArgs.some((a) => !a.startsWith("-") && !["pacifica", "hyperliquid", "lighter", "aster", "hl", "lt", "pac", "ast", "mainnet", "testnet"].includes(a));
|
|
610
623
|
if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawArgs.includes("--help") && !rawArgs.includes("-V") && !rawArgs.includes("--version"))) {
|
|
611
624
|
// No subcommand — show smart landing instead of help dump
|
|
612
625
|
(async () => {
|
|
@@ -638,8 +651,8 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
|
|
|
638
651
|
}
|
|
639
652
|
else {
|
|
640
653
|
// Configured — show exchange status + balance
|
|
641
|
-
const EX_NAMES = ["pacifica", "hyperliquid", "lighter"];
|
|
642
|
-
const exLabel = (e) => e === "pacifica" ? "Pacifica" : e === "hyperliquid" ? "Hyperliquid" : "Lighter";
|
|
654
|
+
const EX_NAMES = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
655
|
+
const exLabel = (e) => e === "pacifica" ? "Pacifica" : e === "hyperliquid" ? "Hyperliquid" : e === "lighter" ? "Lighter" : "Aster";
|
|
643
656
|
// Ping + balance in parallel (with 5s timeout to keep landing fast)
|
|
644
657
|
const withTimeout = (p, ms) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), ms))]);
|
|
645
658
|
const statusResults = await Promise.allSettled(EX_NAMES.map(async (ex) => {
|
|
@@ -184,7 +184,7 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
|
|
|
184
184
|
if (!liq.viable)
|
|
185
185
|
continue;
|
|
186
186
|
// Compute matched size (same for both legs)
|
|
187
|
-
|
|
187
|
+
let matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx);
|
|
188
188
|
if (!matched) {
|
|
189
189
|
log(`[ARB] Skip ${symbol}: can't compute matched size (min notional or precision issue)`);
|
|
190
190
|
continue;
|
|
@@ -199,10 +199,33 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
|
|
|
199
199
|
}
|
|
200
200
|
try {
|
|
201
201
|
log(`[ARB] Opening: ${matched.size} ${symbol} on both legs ($${matched.notional.toFixed(0)}/leg, slippage ~${liq.longSlippage.toFixed(2)}%/${liq.shortSlippage.toFixed(2)}%)...`);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
try {
|
|
203
|
+
await Promise.all([
|
|
204
|
+
longAdapter.marketOrder(symbol, "buy", matched.size),
|
|
205
|
+
shortAdapter.marketOrder(symbol, "sell", matched.size),
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
208
|
+
catch (orderErr) {
|
|
209
|
+
const errMsg = orderErr instanceof Error ? orderErr.message : String(orderErr);
|
|
210
|
+
const lotMatch = errMsg.match(/not a multiple of lot size (\d+(?:\.\d+)?)/);
|
|
211
|
+
if (lotMatch) {
|
|
212
|
+
const lotSize = parseFloat(lotMatch[1]);
|
|
213
|
+
log(`[ARB] Lot size ${lotSize} detected, recalculating...`);
|
|
214
|
+
matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx, { lotSize });
|
|
215
|
+
if (!matched) {
|
|
216
|
+
log(`[ARB] Skip ${symbol}: can't meet lot size ${lotSize}`);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
log(`[ARB] Retry: ${matched.size} ${symbol} ($${matched.notional.toFixed(0)}/leg)`);
|
|
220
|
+
await Promise.all([
|
|
221
|
+
longAdapter.marketOrder(symbol, "buy", matched.size),
|
|
222
|
+
shortAdapter.marketOrder(symbol, "sell", matched.size),
|
|
223
|
+
]);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
throw orderErr;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
206
229
|
// Verify fills match, correct if needed
|
|
207
230
|
try {
|
|
208
231
|
const recon = await reconcileArbFills(longAdapter, shortAdapter, symbol, log);
|