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
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation
|
|
19
19
|
* Base: https://fapi.asterdex.com
|
|
20
20
|
*/
|
|
21
|
-
import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangePublicTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
|
|
21
|
+
import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangePublicTrade, ExchangeFundingPayment, ExchangeKline, FundingHistoryRow } from "./interface.js";
|
|
22
22
|
import type { ResolvedSlippage } from "./slippage.js";
|
|
23
23
|
import type { EvmSigner } from "../signer/interface.js";
|
|
24
24
|
import type { AgentMeta } from "../settings.js";
|
|
@@ -68,6 +68,11 @@ type ResolvedSigner = {
|
|
|
68
68
|
* that the read refuses (EXCHANGE_ERROR) rather than returning a partial history.
|
|
69
69
|
*/
|
|
70
70
|
export declare const ASTER_INCOME_MAX_PAGES = 60;
|
|
71
|
+
/**
|
|
72
|
+
* Kline intervals verified live 2026-10-19 (/fapi/v3/klines BTCUSDT: each answers bars of that
|
|
73
|
+
* length; "7m" / "2d" → -1120 "Invalid interval."). Anything else is refused before a request.
|
|
74
|
+
*/
|
|
75
|
+
export declare const ASTER_KLINE_INTERVALS: ReadonlySet<string>;
|
|
71
76
|
export declare class AsterAdapter implements ExchangeAdapter {
|
|
72
77
|
readonly name = "aster";
|
|
73
78
|
readonly chain = "bnb";
|
|
@@ -89,6 +94,8 @@ export declare class AsterAdapter implements ExchangeAdapter {
|
|
|
89
94
|
/** API symbol → price filters; null = listed but PRICE_FILTER / PERCENT_PRICE unreadable. */
|
|
90
95
|
private _priceFilters;
|
|
91
96
|
private _priceFiltersLoadedAt;
|
|
97
|
+
/** The in-flight exchangeInfo load shared by concurrent first uses (_ensureDirectory). */
|
|
98
|
+
private _directoryLoad;
|
|
92
99
|
private _accountCache;
|
|
93
100
|
private _positionsCache;
|
|
94
101
|
private _ordersCache;
|
|
@@ -135,7 +142,12 @@ export declare class AsterAdapter implements ExchangeAdapter {
|
|
|
135
142
|
* agent. Throws exactly like signed calls when no signer is usable.
|
|
136
143
|
*/
|
|
137
144
|
activeAgentMeta(): AgentMeta | null;
|
|
138
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* CLI symbol → Aster API symbol through the LOADED market directory (exchangeInfo): the exact
|
|
147
|
+
* venue symbol first (BTCUSDT, BTCUSD1, BTCU — a non-USDT market's CLI symbol is its venue
|
|
148
|
+
* symbol, asterCliSymbol), then the USDT market of a base (ETH → ETHUSDT, USD1 → USD1USDT),
|
|
149
|
+
* else SYMBOL_NOT_FOUND — never a guessed suffix. Callers load the directory first (_apiSymbol).
|
|
150
|
+
*/
|
|
139
151
|
private _toApi;
|
|
140
152
|
/** Aster API symbol → CLI symbol (ETHUSDT → ETH) */
|
|
141
153
|
private _fromApi;
|
|
@@ -159,6 +171,18 @@ export declare class AsterAdapter implements ExchangeAdapter {
|
|
|
159
171
|
/** Parse PRICE_FILTER + PERCENT_PRICE for every exchangeInfo symbol into the cache. */
|
|
160
172
|
private _ingestPriceFilters;
|
|
161
173
|
/** PRICE_FILTER / PERCENT_PRICE of an API symbol (exchangeInfo, cached CACHE_TTL). */
|
|
174
|
+
/** exchangeInfo symbols (the market directory) loaded / refreshed per CACHE_TTL. */
|
|
175
|
+
/**
|
|
176
|
+
* exchangeInfo symbols (the market directory), refreshed per CACHE_TTL. Concurrent first uses
|
|
177
|
+
* share ONE in-flight request; a failed load is not cached (the next call retries).
|
|
178
|
+
*/
|
|
179
|
+
private _ensureDirectory;
|
|
180
|
+
/**
|
|
181
|
+
* CLI symbol → Aster API symbol, resolved through the market directory (_toApi), loaded on first
|
|
182
|
+
* use and cached CACHE_TTL — so the answer never depends on what was read before (a cold
|
|
183
|
+
* adapter resolves BTCU, USD1 → USD1USDT, an unknown symbol is SYMBOL_NOT_FOUND).
|
|
184
|
+
*/
|
|
185
|
+
private _apiSymbol;
|
|
162
186
|
private _symbolPriceFilters;
|
|
163
187
|
/**
|
|
164
188
|
* Declared protection-price reference: the venue mark price from
|
|
@@ -191,11 +215,7 @@ export declare class AsterAdapter implements ExchangeAdapter {
|
|
|
191
215
|
asks: [string, string][];
|
|
192
216
|
}>;
|
|
193
217
|
getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
|
|
194
|
-
getFundingHistory(symbol: string, limit?: number): Promise<
|
|
195
|
-
time: number;
|
|
196
|
-
rate: string;
|
|
197
|
-
price: string | null;
|
|
198
|
-
}[]>;
|
|
218
|
+
getFundingHistory(symbol: string, limit?: number): Promise<FundingHistoryRow[]>;
|
|
199
219
|
/**
|
|
200
220
|
* Candles over [startTime, endTime]. GET /fapi/v3/klines returns at most 1500 bars per request
|
|
201
221
|
* (1501 → -1130), so the range is read in pages that start after the last bar returned — a long
|
package/dist/exchanges/aster.js
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* Base: https://fapi.asterdex.com
|
|
20
20
|
*/
|
|
21
21
|
import { nonEmptyOrListed } from "./unknown-symbol.js";
|
|
22
|
+
import { asterCliSymbol, isAsterListedPerpetual } from "../ws/aster-market.js";
|
|
23
|
+
import { decimalsOf } from "./venue-types.js";
|
|
22
24
|
import { accountCacheEpoch } from "../cache.js";
|
|
23
25
|
import { isExpired } from "../agent-wallet/expiry.js";
|
|
24
26
|
import { classifyError, DEFINITE_ORDER_FAILURE_CODES, isTransportFailure, orderNotSent, orderOutcomeUnknown, PerpError, sendFailure } from "../errors.js";
|
|
@@ -174,6 +176,11 @@ export const ASTER_INCOME_MAX_PAGES = 60;
|
|
|
174
176
|
const ASTER_KLINE_PAGE = 1500;
|
|
175
177
|
/** Page cap for one range (150 000 bars). */
|
|
176
178
|
const ASTER_KLINE_MAX_PAGES = 100;
|
|
179
|
+
/**
|
|
180
|
+
* Kline intervals verified live 2026-10-19 (/fapi/v3/klines BTCUSDT: each answers bars of that
|
|
181
|
+
* length; "7m" / "2d" → -1120 "Invalid interval."). Anything else is refused before a request.
|
|
182
|
+
*/
|
|
183
|
+
export const ASTER_KLINE_INTERVALS = new Set(["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"]);
|
|
177
184
|
export class AsterAdapter {
|
|
178
185
|
name = "aster";
|
|
179
186
|
chain = "bnb";
|
|
@@ -197,6 +204,8 @@ export class AsterAdapter {
|
|
|
197
204
|
/** API symbol → price filters; null = listed but PRICE_FILTER / PERCENT_PRICE unreadable. */
|
|
198
205
|
_priceFilters = new Map();
|
|
199
206
|
_priceFiltersLoadedAt = 0;
|
|
207
|
+
/** The in-flight exchangeInfo load shared by concurrent first uses (_ensureDirectory). */
|
|
208
|
+
_directoryLoad = null;
|
|
200
209
|
_accountCache = null;
|
|
201
210
|
_positionsCache = null;
|
|
202
211
|
_ordersCache = null;
|
|
@@ -327,12 +336,23 @@ export class AsterAdapter {
|
|
|
327
336
|
return this._agentMeta;
|
|
328
337
|
}
|
|
329
338
|
// ── Symbol helpers ──
|
|
330
|
-
/**
|
|
339
|
+
/**
|
|
340
|
+
* CLI symbol → Aster API symbol through the LOADED market directory (exchangeInfo): the exact
|
|
341
|
+
* venue symbol first (BTCUSDT, BTCUSD1, BTCU — a non-USDT market's CLI symbol is its venue
|
|
342
|
+
* symbol, asterCliSymbol), then the USDT market of a base (ETH → ETHUSDT, USD1 → USD1USDT),
|
|
343
|
+
* else SYMBOL_NOT_FOUND — never a guessed suffix. Callers load the directory first (_apiSymbol).
|
|
344
|
+
*/
|
|
331
345
|
_toApi(symbol) {
|
|
332
346
|
const s = symbol.toUpperCase().replace(/-PERP$/, "");
|
|
333
|
-
|
|
347
|
+
// A bare base names its USDT market first (X → XUSDT), even if a non-USDT venue symbol is
|
|
348
|
+
// spelled X too; an exact venue symbol otherwise (BTCUSD1, BTCU, XUSDT).
|
|
349
|
+
if (this._priceFilters.has(`${s}USDT`))
|
|
350
|
+
return `${s}USDT`;
|
|
351
|
+
if (this._priceFilters.has(s))
|
|
334
352
|
return s;
|
|
335
|
-
|
|
353
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Aster has no market ${s} (neither the venue symbol ${s} nor ${s}USDT is listed)`, {
|
|
354
|
+
exchange: "aster", remediation: "List markets: perp -e aster market list",
|
|
355
|
+
});
|
|
336
356
|
}
|
|
337
357
|
/** Aster API symbol → CLI symbol (ETHUSDT → ETH) */
|
|
338
358
|
_fromApi(symbol) {
|
|
@@ -408,10 +428,9 @@ export class AsterAdapter {
|
|
|
408
428
|
}
|
|
409
429
|
const premiumMap = new Map(premiums.map(p => [String(p.symbol), p]));
|
|
410
430
|
this._ingestPriceFilters(info.symbols);
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
const tradingSymbols = info.symbols
|
|
414
|
-
.filter((s) => String(s.contractType) === "PERPETUAL" && String(s.status) === "TRADING" && String(s.quoteAsset) === "USDT");
|
|
431
|
+
// Every listed perpetual, any quote (the WS directory's set): USDT-quoted ones by base (ETH),
|
|
432
|
+
// USD1 / U quoted ones by venue symbol (BTCUSD1, BTCU — asterCliSymbol), with quoteAsset.
|
|
433
|
+
const tradingSymbols = info.symbols.filter((s) => isAsterListedPerpetual(s));
|
|
415
434
|
const result = tradingSymbols.flatMap((s) => {
|
|
416
435
|
const sym = String(s.symbol);
|
|
417
436
|
const ticker = tickerMap.get(sym);
|
|
@@ -423,17 +442,24 @@ export class AsterAdapter {
|
|
|
423
442
|
const maxLev = null;
|
|
424
443
|
const lotFilter = s.filters
|
|
425
444
|
?.find(f => f.filterType === "LOT_SIZE");
|
|
445
|
+
const cliSymbol = asterCliSymbol({ symbol: sym, baseAsset: String(s.baseAsset), quoteAsset: s.quoteAsset });
|
|
426
446
|
const fundingHours = this._fundingHoursCache.get(this._fromApi(sym));
|
|
427
447
|
return [{
|
|
428
|
-
symbol:
|
|
448
|
+
symbol: cliSymbol,
|
|
449
|
+
venueSymbol: sym,
|
|
450
|
+
quoteAsset: typeof s.quoteAsset === "string" ? s.quoteAsset : null,
|
|
429
451
|
markPrice: String(premium.markPrice),
|
|
430
452
|
indexPrice: premium?.indexPrice != null ? String(premium.indexPrice) : null,
|
|
431
453
|
fundingRate: premium?.lastFundingRate != null ? String(premium.lastFundingRate) : null,
|
|
454
|
+
// premiumIndex lastFundingRate moves during the period (same value as WS markPrice@arr `r`: accruing).
|
|
455
|
+
fundingRateKind: premium?.lastFundingRate != null ? "accruing" : null,
|
|
432
456
|
volume24h: ticker?.quoteVolume != null ? String(ticker.quoteVolume) : null,
|
|
433
457
|
// /fapi/v3/exchangeInfo and ticker/24hr carry no open interest.
|
|
434
458
|
openInterest: null,
|
|
435
459
|
maxLeverage: maxLev,
|
|
436
|
-
|
|
460
|
+
// The LOT_SIZE step is the authoritative grid (quantityPrecision can disagree with it: CFX
|
|
461
|
+
// step "1", precision 1); its decimals, the public directory's rule (venueMarketRow).
|
|
462
|
+
sizeDecimals: lotFilter?.stepSize != null ? decimalsOf(String(lotFilter.stepSize)) : s.quantityPrecision != null ? Number(s.quantityPrecision) : null,
|
|
437
463
|
stepSize: lotFilter?.stepSize != null ? String(lotFilter.stepSize) : null,
|
|
438
464
|
fundingHours: fundingHours ?? null,
|
|
439
465
|
marginTiers: null,
|
|
@@ -468,14 +494,36 @@ export class AsterAdapter {
|
|
|
468
494
|
this._priceFiltersLoadedAt = Date.now();
|
|
469
495
|
}
|
|
470
496
|
/** PRICE_FILTER / PERCENT_PRICE of an API symbol (exchangeInfo, cached CACHE_TTL). */
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
497
|
+
/** exchangeInfo symbols (the market directory) loaded / refreshed per CACHE_TTL. */
|
|
498
|
+
/**
|
|
499
|
+
* exchangeInfo symbols (the market directory), refreshed per CACHE_TTL. Concurrent first uses
|
|
500
|
+
* share ONE in-flight request; a failed load is not cached (the next call retries).
|
|
501
|
+
*/
|
|
502
|
+
async _ensureDirectory() {
|
|
503
|
+
if (this._priceFiltersLoadedAt && Date.now() - this._priceFiltersLoadedAt < AsterAdapter.CACHE_TTL)
|
|
504
|
+
return;
|
|
505
|
+
if (this._directoryLoad === null) {
|
|
506
|
+
this._directoryLoad = (async () => {
|
|
507
|
+
const info = await this._publicGet("/fapi/v3/exchangeInfo");
|
|
508
|
+
if (!Array.isArray(info?.symbols)) {
|
|
509
|
+
throw new PerpError("EXCHANGE_ERROR", "Aster /fapi/v3/exchangeInfo returned no symbols", { exchange: "aster" });
|
|
510
|
+
}
|
|
511
|
+
this._ingestPriceFilters(info.symbols);
|
|
512
|
+
})().finally(() => { this._directoryLoad = null; });
|
|
478
513
|
}
|
|
514
|
+
return this._directoryLoad;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* CLI symbol → Aster API symbol, resolved through the market directory (_toApi), loaded on first
|
|
518
|
+
* use and cached CACHE_TTL — so the answer never depends on what was read before (a cold
|
|
519
|
+
* adapter resolves BTCU, USD1 → USD1USDT, an unknown symbol is SYMBOL_NOT_FOUND).
|
|
520
|
+
*/
|
|
521
|
+
async _apiSymbol(symbol) {
|
|
522
|
+
await this._ensureDirectory();
|
|
523
|
+
return this._toApi(symbol);
|
|
524
|
+
}
|
|
525
|
+
async _symbolPriceFilters(apiSymbol) {
|
|
526
|
+
await this._ensureDirectory();
|
|
479
527
|
if (!this._priceFilters.has(apiSymbol)) {
|
|
480
528
|
throw new PerpError("SYMBOL_NOT_FOUND", `Aster market ${apiSymbol} not found in exchangeInfo`, { exchange: "aster", remediation: "List markets: perp -e aster market list" });
|
|
481
529
|
}
|
|
@@ -491,7 +539,7 @@ export class AsterAdapter {
|
|
|
491
539
|
* No runtime substitute (no mid / last fallback): unavailable → PRICE_STALE.
|
|
492
540
|
*/
|
|
493
541
|
async referencePrice(symbol) {
|
|
494
|
-
const apiSymbol = this.
|
|
542
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
495
543
|
let mark;
|
|
496
544
|
try {
|
|
497
545
|
const res = await this._publicGet("/fapi/v3/premiumIndex", { symbol: apiSymbol });
|
|
@@ -536,7 +584,7 @@ export class AsterAdapter {
|
|
|
536
584
|
* against PRICE_FILTER / PERCENT_PRICE. appliedBps = the bound after tick alignment.
|
|
537
585
|
*/
|
|
538
586
|
async computeProtectionPrice(symbol, side, slippage) {
|
|
539
|
-
const apiSymbol = this.
|
|
587
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
540
588
|
const f = await this._symbolPriceFilters(apiSymbol);
|
|
541
589
|
const ref = await this.referencePrice(symbol);
|
|
542
590
|
const protectionPrice = alignProtectionPrice(slippagePrice(ref, side, slippage.bps), f.tickSize, side);
|
|
@@ -549,7 +597,7 @@ export class AsterAdapter {
|
|
|
549
597
|
return asterIocOutcome(raw);
|
|
550
598
|
}
|
|
551
599
|
async getOrderbook(symbol) {
|
|
552
|
-
const res = await this._publicGet("/fapi/v3/depth", { symbol: this.
|
|
600
|
+
const res = await this._publicGet("/fapi/v3/depth", { symbol: await this._apiSymbol(symbol), limit: "50" });
|
|
553
601
|
return {
|
|
554
602
|
bids: res?.bids ?? [],
|
|
555
603
|
asks: res?.asks ?? [],
|
|
@@ -557,26 +605,31 @@ export class AsterAdapter {
|
|
|
557
605
|
}
|
|
558
606
|
async getRecentTrades(symbol, limit = 20) {
|
|
559
607
|
const trades = await this._publicGet("/fapi/v3/trades", {
|
|
560
|
-
symbol: this.
|
|
608
|
+
symbol: await this._apiSymbol(symbol),
|
|
561
609
|
limit: String(limit),
|
|
562
610
|
});
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
611
|
+
if (!Array.isArray(trades))
|
|
612
|
+
throw new PerpError("EXCHANGE_ERROR", `Aster trades is not a list (${JSON.stringify(trades).slice(0, 120)})`, { exchange: "aster" });
|
|
613
|
+
// The tape rows carry no symbol: the normalized venue symbol (was the raw argument, "BTC-PERP").
|
|
614
|
+
const sym = this._fromApi(await this._apiSymbol(symbol));
|
|
615
|
+
return trades.map((t) => {
|
|
616
|
+
if (typeof t.time !== "number" || typeof t.price !== "string" || typeof t.qty !== "string" || typeof t.isBuyerMaker !== "boolean") {
|
|
617
|
+
throw new PerpError("EXCHANGE_ERROR", `Aster trade row without time / price / qty / isBuyerMaker: ${JSON.stringify(t).slice(0, 160)}`, { exchange: "aster" });
|
|
618
|
+
}
|
|
619
|
+
return { time: t.time, symbol: sym, side: t.isBuyerMaker ? "sell" : "buy", price: t.price, size: t.qty, fee: null }; // the public tape carries no fee
|
|
620
|
+
});
|
|
571
621
|
}
|
|
572
622
|
async getFundingHistory(symbol, limit = 10) {
|
|
573
623
|
const data = await this._publicGet("/fapi/v3/fundingRate", {
|
|
574
|
-
symbol: this.
|
|
624
|
+
symbol: await this._apiSymbol(symbol),
|
|
575
625
|
limit: String(limit),
|
|
576
626
|
});
|
|
577
627
|
if (!Array.isArray(data)) {
|
|
578
628
|
throw new PerpError("EXCHANGE_ERROR", `Aster fundingRate history is not a list (${JSON.stringify(data).slice(0, 120)})`, { exchange: "aster" });
|
|
579
629
|
}
|
|
630
|
+
// Per-market interval (/fapi/v3/fundingInfo); null when the venue lists none for the symbol.
|
|
631
|
+
const hours = await this.getFundingHours(symbol);
|
|
632
|
+
const intervalHours = hours === undefined ? null : hours;
|
|
580
633
|
// `200 []` for an unlisted symbol (live 2026-10-14): checked against the market directory.
|
|
581
634
|
return nonEmptyOrListed(this, symbol, data.map((d) => {
|
|
582
635
|
const time = Number(d.fundingTime);
|
|
@@ -587,8 +640,10 @@ export class AsterAdapter {
|
|
|
587
640
|
time,
|
|
588
641
|
rate: venueRateField(d.fundingRate, "fundingRate fundingRate", "aster"),
|
|
589
642
|
price: d.markPrice ? String(d.markPrice) : null,
|
|
643
|
+
intervalHours,
|
|
644
|
+
rateKind: "lastSettled",
|
|
590
645
|
};
|
|
591
|
-
}), this._fromApi(this.
|
|
646
|
+
}), this._fromApi(await this._apiSymbol(symbol)));
|
|
592
647
|
}
|
|
593
648
|
/**
|
|
594
649
|
* Candles over [startTime, endTime]. GET /fapi/v3/klines returns at most 1500 bars per request
|
|
@@ -596,6 +651,11 @@ export class AsterAdapter {
|
|
|
596
651
|
* range is never cut to the first page. More than ASTER_KLINE_MAX_PAGES pages → INVALID_PARAMS.
|
|
597
652
|
*/
|
|
598
653
|
async getKlines(symbol, interval, startTime, endTime) {
|
|
654
|
+
if (!ASTER_KLINE_INTERVALS.has(interval)) {
|
|
655
|
+
throw new PerpError("INVALID_PARAMS", `Aster: unsupported candle interval "${interval}"`, {
|
|
656
|
+
remediation: `Use one of: ${[...ASTER_KLINE_INTERVALS].join(", ")}`,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
599
659
|
if (!(startTime <= endTime)) {
|
|
600
660
|
throw new PerpError("INVALID_PARAMS", `Aster klines: start ${startTime} is after end ${endTime}`, {
|
|
601
661
|
remediation: "Pass a --start that is before --end",
|
|
@@ -610,7 +670,7 @@ export class AsterAdapter {
|
|
|
610
670
|
});
|
|
611
671
|
}
|
|
612
672
|
const data = await this._publicGet("/fapi/v3/klines", {
|
|
613
|
-
symbol: this.
|
|
673
|
+
symbol: await this._apiSymbol(symbol), interval,
|
|
614
674
|
startTime: String(from), endTime: String(endTime), limit: String(ASTER_KLINE_PAGE),
|
|
615
675
|
});
|
|
616
676
|
if (!Array.isArray(data)) {
|
|
@@ -993,7 +1053,7 @@ export class AsterAdapter {
|
|
|
993
1053
|
// PRICE_FILTER / PERCENT_PRICE). newOrderRespType=RESULT makes the response carry the
|
|
994
1054
|
// final IOC status (FILLED / EXPIRED) and executedQty.
|
|
995
1055
|
const r = this._resolveSigner();
|
|
996
|
-
const apiSymbol = this.
|
|
1056
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
997
1057
|
const clientOrderId = opts.clientOrderId === undefined ? null : requireAsterClientOrderId(opts.clientOrderId);
|
|
998
1058
|
let protectionPrice;
|
|
999
1059
|
let protection;
|
|
@@ -1081,7 +1141,7 @@ export class AsterAdapter {
|
|
|
1081
1141
|
}
|
|
1082
1142
|
async limitOrder(symbol, side, price, size, opts) {
|
|
1083
1143
|
const r = this._resolveSigner();
|
|
1084
|
-
const apiSymbol = this.
|
|
1144
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
1085
1145
|
const clientOrderId = opts?.clientOrderId === undefined ? null : requireAsterClientOrderId(opts.clientOrderId);
|
|
1086
1146
|
let params;
|
|
1087
1147
|
try {
|
|
@@ -1124,7 +1184,7 @@ export class AsterAdapter {
|
|
|
1124
1184
|
*/
|
|
1125
1185
|
async editOrder(symbol, orderId, price, size) {
|
|
1126
1186
|
const r = this._resolveSigner();
|
|
1127
|
-
const apiSymbol = this.
|
|
1187
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
1128
1188
|
const rawOrders = await this._signedGetEip712("/fapi/v3/openOrders", {}, r);
|
|
1129
1189
|
const existing = (rawOrders ?? []).find(o => String(o.orderId ?? "") === orderId);
|
|
1130
1190
|
if (!existing) {
|
|
@@ -1171,7 +1231,7 @@ export class AsterAdapter {
|
|
|
1171
1231
|
const label = ref.kind === "orderId" ? ref.value : `c:${ref.value}`;
|
|
1172
1232
|
let row;
|
|
1173
1233
|
try {
|
|
1174
|
-
row = await this._signedGetEip712("/fapi/v3/order", { symbol: this.
|
|
1234
|
+
row = await this._signedGetEip712("/fapi/v3/order", { symbol: await this._apiSymbol(symbol), ...key }, this._resolveSigner());
|
|
1175
1235
|
}
|
|
1176
1236
|
catch (e) {
|
|
1177
1237
|
if (e instanceof PerpError && e.structured.details?.venueCode === -2013) {
|
|
@@ -1190,7 +1250,7 @@ export class AsterAdapter {
|
|
|
1190
1250
|
const r = this._resolveSigner();
|
|
1191
1251
|
try {
|
|
1192
1252
|
return await this._signedDeleteEip712("/fapi/v3/order", {
|
|
1193
|
-
symbol: this.
|
|
1253
|
+
symbol: await this._apiSymbol(symbol),
|
|
1194
1254
|
...key,
|
|
1195
1255
|
}, r);
|
|
1196
1256
|
}
|
|
@@ -1211,6 +1271,7 @@ export class AsterAdapter {
|
|
|
1211
1271
|
const r = this._resolveSigner();
|
|
1212
1272
|
if (!symbol) {
|
|
1213
1273
|
const orders = await this.getOpenOrders();
|
|
1274
|
+
await this._ensureDirectory();
|
|
1214
1275
|
const apiSymbols = new Set(orders.map(o => this._toApi(o.symbol)));
|
|
1215
1276
|
const results = [];
|
|
1216
1277
|
for (const sym of apiSymbols) {
|
|
@@ -1218,7 +1279,7 @@ export class AsterAdapter {
|
|
|
1218
1279
|
}
|
|
1219
1280
|
return results;
|
|
1220
1281
|
}
|
|
1221
|
-
return this._signedDeleteEip712("/fapi/v3/allOpenOrders", { symbol: this.
|
|
1282
|
+
return this._signedDeleteEip712("/fapi/v3/allOpenOrders", { symbol: await this._apiSymbol(symbol) }, r);
|
|
1222
1283
|
}
|
|
1223
1284
|
// ── Risk ──
|
|
1224
1285
|
async setLeverage(symbol, leverage, marginMode) {
|
|
@@ -1226,7 +1287,7 @@ export class AsterAdapter {
|
|
|
1226
1287
|
if (marginMode) {
|
|
1227
1288
|
try {
|
|
1228
1289
|
await this._signedPostEip712("/fapi/v3/marginType", {
|
|
1229
|
-
symbol: this.
|
|
1290
|
+
symbol: await this._apiSymbol(symbol),
|
|
1230
1291
|
marginType: marginMode === "cross" ? "CROSSED" : "ISOLATED",
|
|
1231
1292
|
}, r);
|
|
1232
1293
|
}
|
|
@@ -1265,7 +1326,7 @@ export class AsterAdapter {
|
|
|
1265
1326
|
}
|
|
1266
1327
|
}
|
|
1267
1328
|
return this._signedPostEip712("/fapi/v3/leverage", {
|
|
1268
|
-
symbol: this.
|
|
1329
|
+
symbol: await this._apiSymbol(symbol),
|
|
1269
1330
|
leverage: String(leverage),
|
|
1270
1331
|
}, r);
|
|
1271
1332
|
}
|
|
@@ -1284,7 +1345,7 @@ export class AsterAdapter {
|
|
|
1284
1345
|
*/
|
|
1285
1346
|
async stopOrder(symbol, side, size, triggerPrice, opts) {
|
|
1286
1347
|
const r = this._resolveSigner();
|
|
1287
|
-
const apiSymbol = this.
|
|
1348
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
1288
1349
|
const what = `stop ${side} ${size} ${symbol} @ ${triggerPrice}`;
|
|
1289
1350
|
let params;
|
|
1290
1351
|
try {
|
|
@@ -1786,10 +1847,31 @@ export class AsterAdapter {
|
|
|
1786
1847
|
const url = `${this._baseUrl}${path}${qs ? `?${qs}` : ""}`;
|
|
1787
1848
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1788
1849
|
const res = await fetch(url);
|
|
1789
|
-
|
|
1850
|
+
let result;
|
|
1851
|
+
try {
|
|
1852
|
+
result = await this._handleResponse(res, "GET", path, attempt);
|
|
1853
|
+
}
|
|
1854
|
+
catch (e) {
|
|
1855
|
+
if (e instanceof PerpError)
|
|
1856
|
+
throw e;
|
|
1857
|
+
// A public read never surfaces as UNKNOWN: rate limit / server-side / other HTTP failure.
|
|
1858
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1859
|
+
throw new PerpError(res.status === 429 ? "RATE_LIMITED" : res.status >= 500 ? "EXCHANGE_UNREACHABLE" : "EXCHANGE_ERROR", `Aster ${msg}`, {
|
|
1860
|
+
exchange: "aster", status: res.status, remediation: res.status === 429 || res.status >= 500 ? "Retry later" : "Check the request (symbol / parameters)",
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
// A parameter rejection arrives as HTTP 200 with an error body (live 2026-10-19,
|
|
1864
|
+
// fundingRate limit=1001: {"status":"ERROR","type":"VALID","code":"000002","errorData":{"limit":"…"}}).
|
|
1865
|
+
if (result !== null && typeof result === "object" && !Array.isArray(result) && result.status === "ERROR") {
|
|
1866
|
+
const r = result;
|
|
1867
|
+
const detail = JSON.stringify(r.errorData ?? r).slice(0, 200);
|
|
1868
|
+
throw new PerpError(r.type === "VALID" ? "INVALID_PARAMS" : "EXCHANGE_ERROR", `Aster ${path} rejected the request: ${detail}`, {
|
|
1869
|
+
exchange: "aster", venueCode: r.code, remediation: "Check the request parameters (e.g. --limit 1..1000)",
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1790
1872
|
if (result !== null)
|
|
1791
1873
|
return result;
|
|
1792
1874
|
}
|
|
1793
|
-
throw new
|
|
1875
|
+
throw new PerpError("RATE_LIMITED", `Aster GET ${path}: rate limited after 3 attempts`, { exchange: "aster", remediation: "Retry later" });
|
|
1794
1876
|
}
|
|
1795
1877
|
}
|
|
@@ -29,3 +29,10 @@ export declare function isNeverMatch(a: string, b: string): boolean;
|
|
|
29
29
|
* @param symbol plain symbol as reported by the adapter (no quote suffix, no dex prefix)
|
|
30
30
|
*/
|
|
31
31
|
export declare function canonicalFor(venueKey: VenueId | `hl:${string}`, symbol: string, assetClass: AssetClass): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Canonical key for a symbol a USER typed (no venue / asset class known — `arb scan --compare`):
|
|
34
|
+
* a "-PERP" suffix is dropped, a denomination alias maps to its canonical ticker (kPEPE →
|
|
35
|
+
* 1000PEPE, UBTC → BTC), and a venue ticker of the explicit table maps to its canonical key when
|
|
36
|
+
* every venue that lists it agrees (XAU → GOLD, CL → CRUDE_OIL_WTI). Anything else: upper-cased.
|
|
37
|
+
*/
|
|
38
|
+
export declare function canonicalQuerySymbol(symbol: string): string;
|
|
@@ -116,5 +116,25 @@ export function canonicalFor(venueKey, symbol, assetClass) {
|
|
|
116
116
|
case "fx":
|
|
117
117
|
case "preIpo":
|
|
118
118
|
return EXPLICIT_CANONICAL[`${venueKey}|${up}`] ?? null;
|
|
119
|
+
case "unclassified":
|
|
120
|
+
return null; // ambiguous ticker: listed, never matched across venues
|
|
119
121
|
}
|
|
120
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Canonical key for a symbol a USER typed (no venue / asset class known — `arb scan --compare`):
|
|
125
|
+
* a "-PERP" suffix is dropped, a denomination alias maps to its canonical ticker (kPEPE →
|
|
126
|
+
* 1000PEPE, UBTC → BTC), and a venue ticker of the explicit table maps to its canonical key when
|
|
127
|
+
* every venue that lists it agrees (XAU → GOLD, CL → CRUDE_OIL_WTI). Anything else: upper-cased.
|
|
128
|
+
*/
|
|
129
|
+
export function canonicalQuerySymbol(symbol) {
|
|
130
|
+
const up = symbol.trim().toUpperCase().replace(/-PERP$/, "");
|
|
131
|
+
const alias = DENOMINATION_ALIASES[up];
|
|
132
|
+
if (alias !== undefined)
|
|
133
|
+
return alias;
|
|
134
|
+
const targets = new Set();
|
|
135
|
+
for (const [key, canonical] of Object.entries(EXPLICIT_CANONICAL)) {
|
|
136
|
+
if (key.slice(key.indexOf("|") + 1) === up)
|
|
137
|
+
targets.add(canonical);
|
|
138
|
+
}
|
|
139
|
+
return targets.size === 1 ? [...targets][0] : up;
|
|
140
|
+
}
|
|
@@ -29,6 +29,8 @@ export interface TriggerOrderCapable {
|
|
|
29
29
|
export interface DexCapable {
|
|
30
30
|
readonly dex: string;
|
|
31
31
|
setDex(dex: string): void;
|
|
32
|
+
/** An independent adapter for `dex` with this account configuration and its own market state (never mutates this one). */
|
|
33
|
+
forDex(dex: string): ExchangeAdapter & DexCapable;
|
|
32
34
|
listDeployedDexes(): Promise<{
|
|
33
35
|
name: string;
|
|
34
36
|
deployer: string;
|
|
@@ -18,7 +18,8 @@ export function isTriggerOrderCapable(adapter) {
|
|
|
18
18
|
}
|
|
19
19
|
export function isDexCapable(adapter) {
|
|
20
20
|
return typeof adapter.setDex === "function"
|
|
21
|
-
&& typeof adapter.listDeployedDexes === "function"
|
|
21
|
+
&& typeof adapter.listDeployedDexes === "function"
|
|
22
|
+
&& typeof adapter.forDex === "function";
|
|
22
23
|
}
|
|
23
24
|
export function isSubAccountCapable(adapter) {
|
|
24
25
|
return typeof adapter.createSubAccount === "function";
|
|
@@ -39,6 +39,11 @@ export declare function utcDate(ms: number): string;
|
|
|
39
39
|
export declare function normalizeExpiry(input: string): string;
|
|
40
40
|
/** User underlying → venue currency (upper case); INVALID_PARAMS for anything that is not a ticker. */
|
|
41
41
|
export declare function normalizeUnderlying(input: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* CLI option name → Derive wire name: upper-cased, and a decimal point in the strike written as
|
|
44
|
+
* the venue's underscore (`PUMP-20261023-0.0045-C` → `PUMP-20261023-0_0045-C`).
|
|
45
|
+
*/
|
|
46
|
+
export declare function deriveInstrumentName(input: string): string;
|
|
42
47
|
export declare function parseDeriveInstrument(raw: unknown): DeriveOptionInstrument;
|
|
43
48
|
/** A ticker_slim snapshot (get_ticker / get_tickers value) of `inst` → OptionQuote. */
|
|
44
49
|
export declare function parseDeriveOptionTicker(raw: unknown, inst: DeriveOptionInstrument): OptionQuote;
|
|
@@ -75,6 +75,16 @@ export function normalizeUnderlying(input) {
|
|
|
75
75
|
}
|
|
76
76
|
return u;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* CLI option name → Derive wire name: upper-cased, and a decimal point in the strike written as
|
|
80
|
+
* the venue's underscore (`PUMP-20261023-0.0045-C` → `PUMP-20261023-0_0045-C`).
|
|
81
|
+
*/
|
|
82
|
+
export function deriveInstrumentName(input) {
|
|
83
|
+
const parts = input.trim().toUpperCase().split("-");
|
|
84
|
+
if (parts.length === 4)
|
|
85
|
+
parts[2] = parts[2].replace(/\./g, "_");
|
|
86
|
+
return parts.join("-");
|
|
87
|
+
}
|
|
78
88
|
// ── parsing ──
|
|
79
89
|
export function parseDeriveInstrument(raw) {
|
|
80
90
|
const o = obj(raw, "instrument");
|
|
@@ -318,7 +328,7 @@ export class DeriveOptionsVenue {
|
|
|
318
328
|
return buildChain(underlying, expiry, wanted === null ? "nearest" : "requested", listed, res.tickers, q.strikesAroundAtm);
|
|
319
329
|
}
|
|
320
330
|
async getQuote(instrumentInput) {
|
|
321
|
-
const name = instrumentInput
|
|
331
|
+
const name = deriveInstrumentName(instrumentInput);
|
|
322
332
|
// Both reads are needed (instrument facts + live ticker); issued together, judged instrument first.
|
|
323
333
|
const [instR, tickR] = await Promise.allSettled([
|
|
324
334
|
this.client.call("get_instrument", { instrument_name: name }),
|
|
@@ -339,6 +349,15 @@ export class DeriveOptionsVenue {
|
|
|
339
349
|
});
|
|
340
350
|
}
|
|
341
351
|
const inst = parseDeriveInstrument(o);
|
|
352
|
+
if (!inst.isActive && inst.expiryMs <= Date.now()) {
|
|
353
|
+
// Expired (live: ETH-20261019-2400-C, is_active false after expiry, option_details.settlement_price set).
|
|
354
|
+
const sp = o.option_details?.settlement_price;
|
|
355
|
+
const settled = typeof sp === "string" && sp !== "" ? `, settled at ${sp}` : " (settlement price not published yet)";
|
|
356
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Derive option ${name} expired at ${new Date(inst.expiryMs).toISOString()}${settled}`, {
|
|
357
|
+
exchange: VENUE, expired: true, settlementPrice: typeof sp === "string" && sp !== "" ? sp : null,
|
|
358
|
+
remediation: `Active expiries: perp -e ${VENUE} options expiries ${inst.underlying}`,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
342
361
|
if (!inst.isActive) {
|
|
343
362
|
throw new PerpError("SYMBOL_NOT_FOUND", `Derive option ${name} is listed but not active (activation ${new Date(inst.activationMs).toISOString()})`, {
|
|
344
363
|
exchange: VENUE, remediation: `Active expiries: perp -e ${VENUE} options expiries ${inst.underlying}`,
|
|
@@ -19,8 +19,18 @@ export declare const EXTENDED_KLINE_INTERVALS: Readonly<Record<string, {
|
|
|
19
19
|
wire: string;
|
|
20
20
|
ms: number;
|
|
21
21
|
}>>;
|
|
22
|
-
/** Documented per-request maximum of the candles and funding-history endpoints. */
|
|
22
|
+
/** Documented per-request maximum of the candles and funding-history endpoints (accepted `limit`). */
|
|
23
23
|
export declare const EXTENDED_MAX_RECORDS = 10000;
|
|
24
|
+
/**
|
|
25
|
+
* Real server caps, measured live 2026-10-19 (BTC-USD; no pagination field, no error): candles
|
|
26
|
+
* return at most 2800 rows on every interval with that much history (PT1M … PT4H: 2800; PT8H 2445,
|
|
27
|
+
* PT12H 1631, PT24H 816, P7D 118, P30D 28 = the whole history since 2024-07), funding history at
|
|
28
|
+
* most 1000 (the newest). Both page backwards with `endTime` = before the oldest row and stop only
|
|
29
|
+
* on an empty page or at the window start — a short page is never taken as the end.
|
|
30
|
+
*/
|
|
31
|
+
export declare const EXTENDED_CANDLES_PAGE_CAP = 2800;
|
|
32
|
+
export declare const EXTENDED_FUNDING_PAGE_CAP = 1000;
|
|
33
|
+
export declare const EXTENDED_TRADES_CAP = 50;
|
|
24
34
|
/** Legacy market row plus the Extended fields shown in JSON (asset class, precision, schedule). */
|
|
25
35
|
export type ExtendedMarketInfo = ExchangeMarketInfo & {
|
|
26
36
|
venueSymbol: string;
|
|
@@ -62,8 +72,12 @@ export declare class ExtendedPublicAdapter implements ExchangeAdapter {
|
|
|
62
72
|
asks: [string, string][];
|
|
63
73
|
}>;
|
|
64
74
|
getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
|
|
65
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Applied (settled) hourly rates, newest first — fraction per hour; no oracle price published.
|
|
77
|
+
* Paged backwards in pages of EXTENDED_FUNDING_PAGE_CAP until `limit` rows or the window start.
|
|
78
|
+
*/
|
|
66
79
|
getFundingHistory(symbol: string, limit?: number): Promise<ExtendedFundingPrint[]>;
|
|
80
|
+
/** Candles paged backwards from `endTime` in pages of EXTENDED_CANDLES_PAGE_CAP (newest first on the wire). */
|
|
67
81
|
getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
|
|
68
82
|
private refuse;
|
|
69
83
|
getBalance(): Promise<ExchangeBalance>;
|