@imbingox/acex 0.3.1-beta.0 → 0.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,7 @@ const book = client.market.getL1Book({
30
30
  symbol: "BTC/USDT:USDT",
31
31
  });
32
32
  const books = client.market.getL1Books("BTC/USDT:USDT");
33
- console.log(`bid=${book?.bidPrice.toFixed()} ask=${book?.askPrice.toFixed()}`);
33
+ console.log(`bid=${book?.bidPrice} ask=${book?.askPrice}`);
34
34
  console.log(`venues=${books.length}`);
35
35
  console.log(`book freshness=${book?.status.freshness}`);
36
36
 
@@ -44,14 +44,14 @@ const funding = client.market.getFundingRate({
44
44
  symbol: "BTC/USDT:USDT",
45
45
  });
46
46
  const fundingRates = client.market.getFundingRates("BTC/USDT:USDT");
47
- console.log(`funding=${funding?.fundingRate.toFixed()}`);
47
+ console.log(`funding=${funding?.fundingRate}`);
48
48
  console.log(`funding venues=${fundingRates.length}`);
49
49
 
50
50
  for await (const event of client.market.events.l1BookUpdates({
51
51
  venue: "binance",
52
52
  symbol: "BTC/USDT:USDT",
53
53
  })) {
54
- console.log(event.snapshot.bidPrice.toFixed());
54
+ console.log(event.snapshot.bidPrice);
55
55
  break;
56
56
  }
57
57
 
@@ -113,11 +113,11 @@ const juplendRisk = client.account.getRiskSnapshot("jup-loop-a");
113
113
  const juplendBalances = client.account.getBalances("jup-loop-a");
114
114
 
115
115
  for (const balance of juplendBalances) {
116
- console.log(balance.asset, balance.lending?.netAsset.toFixed());
116
+ console.log(balance.asset, balance.lending?.netAsset);
117
117
  }
118
118
  console.log({
119
- binanceRiskRatio: binanceRisk?.riskRatio?.toFixed(),
120
- juplendRiskRatio: juplendRisk?.riskRatio?.toFixed(),
119
+ binanceRiskRatio: binanceRisk?.riskRatio,
120
+ juplendRiskRatio: juplendRisk?.riskRatio,
121
121
  });
122
122
 
123
123
  await client.stop();
@@ -141,7 +141,7 @@ console.log(juplend.order.reason); // "read_only"
141
141
  const capabilities = client.listVenueCapabilities();
142
142
  ```
143
143
 
144
- 价格、数量等输出字段统一是 `BigNumber`;`createOrder()` 的 `price` / `amount` 输入仍接受 decimal string。详见手册 [§3 核心概念](./docs/api.md#3-核心概念)。
144
+ 价格、数量等公共输出字段统一是 canonical decimal string(无科学计数法、不补尾零);输入侧保持宽进严出,`createOrder()` 的 `price` / `amount` decimal string,`DecimalInput` 仍接受 string / number / `BigNumber`。如需运算,使用 SDK re-export 的 `BigNumber`:`new BigNumber(field)`。详见手册 [§3 核心概念](./docs/api.md#3-核心概念)。
145
145
 
146
146
  ## 核心能力
147
147
 
package/docs/api.md CHANGED
@@ -51,13 +51,13 @@ const book = client.market.getL1Book({
51
51
  venue: "binance",
52
52
  symbol: "BTC/USDT:USDT",
53
53
  });
54
- console.log(`bid=${book?.bidPrice.toFixed()} ask=${book?.askPrice.toFixed()}`);
54
+ console.log(`bid=${book?.bidPrice} ask=${book?.askPrice}`);
55
55
 
56
56
  for await (const event of client.market.events.l1BookUpdates({
57
57
  venue: "binance",
58
58
  symbol: "BTC/USDT:USDT",
59
59
  })) {
60
- console.log(event.snapshot.bidPrice.toFixed());
60
+ console.log(event.snapshot.bidPrice);
61
61
  break;
62
62
  }
63
63
 
@@ -114,8 +114,8 @@ await client.account.subscribeAccount({ accountId: "jup-loop-a" });
114
114
  const binanceRisk = client.account.getRiskSnapshot("main-binance");
115
115
  const juplendRisk = client.account.getRiskSnapshot("jup-loop-a");
116
116
 
117
- console.log(binanceRisk?.riskRatio?.toFixed());
118
- console.log(juplendRisk?.riskRatio?.toFixed());
117
+ console.log(binanceRisk?.riskRatio);
118
+ console.log(juplendRisk?.riskRatio);
119
119
  ```
