perp-cli 0.7.2 → 0.7.4
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 +2 -1
- package/dist/bot/config.js +3 -1
- package/dist/bot/engine.js +23 -13
- 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/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 +37 -16
- package/dist/commands/arb.js +25 -6
- package/dist/commands/bot.js +5 -6
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/history.js +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +5 -0
- package/dist/commands/market.js +1 -1
- package/dist/commands/rebalance.js +3 -3
- package/dist/commands/risk.js +1 -1
- package/dist/commands/settings.js +1 -1
- 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/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/dist/utils.js +6 -1
- package/package.json +1 -1
|
@@ -25,6 +25,8 @@ export class GridStrategy {
|
|
|
25
25
|
async init(ctx, snapshot) {
|
|
26
26
|
this._config = ctx.config;
|
|
27
27
|
const params = this.params;
|
|
28
|
+
if (params.grids < 2)
|
|
29
|
+
throw new Error("Grid strategy requires at least 2 grid lines");
|
|
28
30
|
// Determine price range
|
|
29
31
|
if (params.range_mode === "auto") {
|
|
30
32
|
const pct = params.range_pct ?? 3;
|
|
@@ -40,12 +42,12 @@ export class GridStrategy {
|
|
|
40
42
|
ctx.state.set("gridUpper", params.upper);
|
|
41
43
|
ctx.state.set("gridLower", params.lower);
|
|
42
44
|
}
|
|
43
|
-
ctx.state.set("
|
|
45
|
+
ctx.state.set("gridLevels", []);
|
|
46
|
+
ctx.state.set("gridInitialized", false);
|
|
44
47
|
}
|
|
45
48
|
async onTick(ctx, snapshot) {
|
|
46
49
|
const params = this.params;
|
|
47
50
|
const actions = [];
|
|
48
|
-
const gridOrders = ctx.state.get("gridOrders");
|
|
49
51
|
const gridUpper = ctx.state.get("gridUpper");
|
|
50
52
|
const gridLower = ctx.state.get("gridLower");
|
|
51
53
|
// First tick: set leverage + place initial grid
|
|
@@ -53,59 +55,62 @@ export class GridStrategy {
|
|
|
53
55
|
if (params.leverage) {
|
|
54
56
|
actions.push({ type: "set_leverage", leverage: params.leverage });
|
|
55
57
|
}
|
|
56
|
-
|
|
58
|
+
const gridActions = this.buildGridOrders(ctx, snapshot.price);
|
|
59
|
+
ctx.state.set("gridInitialized", true);
|
|
60
|
+
return [...actions, ...gridActions];
|
|
61
|
+
}
|
|
62
|
+
// Wait one tick after init/rebalance for orders to settle on exchange
|
|
63
|
+
if (!ctx.state.get("gridInitialized")) {
|
|
64
|
+
ctx.state.set("gridInitialized", true);
|
|
65
|
+
return [];
|
|
57
66
|
}
|
|
58
67
|
if (params.grids < 2)
|
|
59
68
|
throw new Error("Grid requires at least 2 grid lines");
|
|
60
69
|
const step = (gridUpper - gridLower) / (params.grids - 1);
|
|
61
70
|
const sizePerGrid = params.size / params.grids;
|
|
62
|
-
//
|
|
71
|
+
// Price tolerance for matching: half a grid step
|
|
72
|
+
const tolerance = step * 0.5;
|
|
73
|
+
const gridLevels = ctx.state.get("gridLevels");
|
|
74
|
+
// Check for fills by comparing expected grid levels against actual open orders
|
|
63
75
|
try {
|
|
64
76
|
const openOrders = await ctx.adapter.getOpenOrders();
|
|
65
|
-
const
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Verify the order was actually filled (not just cancelled)
|
|
77
|
-
if (filledIds && !filledIds.has(orderId)) {
|
|
78
|
-
ctx.log(` [GRID] Order ${orderId} missing -- likely cancelled, skipping`);
|
|
79
|
-
gridOrders.delete(idx);
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
// Order filled -- place opposite order
|
|
83
|
-
newFills++;
|
|
84
|
-
// Determine new side and price
|
|
85
|
-
const oldPrice = gridLower + step * idx;
|
|
86
|
-
const wasBuy = oldPrice < snapshot.price;
|
|
87
|
-
const newSide = wasBuy ? "sell" : "buy";
|
|
88
|
-
const newPrice = wasBuy ? oldPrice + step : oldPrice - step;
|
|
89
|
-
if (newPrice >= gridLower && newPrice <= gridUpper) {
|
|
90
|
-
actions.push({
|
|
91
|
-
type: "place_order",
|
|
92
|
-
side: newSide,
|
|
93
|
-
price: newPrice.toFixed(2),
|
|
94
|
-
size: String(sizePerGrid),
|
|
95
|
-
orderType: "limit",
|
|
96
|
-
});
|
|
97
|
-
// We'll update gridOrders when the action is executed
|
|
98
|
-
// For now mark this index as pending -- use a sentinel
|
|
99
|
-
gridOrders.set(idx, `pending-${idx}`);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
gridOrders.delete(idx);
|
|
103
|
-
}
|
|
77
|
+
const symbolOrders = openOrders.filter(o => o.symbol.toUpperCase() === ctx.symbol.toUpperCase());
|
|
78
|
+
// For each grid level, check if a matching open order exists (by price proximity)
|
|
79
|
+
const unmatchedLevels = [];
|
|
80
|
+
const matchedLevels = [];
|
|
81
|
+
for (const level of gridLevels) {
|
|
82
|
+
const hasMatch = symbolOrders.some(o => {
|
|
83
|
+
const orderPrice = parseFloat(o.price);
|
|
84
|
+
return Math.abs(orderPrice - level.price) < tolerance && o.side === level.side;
|
|
85
|
+
});
|
|
86
|
+
if (hasMatch) {
|
|
87
|
+
matchedLevels.push(level);
|
|
104
88
|
}
|
|
89
|
+
else {
|
|
90
|
+
unmatchedLevels.push(level);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (unmatchedLevels.length > 0) {
|
|
94
|
+
ctx.log(` [GRID] ${unmatchedLevels.length} fill(s) detected`);
|
|
105
95
|
}
|
|
106
|
-
|
|
107
|
-
|
|
96
|
+
// For each filled level, place the opposite (counter) order
|
|
97
|
+
const newLevels = [...matchedLevels];
|
|
98
|
+
for (const filled of unmatchedLevels) {
|
|
99
|
+
const wasBuy = filled.side === "buy";
|
|
100
|
+
const newSide = wasBuy ? "sell" : "buy";
|
|
101
|
+
const newPrice = wasBuy ? filled.price + step : filled.price - step;
|
|
102
|
+
if (newPrice >= gridLower && newPrice <= gridUpper) {
|
|
103
|
+
actions.push({
|
|
104
|
+
type: "place_order",
|
|
105
|
+
side: newSide,
|
|
106
|
+
price: newPrice.toFixed(2),
|
|
107
|
+
size: String(sizePerGrid),
|
|
108
|
+
orderType: "limit",
|
|
109
|
+
});
|
|
110
|
+
newLevels.push({ price: newPrice, side: newSide });
|
|
111
|
+
}
|
|
108
112
|
}
|
|
113
|
+
ctx.state.set("gridLevels", newLevels);
|
|
109
114
|
}
|
|
110
115
|
catch { /* retry next loop */ }
|
|
111
116
|
// Auto-rebalance if price exits range
|
|
@@ -120,12 +125,12 @@ export class GridStrategy {
|
|
|
120
125
|
ctx.state.set("gridUpper", newUpper);
|
|
121
126
|
ctx.state.set("gridLower", newLower);
|
|
122
127
|
ctx.state.set("lastRebalance", Date.now());
|
|
128
|
+
ctx.state.set("gridInitialized", false);
|
|
123
129
|
const rebalanceCount = (ctx.state.get("rebalanceCount") ?? 0) + 1;
|
|
124
130
|
ctx.state.set("rebalanceCount", rebalanceCount);
|
|
125
131
|
ctx.log(` [GRID] Rebalance #${rebalanceCount}: price $${snapshot.price.toFixed(2)} outside range -> new $${newLower.toFixed(2)} - $${newUpper.toFixed(2)}`);
|
|
126
132
|
// Cancel all and rebuild
|
|
127
133
|
actions.push({ type: "cancel_all" });
|
|
128
|
-
gridOrders.clear();
|
|
129
134
|
return [...actions, ...this.buildGridOrders(ctx, snapshot.price)];
|
|
130
135
|
}
|
|
131
136
|
}
|
|
@@ -144,11 +149,11 @@ export class GridStrategy {
|
|
|
144
149
|
const step = (gridUpper - gridLower) / (params.grids - 1);
|
|
145
150
|
const sizePerGrid = params.size / params.grids;
|
|
146
151
|
const actions = [];
|
|
152
|
+
const gridLevels = [];
|
|
147
153
|
// Cancel existing orders first
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
154
|
+
const existingLevels = ctx.state.get("gridLevels");
|
|
155
|
+
if (existingLevels && existingLevels.length > 0) {
|
|
150
156
|
actions.push({ type: "cancel_all" });
|
|
151
|
-
gridOrders.clear();
|
|
152
157
|
}
|
|
153
158
|
for (let i = 0; i < params.grids; i++) {
|
|
154
159
|
const price = gridLower + step * i;
|
|
@@ -166,9 +171,9 @@ export class GridStrategy {
|
|
|
166
171
|
size: String(sizePerGrid),
|
|
167
172
|
orderType: "limit",
|
|
168
173
|
});
|
|
169
|
-
|
|
170
|
-
gridOrders.set(i, `pending-${i}`);
|
|
174
|
+
gridLevels.push({ price, side });
|
|
171
175
|
}
|
|
176
|
+
ctx.state.set("gridLevels", gridLevels);
|
|
172
177
|
ctx.log(` [GRID] Placing ${params.grids} orders (step: $${step.toFixed(2)}, size: ${sizePerGrid.toFixed(6)})`);
|
|
173
178
|
return actions;
|
|
174
179
|
}
|
package/dist/cli-spec.js
CHANGED
|
@@ -38,7 +38,7 @@ export function getCliSpec(program) {
|
|
|
38
38
|
description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)",
|
|
39
39
|
globalOptions,
|
|
40
40
|
commands,
|
|
41
|
-
exchanges: ["pacifica", "hyperliquid", "lighter"],
|
|
41
|
+
exchanges: ["pacifica", "hyperliquid", "lighter", "aster"],
|
|
42
42
|
envelope: {
|
|
43
43
|
success: "{ ok: true, data: T, meta: { timestamp } }",
|
|
44
44
|
error: "{ ok: false, error: { code, message, retryable?, retryAfterMs? }, meta: { timestamp } }",
|
package/dist/commands/account.js
CHANGED
|
@@ -2,7 +2,7 @@ import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, symbolMa
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { assessRisk } from "../risk.js";
|
|
4
4
|
import { hasPacificaSdk, isDexCapable } from "../exchanges/capabilities.js";
|
|
5
|
-
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
5
|
+
const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
6
6
|
async function fetchExchangeSnapshot(name, getAdapter) {
|
|
7
7
|
try {
|
|
8
8
|
const adapter = await getAdapter(name);
|
|
@@ -26,11 +26,12 @@ function formatDuration(ms) {
|
|
|
26
26
|
}
|
|
27
27
|
async function fetchFundingRatesMap() {
|
|
28
28
|
const rateMap = new Map();
|
|
29
|
-
const [pacAssets, hlAssets, ltDetails, ltFunding] = await Promise.all([
|
|
29
|
+
const [pacAssets, hlAssets, ltDetails, ltFunding, asterPremiums] = await Promise.all([
|
|
30
30
|
fetchPacificaPrices(),
|
|
31
31
|
fetchHyperliquidMeta(),
|
|
32
32
|
fetchLighterOrderBookDetails(),
|
|
33
33
|
fetchLighterFundingRates(),
|
|
34
|
+
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()).catch(() => []),
|
|
34
35
|
]);
|
|
35
36
|
const addRate = (sym, exchange, rate, markPrice) => {
|
|
36
37
|
if (!sym)
|
|
@@ -51,6 +52,13 @@ async function fetchFundingRatesMap() {
|
|
|
51
52
|
const mp = fr.markPrice || ltPriceMap.get(fr.marketId) || 0;
|
|
52
53
|
addRate(sym, "lighter", fr.rate, mp);
|
|
53
54
|
}
|
|
55
|
+
// Aster: premiumIndex
|
|
56
|
+
for (const p of asterPremiums ?? []) {
|
|
57
|
+
const rawSym = String(p.symbol ?? "");
|
|
58
|
+
if (!rawSym.endsWith("USDT"))
|
|
59
|
+
continue;
|
|
60
|
+
addRate(rawSym.replace(/USDT$/, ""), "aster", Number(p.lastFundingRate ?? 0), Number(p.markPrice ?? 0));
|
|
61
|
+
}
|
|
54
62
|
return rateMap;
|
|
55
63
|
}
|
|
56
64
|
function findArbEntryForPair(symbol, longExchange, shortExchange) {
|
|
@@ -454,7 +462,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
454
462
|
// ── Spot-Perp detailed view ──
|
|
455
463
|
if (spotPerpPositions.length > 0) {
|
|
456
464
|
for (const sp of spotPerpPositions) {
|
|
457
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
465
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e === "aster" ? "AST" : e.toUpperCase();
|
|
458
466
|
const spreadStr = sp.currentSpread !== null ? `${sp.currentSpread.toFixed(1)}%` : "-";
|
|
459
467
|
const spotPriceEach = sp.spotAmount > 0 ? sp.spotValueUsd / sp.spotAmount : 0;
|
|
460
468
|
const spotPnl = sp.spotValueUsd - (sp.perpEntryPrice * sp.spotAmount); // spot value vs entry
|
|
@@ -1272,8 +1280,8 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
1272
1280
|
const snapshots = await fetchAllBalances(adapters);
|
|
1273
1281
|
const totalEquity = snapshots.reduce((s, e) => s + e.equity, 0);
|
|
1274
1282
|
const totalAvailable = snapshots.reduce((s, e) => s + e.available, 0);
|
|
1275
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e.toUpperCase();
|
|
1276
|
-
const exChain = (e) => e === "pacifica" ? "Solana" : e === "hyperliquid" ? "Hyperliquid" : e === "lighter" ? "Arbitrum" : "unknown";
|
|
1283
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e === "aster" ? "AST" : e.toUpperCase();
|
|
1284
|
+
const exChain = (e) => e === "pacifica" ? "Solana" : e === "hyperliquid" ? "Hyperliquid" : e === "lighter" ? "Arbitrum" : e === "aster" ? "BNB" : "unknown";
|
|
1277
1285
|
if (opts.check) {
|
|
1278
1286
|
// Show current balance distribution
|
|
1279
1287
|
if (isJson()) {
|
|
@@ -5,6 +5,7 @@ interface FundingSnapshot {
|
|
|
5
5
|
pacRate: number;
|
|
6
6
|
hlRate: number;
|
|
7
7
|
ltRate: number;
|
|
8
|
+
astRate: number;
|
|
8
9
|
spread: number;
|
|
9
10
|
longExch: string;
|
|
10
11
|
shortExch: string;
|
|
@@ -12,6 +13,7 @@ interface FundingSnapshot {
|
|
|
12
13
|
pacMarkPrice: number;
|
|
13
14
|
hlMarkPrice: number;
|
|
14
15
|
ltMarkPrice: number;
|
|
16
|
+
astMarkPrice: number;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Compute the estimated round-trip cost as a percentage of notional.
|
|
@@ -98,28 +98,41 @@ export function isNearSettlement(longExchange, shortExchange, bufferMinutes = 5,
|
|
|
98
98
|
* meaning we're now paying on both sides instead of collecting.
|
|
99
99
|
*/
|
|
100
100
|
export function isSpreadReversed(longExchange, shortExchange, snapshot) {
|
|
101
|
-
const rateFor = (e) => e === "pacifica" ? snapshot.pacRate : e === "hyperliquid" ? snapshot.hlRate : snapshot.ltRate;
|
|
101
|
+
const rateFor = (e) => e === "pacifica" ? snapshot.pacRate : e === "hyperliquid" ? snapshot.hlRate : e === "aster" ? snapshot.astRate : snapshot.ltRate;
|
|
102
102
|
const longHourly = toHourlyRate(rateFor(longExchange), longExchange);
|
|
103
103
|
const shortHourly = toHourlyRate(rateFor(shortExchange), shortExchange);
|
|
104
104
|
// Reversed if the long side rate exceeds the short side rate
|
|
105
105
|
return longHourly > shortHourly;
|
|
106
106
|
}
|
|
107
107
|
async function fetchFundingSpreads() {
|
|
108
|
-
const [pacRes, hlRes, ltDetailsRes, ltFundingRes] = await Promise.all([
|
|
108
|
+
const [pacRes, hlRes, ltDetailsRes, ltFundingRes, astPremiums] = await Promise.all([
|
|
109
109
|
fetchPacificaPricesRaw(),
|
|
110
110
|
fetchHyperliquidMetaRaw(),
|
|
111
111
|
fetchLighterOrderBookDetailsRaw(),
|
|
112
112
|
fetchLighterFundingRatesRaw(),
|
|
113
|
+
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()).catch(() => []),
|
|
113
114
|
]);
|
|
114
115
|
const { rates: pacRates, prices: pacPrices } = parsePacificaRaw(pacRes);
|
|
115
116
|
const { rates: hlRates, prices: hlPrices } = parseHyperliquidMetaRaw(hlRes);
|
|
116
117
|
const { rates: ltRates, prices: ltPrices } = parseLighterRaw(ltDetailsRes, ltFundingRes);
|
|
118
|
+
// Aster: parse premiumIndex
|
|
119
|
+
const astRates = new Map();
|
|
120
|
+
const astPrices = new Map();
|
|
121
|
+
for (const p of astPremiums ?? []) {
|
|
122
|
+
const rawSym = String(p.symbol ?? "");
|
|
123
|
+
if (!rawSym.endsWith("USDT"))
|
|
124
|
+
continue;
|
|
125
|
+
const sym = rawSym.replace(/USDT$/, "");
|
|
126
|
+
astRates.set(sym, Number(p.lastFundingRate ?? 0));
|
|
127
|
+
astPrices.set(sym, Number(p.markPrice ?? 0));
|
|
128
|
+
}
|
|
117
129
|
const snapshots = [];
|
|
118
|
-
const allSymbols = new Set([...pacRates.keys(), ...hlRates.keys(), ...ltRates.keys()]);
|
|
130
|
+
const allSymbols = new Set([...pacRates.keys(), ...hlRates.keys(), ...ltRates.keys(), ...astRates.keys()]);
|
|
119
131
|
for (const sym of allSymbols) {
|
|
120
132
|
const pac = pacRates.get(sym);
|
|
121
133
|
const hl = hlRates.get(sym);
|
|
122
134
|
const lt = ltRates.get(sym);
|
|
135
|
+
const ast = astRates.get(sym);
|
|
123
136
|
// Need at least 2 exchanges
|
|
124
137
|
const available = [];
|
|
125
138
|
if (pac !== undefined)
|
|
@@ -128,6 +141,8 @@ async function fetchFundingSpreads() {
|
|
|
128
141
|
available.push({ exchange: "hyperliquid", rate: hl });
|
|
129
142
|
if (lt !== undefined)
|
|
130
143
|
available.push({ exchange: "lighter", rate: lt });
|
|
144
|
+
if (ast !== undefined)
|
|
145
|
+
available.push({ exchange: "aster", rate: ast });
|
|
131
146
|
if (available.length < 2)
|
|
132
147
|
continue;
|
|
133
148
|
const norm = (r) => toHourlyRate(r.rate, r.exchange);
|
|
@@ -135,13 +150,14 @@ async function fetchFundingSpreads() {
|
|
|
135
150
|
const lowest = available[0];
|
|
136
151
|
const highest = available[available.length - 1];
|
|
137
152
|
const spread = computeAnnualSpread(highest.rate, highest.exchange, lowest.rate, lowest.exchange);
|
|
138
|
-
// Use best available mark price (prefer HL as most liquid, then PAC, then LT)
|
|
139
|
-
const markPrice = hlPrices.get(sym) ?? pacPrices.get(sym) ?? ltPrices.get(sym) ?? 0;
|
|
153
|
+
// Use best available mark price (prefer HL as most liquid, then PAC, then LT, then AST)
|
|
154
|
+
const markPrice = hlPrices.get(sym) ?? pacPrices.get(sym) ?? ltPrices.get(sym) ?? astPrices.get(sym) ?? 0;
|
|
140
155
|
snapshots.push({
|
|
141
156
|
symbol: sym,
|
|
142
157
|
pacRate: pac ?? 0,
|
|
143
158
|
hlRate: hl ?? 0,
|
|
144
159
|
ltRate: lt ?? 0,
|
|
160
|
+
astRate: ast ?? 0,
|
|
145
161
|
spread,
|
|
146
162
|
longExch: lowest.exchange, // long where funding is lowest
|
|
147
163
|
shortExch: highest.exchange, // short where funding is highest
|
|
@@ -149,6 +165,7 @@ async function fetchFundingSpreads() {
|
|
|
149
165
|
pacMarkPrice: pacPrices.get(sym) ?? 0,
|
|
150
166
|
hlMarkPrice: hlPrices.get(sym) ?? 0,
|
|
151
167
|
ltMarkPrice: ltPrices.get(sym) ?? 0,
|
|
168
|
+
astMarkPrice: astPrices.get(sym) ?? 0,
|
|
152
169
|
});
|
|
153
170
|
}
|
|
154
171
|
return snapshots.sort((a, b) => Math.abs(b.spread) - Math.abs(a.spread));
|
|
@@ -541,7 +558,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
541
558
|
// Log next settlement times and funding estimation
|
|
542
559
|
if (!isJson() && settleStrategy !== "off") {
|
|
543
560
|
const nowDate = new Date();
|
|
544
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
561
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e === "aster" ? "AST" : e.toUpperCase();
|
|
545
562
|
const nextHL = getNextSettlement("hyperliquid", nowDate);
|
|
546
563
|
const nextPAC = getNextSettlement("pacifica", nowDate);
|
|
547
564
|
const nextLT = getNextSettlement("lighter", nowDate);
|
|
@@ -553,7 +570,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
553
570
|
const fSnap = filtered.find(s => s.symbol === fPos.symbol);
|
|
554
571
|
if (!fSnap)
|
|
555
572
|
continue;
|
|
556
|
-
const fRateFor = (e) => e === "pacifica" ? fSnap.pacRate : e === "hyperliquid" ? fSnap.hlRate : fSnap.ltRate;
|
|
573
|
+
const fRateFor = (e) => e === "pacifica" ? fSnap.pacRate : e === "hyperliquid" ? fSnap.hlRate : e === "aster" ? fSnap.astRate : fSnap.ltRate;
|
|
557
574
|
const fHlHourly = toHourlyRate(fRateFor("hyperliquid"), "hyperliquid");
|
|
558
575
|
const fPacHourly = toHourlyRate(fRateFor("pacifica"), "pacifica");
|
|
559
576
|
const fNotional = fPos.size * fSnap.markPrice;
|
|
@@ -679,7 +696,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
679
696
|
const effectiveNetSpread = netSpread * settleBoostMultiplier;
|
|
680
697
|
if (effectiveNetSpread < minSpread)
|
|
681
698
|
continue;
|
|
682
|
-
const rateForExch = (e) => e === "pacifica" ? snap.pacRate : e === "hyperliquid" ? snap.hlRate : snap.ltRate;
|
|
699
|
+
const rateForExch = (e) => e === "pacifica" ? snap.pacRate : e === "hyperliquid" ? snap.hlRate : e === "aster" ? snap.astRate : snap.ltRate;
|
|
683
700
|
console.log(chalk.green(` ${now} ENTER ${snap.symbol} — gross ${grossSpread.toFixed(1)}% net ${netSpread.toFixed(1)}%` +
|
|
684
701
|
` | Long ${longExchange} (${(rateForExch(longExchange) * 100).toFixed(4)}%)` +
|
|
685
702
|
` | Short ${shortExchange} (${(rateForExch(shortExchange) * 100).toFixed(4)}%)`));
|
|
@@ -885,7 +902,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
885
902
|
const elapsedHours = (nowMs - pos.lastCheckTime) / (1000 * 60 * 60);
|
|
886
903
|
const notional = pos.size * current.markPrice;
|
|
887
904
|
// Estimate funding collected: normalize all rates to hourly before comparing
|
|
888
|
-
const rateFor = (e) => e === "pacifica" ? current.pacRate : e === "hyperliquid" ? current.hlRate : current.ltRate;
|
|
905
|
+
const rateFor = (e) => e === "pacifica" ? current.pacRate : e === "hyperliquid" ? current.hlRate : e === "aster" ? current.astRate : current.ltRate;
|
|
889
906
|
const longHourly = toHourlyRate(rateFor(pos.longExchange), pos.longExchange);
|
|
890
907
|
const shortHourly = toHourlyRate(rateFor(pos.shortExchange), pos.shortExchange);
|
|
891
908
|
// Income = short gets paid positive funding, long pays; net = (shortRate - longRate) * notional * hours
|
|
@@ -1074,7 +1091,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1074
1091
|
console.log(chalk.gray(` Liquidity check: ON`));
|
|
1075
1092
|
console.log(chalk.gray(` Press Ctrl+C to stop\n`));
|
|
1076
1093
|
}
|
|
1077
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
1094
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e === "aster" ? "AST" : e.toUpperCase();
|
|
1078
1095
|
while (true) {
|
|
1079
1096
|
cycle++;
|
|
1080
1097
|
const ts = new Date().toLocaleTimeString();
|
|
@@ -1191,7 +1208,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1191
1208
|
console.log(chalk.gray(` No spot+perp opportunities above ${minSpread}%\n`));
|
|
1192
1209
|
}
|
|
1193
1210
|
else {
|
|
1194
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
1211
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e === "aster" ? "AST" : e.toUpperCase();
|
|
1195
1212
|
const rows = spotSpreads.slice(0, parseInt(opts.top ?? "30")).map(s => {
|
|
1196
1213
|
const spotEx = s.spotExchanges[0] || "lighter";
|
|
1197
1214
|
const rtCost = computeRoundTripCostPct(spotEx, s.perpExchange);
|
|
@@ -1235,8 +1252,9 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1235
1252
|
const rtCost = computeRoundTripCostPct(s.longExch, s.shortExch);
|
|
1236
1253
|
const net = computeNetSpread(grossSpread, holdDays, rtCost, bridgeCostUsd, sizeUsd);
|
|
1237
1254
|
// Compute price gap between long and short exchanges
|
|
1238
|
-
const
|
|
1239
|
-
const
|
|
1255
|
+
const pFor = (ex) => ex === "pacifica" ? s.pacMarkPrice : ex === "hyperliquid" ? s.hlMarkPrice : ex === "lighter" ? s.ltMarkPrice : s.astMarkPrice;
|
|
1256
|
+
const longPrice = pFor(s.longExch);
|
|
1257
|
+
const shortPrice = pFor(s.shortExch);
|
|
1240
1258
|
const mid = (longPrice + shortPrice) / 2;
|
|
1241
1259
|
const priceGapPct = mid > 0 ? Math.abs(longPrice - shortPrice) / mid * 100 : 0;
|
|
1242
1260
|
return { mode: "perp-perp", symbol: s.symbol, longExch: s.longExch, shortExch: s.shortExch, markPrice: s.markPrice, grossSpread, netSpread: net, estFeesPct: rtCost, priceGapPct: Math.round(priceGapPct * 10000) / 10000 };
|
|
@@ -1264,7 +1282,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1264
1282
|
console.log(chalk.gray(` No perp-perp spreads above ${minSpread}%\n`));
|
|
1265
1283
|
return;
|
|
1266
1284
|
}
|
|
1267
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
1285
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e === "aster" ? "AST" : e.toUpperCase();
|
|
1268
1286
|
const rows = filtered.map(s => {
|
|
1269
1287
|
const direction = `${exAbbr(s.shortExch)}>${exAbbr(s.longExch)}`;
|
|
1270
1288
|
const grossSpread = Math.abs(s.spread);
|
|
@@ -1272,8 +1290,9 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1272
1290
|
const netSpread = computeNetSpread(grossSpread, holdDays, rtCost, bridgeCostUsd, sizeUsd);
|
|
1273
1291
|
const grossColor = grossSpread >= 50 ? chalk.green.bold : grossSpread >= 30 ? chalk.green : chalk.yellow;
|
|
1274
1292
|
const netColor = netSpread >= 20 ? chalk.green : netSpread >= 0 ? chalk.yellow : chalk.red;
|
|
1275
|
-
const
|
|
1276
|
-
const
|
|
1293
|
+
const priceFor = (ex) => ex === "pacifica" ? s.pacMarkPrice : ex === "hyperliquid" ? s.hlMarkPrice : ex === "lighter" ? s.ltMarkPrice : s.astMarkPrice;
|
|
1294
|
+
const longPrice = priceFor(s.longExch);
|
|
1295
|
+
const shortPrice = priceFor(s.shortExch);
|
|
1277
1296
|
const mid = (longPrice + shortPrice) / 2;
|
|
1278
1297
|
const priceGapPct = mid > 0 ? Math.abs(longPrice - shortPrice) / mid * 100 : 0;
|
|
1279
1298
|
const gapColor = priceGapPct >= 0.5 ? chalk.red : priceGapPct >= 0.1 ? chalk.yellow : chalk.gray;
|
|
@@ -1284,6 +1303,8 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1284
1303
|
rates.push(`HL:${(s.hlRate * 100).toFixed(4)}%`);
|
|
1285
1304
|
if (s.ltRate)
|
|
1286
1305
|
rates.push(`LT:${(s.ltRate * 100).toFixed(4)}%`);
|
|
1306
|
+
if (s.astRate)
|
|
1307
|
+
rates.push(`AST:${(s.astRate * 100).toFixed(4)}%`);
|
|
1287
1308
|
return [
|
|
1288
1309
|
chalk.white.bold(s.symbol),
|
|
1289
1310
|
grossColor(`${grossSpread.toFixed(1)}%`),
|
package/dist/commands/arb.js
CHANGED
|
@@ -24,6 +24,23 @@ async function fetchHyperliquidRates() {
|
|
|
24
24
|
markPrice: a.markPx,
|
|
25
25
|
}));
|
|
26
26
|
}
|
|
27
|
+
async function fetchAsterRates() {
|
|
28
|
+
try {
|
|
29
|
+
const premiums = await fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json());
|
|
30
|
+
return (premiums ?? [])
|
|
31
|
+
.filter(p => String(p.symbol ?? "").endsWith("USDT"))
|
|
32
|
+
.map(p => ({
|
|
33
|
+
exchange: "aster",
|
|
34
|
+
symbol: String(p.symbol ?? "").replace(/USDT$/, ""),
|
|
35
|
+
fundingRate: Number(p.lastFundingRate ?? 0),
|
|
36
|
+
markPrice: Number(p.markPrice ?? 0),
|
|
37
|
+
nextFunding: Number(p.nextFundingTime ?? 0),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
27
44
|
async function fetchLighterRates() {
|
|
28
45
|
try {
|
|
29
46
|
const [details, funding] = await Promise.all([
|
|
@@ -222,18 +239,20 @@ function printSnapshotTable(snapshot) {
|
|
|
222
239
|
const pacRate = s.rates.find(r => r.exchange === "pacifica");
|
|
223
240
|
const hlRate = s.rates.find(r => r.exchange === "hyperliquid");
|
|
224
241
|
const ltRate = s.rates.find(r => r.exchange === "lighter");
|
|
242
|
+
const astRate = s.rates.find(r => r.exchange === "aster");
|
|
225
243
|
const spreadColor = s.maxSpreadAnnual >= 30 ? chalk.green : s.maxSpreadAnnual >= 10 ? chalk.yellow : chalk.white;
|
|
226
|
-
const bestRate = hlRate ?? pacRate ?? ltRate;
|
|
244
|
+
const bestRate = hlRate ?? pacRate ?? ltRate ?? astRate;
|
|
227
245
|
const trend = trendMap.get(s.symbol) ?? chalk.gray("-");
|
|
228
246
|
return [
|
|
229
247
|
chalk.white.bold(s.symbol), pacRate ? formatPercent(pacRate.fundingRate) : chalk.gray("-"),
|
|
230
248
|
hlRate ? formatPercent(hlRate.fundingRate) : chalk.gray("-"), ltRate ? formatPercent(ltRate.fundingRate) : chalk.gray("-"),
|
|
249
|
+
astRate ? formatPercent(astRate.fundingRate) : chalk.gray("-"),
|
|
231
250
|
spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`), trend,
|
|
232
251
|
s.maxSpreadAnnual >= 5 ? `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}` : chalk.gray("-"),
|
|
233
252
|
formatAvgRate(bestRate?.historicalAvg?.avg8h), formatAvgRate(bestRate?.historicalAvg?.avg24h), formatAvgRate(bestRate?.historicalAvg?.avg7d),
|
|
234
253
|
];
|
|
235
254
|
});
|
|
236
|
-
console.log(makeTable(["Symbol", "Pacifica", "Hyperliquid", "Lighter", "Spread", "24h", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
|
|
255
|
+
console.log(makeTable(["Symbol", "Pacifica", "Hyperliquid", "Lighter", "Aster", "Spread", "24h", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
|
|
237
256
|
console.log(chalk.gray(`\n ${snapshot.symbols.length} symbols compared across exchanges.\n`));
|
|
238
257
|
}
|
|
239
258
|
function printFundingExchangeStatus(snapshot) {
|
|
@@ -244,7 +263,7 @@ function printFundingExchangeStatus(snapshot) {
|
|
|
244
263
|
console.log(chalk.gray(` Exchange status: ${statuses.join(" ")}\n`));
|
|
245
264
|
}
|
|
246
265
|
function printDetailedComparison(comparison) {
|
|
247
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
|
|
266
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e === "aster" ? "AST" : e.toUpperCase();
|
|
248
267
|
console.log(chalk.cyan.bold(` ${comparison.symbol} Funding Rate Comparison\n`));
|
|
249
268
|
console.log(` Mark Price: $${formatUsd(comparison.bestMarkPrice)}\n`);
|
|
250
269
|
for (const r of comparison.rates) {
|
|
@@ -301,10 +320,10 @@ export async function handleRates(isJson, opts) {
|
|
|
301
320
|
export async function handleBasisScan(isJson, opts) {
|
|
302
321
|
if (!isJson())
|
|
303
322
|
console.log(chalk.cyan(" Fetching prices for basis calculation...\n"));
|
|
304
|
-
const [pacRates, hlRates, ltRates] = await Promise.all([fetchPacificaRates(), fetchHyperliquidRates(), fetchLighterRates()]);
|
|
323
|
+
const [pacRates, hlRates, ltRates, astRates] = await Promise.all([fetchPacificaRates(), fetchHyperliquidRates(), fetchLighterRates(), fetchAsterRates()]);
|
|
305
324
|
const exchangePrices = new Map();
|
|
306
325
|
const filterSymbol = opts.symbol?.toUpperCase();
|
|
307
|
-
for (const r of [...pacRates, ...hlRates, ...ltRates]) {
|
|
326
|
+
for (const r of [...pacRates, ...hlRates, ...ltRates, ...astRates]) {
|
|
308
327
|
if (r.markPrice <= 0)
|
|
309
328
|
continue;
|
|
310
329
|
if (filterSymbol && r.symbol.toUpperCase() !== filterSymbol)
|
|
@@ -508,7 +527,7 @@ export async function handleFundingPositions(isJson, getAdapterForExchange, opts
|
|
|
508
527
|
const tA = results.reduce((s, r) => s + r.actualNet24h, 0);
|
|
509
528
|
if (isJson())
|
|
510
529
|
return printJson(jsonOk({ positions: results.map(r => ({ exchange: r.exchange, symbol: r.symbol, side: r.side, size: r.size, notionalUsd: +r.notionalUsd.toFixed(2), fundingRate: r.fundingRate, annualPct: +r.annualPct.toFixed(2), predicted: { hourly: +r.hourlyPayment.toFixed(6), daily: +r.dailyPayment.toFixed(4) }, actual24h: { received: +r.actualReceived24h.toFixed(6), paid: +r.actualPaid24h.toFixed(6), net: +r.actualNet24h.toFixed(6) }, unrealizedPnl: r.unrealizedPnl })), totals: { predicted: { hourly: +tH.toFixed(6), daily: +tD.toFixed(4) }, actual24h: { net: +tA.toFixed(6) }, notionalUsd: +tN.toFixed(2) }, errors: Object.keys(exchangeErrors).length > 0 ? exchangeErrors : undefined }));
|
|
511
|
-
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e.toUpperCase();
|
|
530
|
+
const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e === "aster" ? "AST" : e.toUpperCase();
|
|
512
531
|
console.log(chalk.cyan.bold(" Position Funding Impact\n"));
|
|
513
532
|
const rows = results.map(r => { const sc = r.side === "long" ? chalk.green : chalk.red; const rc = r.fundingRate > 0 ? chalk.red : r.fundingRate < 0 ? chalk.green : chalk.white; const as = r.actualNet24h !== 0 ? (r.actualNet24h > 0 ? chalk.green : chalk.red)(`${r.actualNet24h > 0 ? "+" : ""}$${r.actualNet24h.toFixed(4)}`) : chalk.gray("-"); return [chalk.white.bold(exAbbr(r.exchange)), chalk.white.bold(r.symbol), sc(r.side.toUpperCase()), `$${formatUsd(r.notionalUsd)}`, rc(formatPercent(r.fundingRate)), rc(`${r.annualPct.toFixed(1)}%`), as, formatPnl(r.unrealizedPnl)]; });
|
|
514
533
|
console.log(makeTable(["Ex", "Symbol", "Side", "Notional", "Rate(now)", "Annual(now)", "Actual 24h", "uPnL"], rows));
|
package/dist/commands/bot.js
CHANGED
|
@@ -140,7 +140,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
140
140
|
.option("--close-spread <pct>", "Close when spread drops below (%)", "5")
|
|
141
141
|
.option("--size <usd>", "Position size per leg ($)", "50")
|
|
142
142
|
.option("--max-positions <n>", "Max simultaneous positions", "3")
|
|
143
|
-
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter")
|
|
143
|
+
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter,aster")
|
|
144
144
|
.option("--interval <sec>", "Check interval in seconds", "60")
|
|
145
145
|
.option("--max-drawdown <usd>", "Stop if drawdown exceeds ($)", "200")
|
|
146
146
|
.option("--background", "Run in background (tmux)")
|
|
@@ -446,8 +446,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
446
446
|
if (sym === "ALL" && symbolRequiredStrategies.includes(strategyName)) {
|
|
447
447
|
console.error(chalk.red(`\n Error: The '${strategyName}' strategy requires a <symbol> argument.`));
|
|
448
448
|
console.error(chalk.gray(` Usage: perp bot run ${strategyName} <symbol>\n`));
|
|
449
|
-
process.
|
|
450
|
-
return;
|
|
449
|
+
return process.exit(1);
|
|
451
450
|
}
|
|
452
451
|
// Load or build config
|
|
453
452
|
let config;
|
|
@@ -505,7 +504,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
505
504
|
for (const s of available)
|
|
506
505
|
console.error(chalk.gray(` - ${s}`));
|
|
507
506
|
console.error();
|
|
508
|
-
return;
|
|
507
|
+
return process.exit(1);
|
|
509
508
|
}
|
|
510
509
|
const adapter = await getAdapter();
|
|
511
510
|
config.exchange = adapter.name;
|
|
@@ -516,7 +515,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
516
515
|
const multiExchangeStrategies = ["funding-auto", "funding-arb", "funding-arb-v2", "basis-arb", "hedge-agent"];
|
|
517
516
|
if (multiExchangeStrategies.includes(strategyName) && getAdapterFor) {
|
|
518
517
|
extraAdapters = new Map();
|
|
519
|
-
for (const ex of ["pacifica", "hyperliquid", "lighter"]) {
|
|
518
|
+
for (const ex of ["pacifica", "hyperliquid", "lighter", "aster"]) {
|
|
520
519
|
if (ex === adapter.name)
|
|
521
520
|
continue;
|
|
522
521
|
try {
|
|
@@ -684,7 +683,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
|
|
|
684
683
|
.option("--interval <sec>", "Check interval in seconds", "60")
|
|
685
684
|
.option("--auto-execute", "Actually place trades (default: monitor only)")
|
|
686
685
|
.option("--max-positions <n>", "Max simultaneous positions", "3")
|
|
687
|
-
.option("--exchanges <list>", "Comma-separated exchanges to use", "pacifica,hyperliquid,lighter")
|
|
686
|
+
.option("--exchanges <list>", "Comma-separated exchanges to use", "pacifica,hyperliquid,lighter,aster")
|
|
688
687
|
.option("--auto-rebalance", "Trigger rebalance alerts when exchange balance is low")
|
|
689
688
|
.option("--rebalance-threshold <usd>", "Rebalance when available < this ($)", "100")
|
|
690
689
|
.option("--max-drawdown <usd>", "Close all positions if total uPnL exceeds this loss ($)")
|
|
@@ -50,7 +50,7 @@ export function registerDashboardCommands(program, getAdapterForExchange, isJson
|
|
|
50
50
|
.description("Launch live web dashboard for real-time portfolio monitoring")
|
|
51
51
|
.option("-p, --port <port>", "Port to serve dashboard on (auto-finds free port)", "3456")
|
|
52
52
|
.option("--interval <ms>", "Poll interval in milliseconds", "5000")
|
|
53
|
-
.option("--exchanges <list>", "Comma-separated exchanges to monitor", "pacifica,hyperliquid,lighter")
|
|
53
|
+
.option("--exchanges <list>", "Comma-separated exchanges to monitor", "pacifica,hyperliquid,lighter,aster")
|
|
54
54
|
.option("--dex <list>", "Comma-separated HIP-3 dexes to monitor (e.g. xyz,flx,hyna)")
|
|
55
55
|
.option("--no-auto-dex", "Disable auto-detection of HIP-3 dexes with active positions")
|
|
56
56
|
.action(async (opts) => {
|
package/dist/commands/history.js
CHANGED
|
@@ -4,7 +4,7 @@ import { readExecutionLog, getExecutionStats, pruneExecutionLog } from "../execu
|
|
|
4
4
|
import { loadArbState } from "../arb/index.js";
|
|
5
5
|
import { readPositionHistory, getPositionStats } from "../position-history.js";
|
|
6
6
|
import { saveEquitySnapshot, readEquityHistory, computePnlMetrics, computeDailyPnl, aggregateWeekly, } from "../equity-tracker.js";
|
|
7
|
-
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
7
|
+
const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
8
8
|
function parseSince(since) {
|
|
9
9
|
if (!since)
|
|
10
10
|
return undefined;
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -5,10 +5,10 @@ declare function loadEnvFile(): Record<string, string>;
|
|
|
5
5
|
declare function setEnvVar(key: string, value: string): void;
|
|
6
6
|
export declare const EXCHANGE_ENV_MAP: Record<string, {
|
|
7
7
|
envKey: string;
|
|
8
|
-
chain: "solana" | "evm";
|
|
8
|
+
chain: "solana" | "evm" | "apikey";
|
|
9
9
|
label: string;
|
|
10
10
|
}>;
|
|
11
|
-
export declare function validateKey(chain: "solana" | "evm", key: string): Promise<{
|
|
11
|
+
export declare function validateKey(chain: "solana" | "evm" | "apikey", key: string): Promise<{
|
|
12
12
|
valid: boolean;
|
|
13
13
|
address: string;
|
|
14
14
|
}>;
|
package/dist/commands/init.js
CHANGED
|
@@ -70,6 +70,7 @@ export const EXCHANGE_ENV_MAP = {
|
|
|
70
70
|
pacifica: { envKey: "PACIFICA_PRIVATE_KEY", chain: "solana", label: "Pacifica (Solana)" },
|
|
71
71
|
hyperliquid: { envKey: "HL_PRIVATE_KEY", chain: "evm", label: "Hyperliquid (EVM)" },
|
|
72
72
|
lighter: { envKey: "LIGHTER_PRIVATE_KEY", chain: "evm", label: "Lighter (EVM)" },
|
|
73
|
+
aster: { envKey: "ASTER_API_KEY", chain: "apikey", label: "Aster (BNB Chain)" },
|
|
73
74
|
};
|
|
74
75
|
// ── Validate keys ─────────────────────────────────────────────
|
|
75
76
|
export async function validateKey(chain, key) {
|
|
@@ -91,6 +92,10 @@ export async function validateKey(chain, key) {
|
|
|
91
92
|
return { valid: false, address: "" };
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
else if (chain === "apikey") {
|
|
96
|
+
// API key validation: just check it's a non-empty hex-like string
|
|
97
|
+
return { valid: key.length >= 16, address: key.slice(0, 8) + "..." + key.slice(-4) };
|
|
98
|
+
}
|
|
94
99
|
else {
|
|
95
100
|
try {
|
|
96
101
|
const { ethers } = await import("ethers");
|
package/dist/commands/market.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { makeTable, formatUsd, formatPercent, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { hasPacificaSdk, isDexCapable } from "../exchanges/capabilities.js";
|
|
4
|
-
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
4
|
+
const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
5
5
|
export function registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange) {
|
|
6
6
|
const market = program.command("market").description("Market data commands");
|
|
7
7
|
market
|