120
120
 
121
121
  需要账户或订单能力时,在 `start()` 前后任意时刻 `registerAccount()`:
@@ -182,19 +182,19 @@ await client.market.subscribeL1Book({ venue, symbol });
182
182
 
183
183
  退订后 `activity` 变为 `"inactive"`,但最后一份快照仍可读——不要把它当实时值。
184
184
 
185
- ### 3.5 BigNumber 约定
185
+ ### 3.5 Decimal string 约定
186
186
 
187
- 输出侧的价格、数量、金额统一是 `BigNumber`(来自 [bignumber.js](https://github.com/MikeMcl/bignumber.js),SDK 已 re-export):
187
+ 输出侧的价格、数量、金额统一是 canonical 十进制 string:无损、无科学计数法、不补尾零。SDK 仍 re-export `BigNumber`(来自 [bignumber.js](https://github.com/MikeMcl/bignumber.js))作为可选工具;需要运算时由调用方显式解析:
188
188
 
189
189
  ```ts
190
190
  import { BigNumber } from "@imbingox/acex";
191
191
 
192
192
  const book = client.market.getL1Book({ venue, symbol });
193
- const spread = book!.askPrice.minus(book!.bidPrice); // BigNumber
193
+ const spread = new BigNumber(book!.askPrice).minus(new BigNumber(book!.bidPrice));
194
194
  console.log(spread.toFixed());
195
195
  ```
196
196
 
197
- **输入侧不对称**:`createOrder()` 的 `price` / `amount` 仍接受 decimal string。这是为了让调用方直接从交易所精度(`MarketDefinition.priceStep` / `amountStep`)做字符串格式化,不必先转 BigNumber 再转字符串。
197
+ 不要用 `parseFloat()` 解析输出字段,否则会退回 JS 浮点精度。输入侧保持宽进严出:`createOrder()` 的 `price` / `amount` decimal string,`normalizeOrderInput()` 的 `DecimalInput` 仍接受 string / number / `BigNumber`,但公共输出一律是 canonical decimal string。
198
198
 
199
199
  ## 4. Client 生命周期
200
200
 
@@ -378,7 +378,7 @@ const allBtcPerp = client.market.getMarkets("BTC/USDT:USDT");
378
378
 
379
379
  `getMarkets(symbol)` 严格按完整统一 symbol 匹配。
380
380
 
381
- `MarketDefinition` 见 [§9](#9-数据类型参考)。价格/数量相关字段(`priceStep`、`amountStep`、`contractSize`、`minAmount`、`minNotional`)都是 `BigNumber`。
381
+ `MarketDefinition` 见 [§9](#9-数据类型参考)。价格/数量相关字段(`priceStep`、`amountStep`、`contractSize`、`minAmount`、`minNotional`)都是 canonical decimal string;需要运算时用 `new BigNumber(field)` 自行解析。
382
382
 
383
383
  归一化下单价格和数量:
384
384
 
@@ -459,7 +459,7 @@ const book = client.market.getL1Book({
459
459
  });
460
460
 
461
461
  if (book) {
462
- const spread = book.askPrice.minus(book.bidPrice);
462
+ const spread = new BigNumber(book.askPrice).minus(new BigNumber(book.bidPrice));
463
463
  console.log(`spread=${spread.toFixed()}`);
464
464
  }
465
465
  ```
@@ -471,7 +471,7 @@ for await (const event of client.market.events.l1BookUpdates({
471
471
  venue: "binance",
472
472
  symbol: "BTC/USDT:USDT",
473
473
  })) {
474
- console.log(event.snapshot.bidPrice.toFixed());
474
+ console.log(event.snapshot.bidPrice);
475
475
  }
476
476
  ```
477
477
 
@@ -529,9 +529,9 @@ const funding = client.market.getFundingRate({
529
529
  });
530
530
 
531
531
  if (funding) {
532
- console.log(funding.fundingRate.toFixed());
533
- console.log(funding.markPrice?.toFixed());
534
- console.log(funding.indexPrice?.toFixed());
532
+ console.log(funding.fundingRate);
533
+ console.log(funding.markPrice);
534
+ console.log(funding.indexPrice);
535
535
  console.log(funding.nextFundingTime);
536
536
  console.log(funding.status.freshness);
537
537
  }
@@ -544,7 +544,7 @@ for await (const event of client.market.events.fundingRateUpdates({
544
544
  venue: "binance",
545
545
  symbol: "BTC/USDT:USDT",
546
546
  })) {
547
- console.log(event.snapshot.fundingRate.toFixed());
547
+ console.log(event.snapshot.fundingRate);
548
548
  }
549
549
  ```
550
550
 
@@ -638,7 +638,7 @@ const btcPosition = client.account.getPosition({
638
638
  const risk = client.account.getRiskSnapshot("main-binance");
639
639
  ```
640
640
 
641
- 所有数量字段(`free` / `used` / `total` / `size` / `entryPrice` / `netEquity` / `riskEquity` / ...)都是 `BigNumber`。
641
+ 所有数量字段(`free` / `used` / `total` / `size` / `entryPrice` / `netEquity` / `riskEquity` / ...)都是 canonical decimal string;需要运算时用 `new BigNumber(field)` 自行解析。
642
642
 
643
643
  `RiskSnapshot.netEquity` 表示不含风控折算的净资产价值;`riskEquity` 表示抵押系数或清算阈值折算后的风控净权益。Binance 使用 `actualEquity` / `accountEquity` 映射这两个字段;Juplend 使用 `totalCollateralUsd - totalDebtUsd` / `Σ(suppliedValue × liquidationThreshold) - totalDebtUsd`。
644
644
 
@@ -652,13 +652,13 @@ for await (const event of client.account.events.updates({
652
652
  })) {
653
653
  switch (event.type) {
654
654
  case "balance.updated":
655
- console.log(event.asset, event.snapshot.free.toFixed());
655
+ console.log(event.asset, event.snapshot.free);
656
656
  break;
657
657
  case "position.updated":
658
- console.log(event.symbol, event.snapshot.size.toFixed());
658
+ console.log(event.symbol, event.snapshot.size);
659
659
  break;
660
660
  case "risk.updated":
661
- console.log(event.snapshot.riskRatio?.toFixed());
661
+ console.log(event.snapshot.riskRatio);
662
662
  break;
663
663
  case "account.snapshot_replaced":
664
664
  // 私有链路重连/重对账后的全量替换
@@ -801,10 +801,10 @@ for await (const event of client.order.events.updates({
801
801
  })) {
802
802
  switch (event.type) {
803
803
  case "order.updated":
804
- console.log("更新", event.snapshot.status, event.snapshot.filled.toFixed());
804
+ console.log("更新", event.snapshot.status, event.snapshot.filled);
805
805
  break;
806
806
  case "order.filled":
807
- console.log("全部成交", event.snapshot.avgFillPrice?.toFixed());
807
+ console.log("全部成交", event.snapshot.avgFillPrice);
808
808
  break;
809
809
  case "order.canceled":
810
810
  console.log("已撤单");
@@ -1100,13 +1100,13 @@ interface MarketDefinition {
1100
1100
  contract: boolean;
1101
1101
  linear?: boolean;
1102
1102
  inverse?: boolean;
1103
- contractSize?: BigNumber;
1103
+ contractSize?: string;
1104
1104
  pricePrecision: number;
1105
1105
  amountPrecision: number;
1106
- priceStep: BigNumber;
1107
- amountStep: BigNumber;
1108
- minAmount?: BigNumber;
1109
- minNotional?: BigNumber;
1106
+ priceStep: string;
1107
+ amountStep: string;
1108
+ minAmount?: string;
1109
+ minNotional?: string;
1110
1110
  expiry?: number;
1111
1111
  raw: Record<string, unknown>;
1112
1112
  }
@@ -1155,10 +1155,10 @@ interface MarketEventFilter {
1155
1155
  interface L1Book {
1156
1156
  venue: Venue;
1157
1157
  symbol: string;
1158
- bidPrice: BigNumber;
1159
- bidSize: BigNumber;
1160
- askPrice: BigNumber;
1161
- askSize: BigNumber;
1158
+ bidPrice: string;
1159
+ bidSize: string;
1160
+ askPrice: string;
1161
+ askSize: string;
1162
1162
  exchangeTs?: number;
1163
1163
  receivedAt: number;
1164
1164
  updatedAt: number;
@@ -1169,10 +1169,10 @@ interface L1Book {
1169
1169
  interface FundingRateSnapshot {
1170
1170
  venue: Venue;
1171
1171
  symbol: string;
1172
- fundingRate: BigNumber;
1172
+ fundingRate: string;
1173
1173
  nextFundingTime?: number;
1174
- markPrice?: BigNumber;
1175
- indexPrice?: BigNumber;
1174
+ markPrice?: string;
1175
+ indexPrice?: string;
1176
1176
  exchangeTs?: number;
1177
1177
  receivedAt: number;
1178
1178
  updatedAt: number;
@@ -1210,9 +1210,9 @@ interface BalanceSnapshot {
1210
1210
  accountId: string;
1211
1211
  venue: Venue;
1212
1212
  asset: string;
1213
- free: BigNumber;
1214
- used: BigNumber;
1215
- total: BigNumber;
1213
+ free: string;
1214
+ used: string;
1215
+ total: string;
1216
1216
  exchangeTs?: number;
1217
1217
  receivedAt: number;
1218
1218
  updatedAt: number;
@@ -1221,12 +1221,12 @@ interface BalanceSnapshot {
1221
1221
  }
1222
1222
 
1223
1223
  interface LendingBalanceFacet {
1224
- supplied: BigNumber;
1225
- borrowed: BigNumber;
1226
- interest: BigNumber;
1227
- netAsset: BigNumber;
1228
- supplyAPY?: BigNumber;
1229
- borrowAPY?: BigNumber;
1224
+ supplied: string;
1225
+ borrowed: string;
1226
+ interest: string;
1227
+ netAsset: string;
1228
+ supplyAPY?: string;
1229
+ borrowAPY?: string;
1230
1230
  }
1231
1231
 
1232
1232
  interface PositionSnapshot {
@@ -1234,12 +1234,12 @@ interface PositionSnapshot {
1234
1234
  venue: Venue;
1235
1235
  symbol: string;
1236
1236
  side: PositionSide;
1237
- size: BigNumber;
1238
- entryPrice?: BigNumber;
1239
- markPrice?: BigNumber;
1240
- unrealizedPnl?: BigNumber;
1241
- leverage?: BigNumber;
1242
- liquidationPrice?: BigNumber;
1237
+ size: string;
1238
+ entryPrice?: string;
1239
+ markPrice?: string;
1240
+ unrealizedPnl?: string;
1241
+ leverage?: string;
1242
+ liquidationPrice?: string;
1243
1243
  exchangeTs?: number;
1244
1244
  receivedAt: number;
1245
1245
  updatedAt: number;
@@ -1249,12 +1249,12 @@ interface PositionSnapshot {
1249
1249
  interface RiskSnapshot {
1250
1250
  accountId: string;
1251
1251
  venue: Venue;
1252
- netEquity?: BigNumber;
1253
- riskEquity?: BigNumber;
1254
- riskRatio?: BigNumber;
1255
- riskLeverage?: BigNumber;
1256
- initialMargin?: BigNumber;
1257
- maintenanceMargin?: BigNumber;
1252
+ netEquity?: string;
1253
+ riskEquity?: string;
1254
+ riskRatio?: string;
1255
+ riskLeverage?: string;
1256
+ initialMargin?: string;
1257
+ maintenanceMargin?: string;
1258
1258
  exchangeTs?: number;
1259
1259
  receivedAt: number;
1260
1260
  updatedAt: number;
@@ -1263,12 +1263,12 @@ interface RiskSnapshot {
1263
1263
  }
1264
1264
 
1265
1265
  interface LendingRiskFacet {
1266
- marginLevel?: BigNumber;
1267
- healthFactor?: BigNumber;
1268
- ltv?: BigNumber;
1269
- liquidationThreshold?: BigNumber;
1270
- totalCollateralUSD?: BigNumber;
1271
- totalDebtUSD?: BigNumber;
1266
+ marginLevel?: string;
1267
+ healthFactor?: string;
1268
+ ltv?: string;
1269
+ liquidationThreshold?: string;
1270
+ totalCollateralUSD?: string;
1271
+ totalDebtUSD?: string;
1272
1272
  }
1273
1273
 
1274
1274
  interface AccountSnapshot {
@@ -1350,14 +1350,14 @@ interface OrderSnapshot {
1350
1350
  side: OrderSide;
1351
1351
  type: string; // 交易所原始 type 字符串
1352
1352
  status: OrderStatus;
1353
- price?: BigNumber;
1354
- triggerPrice?: BigNumber;
1355
- amount: BigNumber;
1356
- filled: BigNumber;
1357
- remaining?: BigNumber;
1353
+ price?: string;
1354
+ triggerPrice?: string;
1355
+ amount: string;
1356
+ filled: string;
1357
+ remaining?: string;
1358
1358
  reduceOnly?: boolean;
1359
1359
  positionSide?: PositionSide;
1360
- avgFillPrice?: BigNumber;
1360
+ avgFillPrice?: string;
1361
1361
  exchangeTs?: number;
1362
1362
  receivedAt: number;
1363
1363
  updatedAt: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imbingox/acex",
3
- "version": "0.3.1-beta.0",
3
+ "version": "0.4.0-beta.2",
4
4
  "description": "Multi-exchange trading SDK for market data, account, and order management",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,4 +1,8 @@
1
- import BigNumber from "bignumber.js";
1
+ import { toCanonical } from "../../internal/decimal.ts";
2
+ import {
3
+ type HttpClientMessages,
4
+ httpRequest,
5
+ } from "../../internal/http-client.ts";
2
6
  import type { MarketDefinition, MarketType } from "../../types/index.ts";
3
7
 
4
8
  type FetchLike = typeof fetch;
@@ -54,6 +58,11 @@ const BINANCE_USDM_EXCHANGE_INFO_URL =
54
58
  "https://fapi.binance.com/fapi/v1/exchangeInfo";
55
59
  const BINANCE_COINM_EXCHANGE_INFO_URL =
56
60
  "https://dapi.binance.com/dapi/v1/exchangeInfo";
61
+ const DEFAULT_HTTP_TIMEOUT_MS = 10_000;
62
+ const BINANCE_CATALOG_HTTP_MESSAGES: HttpClientMessages = {
63
+ http: ({ status, statusText }) =>
64
+ `Binance request failed: ${status} ${statusText ?? ""}`,
65
+ };
57
66
 
58
67
  function toRecord(value: unknown): Record<string, unknown> {
59
68
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -146,12 +155,12 @@ function normalizeSpotSymbol(
146
155
  contract: false,
147
156
  pricePrecision: precisionFromStep(priceStep),
148
157
  amountPrecision: precisionFromStep(amountStep),
149
- priceStep: new BigNumber(priceStep),
150
- amountStep: new BigNumber(amountStep),
158
+ priceStep: toCanonical(priceStep),
159
+ amountStep: toCanonical(amountStep),
151
160
  minAmount: lotSizeFilter?.minQty
152
- ? new BigNumber(lotSizeFilter.minQty)
161
+ ? toCanonical(lotSizeFilter.minQty)
153
162
  : undefined,
154
- minNotional: notionalValue ? new BigNumber(notionalValue) : undefined,
163
+ minNotional: notionalValue ? toCanonical(notionalValue) : undefined,
155
164
  raw: toRecord(symbol),
156
165
  };
157
166
  }
@@ -198,29 +207,38 @@ function normalizeDerivativesSymbol(
198
207
  contract: true,
199
208
  linear: family === "usdm",
200
209
  inverse: family === "coinm",
201
- contractSize: contractSize ? new BigNumber(contractSize) : undefined,
210
+ contractSize: contractSize ? toCanonical(contractSize) : undefined,
202
211
  pricePrecision: precisionFromStep(priceStep),
203
212
  amountPrecision: precisionFromStep(amountStep),
204
- priceStep: new BigNumber(priceStep),
205
- amountStep: new BigNumber(amountStep),
213
+ priceStep: toCanonical(priceStep),
214
+ amountStep: toCanonical(amountStep),
206
215
  minAmount: lotSizeFilter?.minQty
207
- ? new BigNumber(lotSizeFilter.minQty)
216
+ ? toCanonical(lotSizeFilter.minQty)
208
217
  : undefined,
209
- minNotional: notionalValue ? new BigNumber(notionalValue) : undefined,
218
+ minNotional: notionalValue ? toCanonical(notionalValue) : undefined,
210
219
  expiry: type === "future" ? symbol.deliveryDate : undefined,
211
220
  raw: toRecord(symbol),
212
221
  };
213
222
  }
214
223
 
215
- async function fetchJson<T>(fetchFn: FetchLike, url: string): Promise<T> {
216
- const response = await fetchFn(url);
217
- if (!response.ok) {
218
- throw new Error(
219
- `Binance request failed: ${response.status} ${response.statusText}`,
220
- );
221
- }
224
+ async function requestCatalogJson<T>(
225
+ fetchFn: FetchLike,
226
+ url: string,
227
+ ): Promise<T> {
228
+ const response = await httpRequest<T>({
229
+ fetchFn,
230
+ url,
231
+ timeoutMs: DEFAULT_HTTP_TIMEOUT_MS,
232
+ parseAs: "json",
233
+ jsonParseMode: "response",
234
+ retryPolicy: {
235
+ idempotent: true,
236
+ maxAttempts: 3,
237
+ },
238
+ messages: BINANCE_CATALOG_HTTP_MESSAGES,
239
+ });
222
240
 
223
- return (await response.json()) as T;
241
+ return response.body;
224
242
  }
225
243
 
226
244
  function sortMarkets(
@@ -235,12 +253,15 @@ export async function loadBinanceMarkets(
235
253
  fetchFn: FetchLike = fetch,
236
254
  ): Promise<BinanceMarketDefinition[]> {
237
255
  const [spot, usdm, coinm] = await Promise.all([
238
- fetchJson<BinanceSpotExchangeInfo>(fetchFn, BINANCE_SPOT_EXCHANGE_INFO_URL),
239
- fetchJson<BinanceDerivativesExchangeInfo>(
256
+ requestCatalogJson<BinanceSpotExchangeInfo>(
257
+ fetchFn,
258
+ BINANCE_SPOT_EXCHANGE_INFO_URL,
259
+ ),
260
+ requestCatalogJson<BinanceDerivativesExchangeInfo>(
240
261
  fetchFn,
241
262
  BINANCE_USDM_EXCHANGE_INFO_URL,
242
263
  ),
243
- fetchJson<BinanceDerivativesExchangeInfo>(
264
+ requestCatalogJson<BinanceDerivativesExchangeInfo>(
244
265
  fetchFn,
245
266
  BINANCE_COINM_EXCHANGE_INFO_URL,
246
267
  ),