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

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.1",
4
4
  "description": "Multi-exchange trading SDK for market data, account, and order management",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,4 +1,4 @@
1
- import BigNumber from "bignumber.js";
1
+ import { toCanonical } from "../../internal/decimal.ts";
2
2
  import type { MarketDefinition, MarketType } from "../../types/index.ts";
3
3
 
4
4
  type FetchLike = typeof fetch;
@@ -146,12 +146,12 @@ function normalizeSpotSymbol(
146
146
  contract: false,
147
147
  pricePrecision: precisionFromStep(priceStep),
148
148
  amountPrecision: precisionFromStep(amountStep),
149
- priceStep: new BigNumber(priceStep),
150
- amountStep: new BigNumber(amountStep),
149
+ priceStep: toCanonical(priceStep),
150
+ amountStep: toCanonical(amountStep),
151
151
  minAmount: lotSizeFilter?.minQty
152
- ? new BigNumber(lotSizeFilter.minQty)
152
+ ? toCanonical(lotSizeFilter.minQty)
153
153
  : undefined,
154
- minNotional: notionalValue ? new BigNumber(notionalValue) : undefined,
154
+ minNotional: notionalValue ? toCanonical(notionalValue) : undefined,
155
155
  raw: toRecord(symbol),
156
156
  };
157
157
  }
@@ -198,15 +198,15 @@ function normalizeDerivativesSymbol(
198
198
  contract: true,
199
199
  linear: family === "usdm",
200
200
  inverse: family === "coinm",
201
- contractSize: contractSize ? new BigNumber(contractSize) : undefined,
201
+ contractSize: contractSize ? toCanonical(contractSize) : undefined,
202
202
  pricePrecision: precisionFromStep(priceStep),
203
203
  amountPrecision: precisionFromStep(amountStep),
204
- priceStep: new BigNumber(priceStep),
205
- amountStep: new BigNumber(amountStep),
204
+ priceStep: toCanonical(priceStep),
205
+ amountStep: toCanonical(amountStep),
206
206
  minAmount: lotSizeFilter?.minQty
207
- ? new BigNumber(lotSizeFilter.minQty)
207
+ ? toCanonical(lotSizeFilter.minQty)
208
208
  : undefined,
209
- minNotional: notionalValue ? new BigNumber(notionalValue) : undefined,
209
+ minNotional: notionalValue ? toCanonical(notionalValue) : undefined,
210
210
  expiry: type === "future" ? symbol.deliveryDate : undefined,
211
211
  raw: toRecord(symbol),
212
212
  };
@@ -0,0 +1,19 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type { DecimalInput } from "../types/index.ts";
3
+
4
+ /**
5
+ * Convert a decimal value to its canonical string form: full precision, no
6
+ * scientific notation, no trailing zeros.
7
+ *
8
+ * Throws on non-finite input (NaN / Infinity) so producers can never leak
9
+ * sentinel strings into public output fields. Call sites that legitimately
10
+ * accept non-finite input (e.g. order-input validation) must guard before
11
+ * calling this.
12
+ */
13
+ export function toCanonical(value: DecimalInput): string {
14
+ const bn = new BigNumber(value);
15
+ if (!bn.isFinite()) {
16
+ throw new RangeError(`invalid non-finite DecimalInput: ${bn.toString()}`);
17
+ }
18
+ return bn.toFixed();
19
+ }
@@ -15,6 +15,7 @@ import type {
15
15
  PrivateSubscriptionState,
16
16
  } from "../client/context.ts";
17
17
  import { AsyncEventBus } from "../internal/async-event-bus.ts";
18
+ import { toCanonical } from "../internal/decimal.ts";
18
19
  import { matchesAccountFilter } from "../internal/filters.ts";
19
20
  import type {
20
21
  AccountDataStatus,
@@ -56,6 +57,10 @@ function getBigNumber(
56
57
  return value === undefined ? fallback : new BigNumber(value);
57
58
  }
58
59
 
60
+ function isZeroDecimal(value: string): boolean {
61
+ return new BigNumber(value).isZero();
62
+ }
63
+
59
64
  export class AccountManagerImpl
60
65
  implements
61
66
  AccountManager,
@@ -322,7 +327,7 @@ export class AccountManagerImpl
322
327
  positions.get(key),
323
328
  );
324
329
 
325
- if (nextPosition.size.isZero()) {
330
+ if (isZeroDecimal(nextPosition.size)) {
326
331
  positions.delete(key);
327
332
  } else {
328
333
  positions.set(key, nextPosition);
@@ -461,7 +466,7 @@ export class AccountManagerImpl
461
466
  );
462
467
  const positions = bootstrap.positions
463
468
  .map((position) => this.createPosition(accountId, venue, position))
464
- .filter((position) => !position.size.isZero());
469
+ .filter((position) => !isZeroDecimal(position.size));
465
470
  const risk = bootstrap.risk
466
471
  ? this.createRisk(accountId, venue, bootstrap.risk)
467
472
  : undefined;
@@ -499,9 +504,12 @@ export class AccountManagerImpl
499
504
  input: RawBalanceUpdate,
500
505
  previous?: BalanceSnapshot,
501
506
  ): BalanceSnapshot {
502
- const previousFree = previous?.free ?? new BigNumber(0);
503
- const previousUsed = previous?.used ?? new BigNumber(0);
504
- const previousTotal = previous?.total ?? previousFree.plus(previousUsed);
507
+ const previousFree = new BigNumber(previous?.free ?? 0);
508
+ const previousUsed = new BigNumber(previous?.used ?? 0);
509
+ const previousTotal =
510
+ previous?.total === undefined
511
+ ? previousFree.plus(previousUsed)
512
+ : new BigNumber(previous.total);
505
513
  const free = getBigNumber(input.free, previousFree);
506
514
  const total = getBigNumber(input.total, previousTotal);
507
515
  const used =
@@ -515,27 +523,27 @@ export class AccountManagerImpl
515
523
  accountId,
516
524
  venue,
517
525
  asset: input.asset,
518
- free,
519
- used,
520
- total,
526
+ free: toCanonical(free),
527
+ used: toCanonical(used),
528
+ total: toCanonical(total),
521
529
  exchangeTs: input.exchangeTs,
522
530
  receivedAt: input.receivedAt,
523
531
  updatedAt: input.receivedAt,
524
532
  seq: (previous?.seq ?? 0) + 1,
525
533
  lending: input.lending
526
534
  ? {
527
- supplied: new BigNumber(input.lending.supplied),
528
- borrowed: new BigNumber(input.lending.borrowed),
529
- interest: new BigNumber(input.lending.interest),
530
- netAsset: new BigNumber(input.lending.netAsset),
535
+ supplied: toCanonical(input.lending.supplied),
536
+ borrowed: toCanonical(input.lending.borrowed),
537
+ interest: toCanonical(input.lending.interest),
538
+ netAsset: toCanonical(input.lending.netAsset),
531
539
  supplyAPY:
532
540
  input.lending.supplyAPY === undefined
533
541
  ? undefined
534
- : new BigNumber(input.lending.supplyAPY),
542
+ : toCanonical(input.lending.supplyAPY),
535
543
  borrowAPY:
536
544
  input.lending.borrowAPY === undefined
537
545
  ? undefined
538
- : new BigNumber(input.lending.borrowAPY),
546
+ : toCanonical(input.lending.borrowAPY),
539
547
  }
540
548
  : previous?.lending,
541
549
  };
@@ -552,27 +560,27 @@ export class AccountManagerImpl
552
560
  venue,
553
561
  symbol: input.symbol,
554
562
  side: input.side,
555
- size: new BigNumber(input.size),
563
+ size: toCanonical(input.size),
556
564
  entryPrice:
557
565
  input.entryPrice === undefined
558
566
  ? previous?.entryPrice
559
- : new BigNumber(input.entryPrice),
567
+ : toCanonical(input.entryPrice),
560
568
  markPrice:
561
569
  input.markPrice === undefined
562
570
  ? previous?.markPrice
563
- : new BigNumber(input.markPrice),
571
+ : toCanonical(input.markPrice),
564
572
  unrealizedPnl:
565
573
  input.unrealizedPnl === undefined
566
574
  ? previous?.unrealizedPnl
567
- : new BigNumber(input.unrealizedPnl),
575
+ : toCanonical(input.unrealizedPnl),
568
576
  leverage:
569
577
  input.leverage === undefined
570
578
  ? previous?.leverage
571
- : new BigNumber(input.leverage),
579
+ : toCanonical(input.leverage),
572
580
  liquidationPrice:
573
581
  input.liquidationPrice === undefined
574
582
  ? previous?.liquidationPrice
575
- : new BigNumber(input.liquidationPrice),
583
+ : toCanonical(input.liquidationPrice),
576
584
  exchangeTs: input.exchangeTs,
577
585
  receivedAt: input.receivedAt,
578
586
  updatedAt: input.receivedAt,
@@ -592,27 +600,27 @@ export class AccountManagerImpl
592
600
  netEquity:
593
601
  input.netEquity === undefined
594
602
  ? previous?.netEquity
595
- : new BigNumber(input.netEquity),
603
+ : toCanonical(input.netEquity),
596
604
  riskEquity:
597
605
  input.riskEquity === undefined
598
606
  ? previous?.riskEquity
599
- : new BigNumber(input.riskEquity),
607
+ : toCanonical(input.riskEquity),
600
608
  riskRatio:
601
609
  input.riskRatio === undefined
602
610
  ? previous?.riskRatio
603
- : new BigNumber(input.riskRatio),
611
+ : toCanonical(input.riskRatio),
604
612
  riskLeverage:
605
613
  input.riskLeverage === undefined
606
614
  ? previous?.riskLeverage
607
- : new BigNumber(input.riskLeverage),
615
+ : toCanonical(input.riskLeverage),
608
616
  initialMargin:
609
617
  input.initialMargin === undefined
610
618
  ? previous?.initialMargin
611
- : new BigNumber(input.initialMargin),
619
+ : toCanonical(input.initialMargin),
612
620
  maintenanceMargin:
613
621
  input.maintenanceMargin === undefined
614
622
  ? previous?.maintenanceMargin
615
- : new BigNumber(input.maintenanceMargin),
623
+ : toCanonical(input.maintenanceMargin),
616
624
  exchangeTs: input.exchangeTs,
617
625
  receivedAt: input.receivedAt,
618
626
  updatedAt: input.receivedAt,
@@ -622,27 +630,27 @@ export class AccountManagerImpl
622
630
  marginLevel:
623
631
  input.lending.marginLevel === undefined
624
632
  ? undefined
625
- : new BigNumber(input.lending.marginLevel),
633
+ : toCanonical(input.lending.marginLevel),
626
634
  healthFactor:
627
635
  input.lending.healthFactor === undefined
628
636
  ? undefined
629
- : new BigNumber(input.lending.healthFactor),
637
+ : toCanonical(input.lending.healthFactor),
630
638
  ltv:
631
639
  input.lending.ltv === undefined
632
640
  ? undefined
633
- : new BigNumber(input.lending.ltv),
641
+ : toCanonical(input.lending.ltv),
634
642
  liquidationThreshold:
635
643
  input.lending.liquidationThreshold === undefined
636
644
  ? undefined
637
- : new BigNumber(input.lending.liquidationThreshold),
645
+ : toCanonical(input.lending.liquidationThreshold),
638
646
  totalCollateralUSD:
639
647
  input.lending.totalCollateralUSD === undefined
640
648
  ? undefined
641
- : new BigNumber(input.lending.totalCollateralUSD),
649
+ : toCanonical(input.lending.totalCollateralUSD),
642
650
  totalDebtUSD:
643
651
  input.lending.totalDebtUSD === undefined
644
652
  ? undefined
645
- : new BigNumber(input.lending.totalDebtUSD),
653
+ : toCanonical(input.lending.totalDebtUSD),
646
654
  }
647
655
  : previous?.lending,
648
656
  };
@@ -16,6 +16,7 @@ import type {
16
16
  } from "../client/context.ts";
17
17
  import { AcexError } from "../errors.ts";
18
18
  import { AsyncEventBus } from "../internal/async-event-bus.ts";
19
+ import { toCanonical } from "../internal/decimal.ts";
19
20
  import { matchesMarketFilter } from "../internal/filters.ts";
20
21
  import type {
21
22
  FundingRateSnapshot,
@@ -100,10 +101,6 @@ function floorToStep(value: BigNumber, step: BigNumber): BigNumber {
100
101
  return value.dividedToIntegerBy(step).multipliedBy(step);
101
102
  }
102
103
 
103
- function normalizeDecimalInput(value: BigNumber): string {
104
- return value.isFinite() ? value.toFixed() : value.toString();
105
- }
106
-
107
104
  export class MarketManagerImpl
108
105
  implements MarketManager, ManagerLifecycle, HealthReporter<MarketDataStatus>
109
106
  {
@@ -262,20 +259,36 @@ export class MarketManagerImpl
262
259
  const market = this.resolveLoadedMarket(input);
263
260
  const rawPrice = new BigNumber(input.price);
264
261
  const rawAmount = new BigNumber(input.amount);
265
- const price = floorToStep(rawPrice, market.priceStep);
266
- const amount = floorToStep(rawAmount, market.amountStep);
262
+ const priceStep = new BigNumber(market.priceStep);
263
+ const amountStep = new BigNumber(market.amountStep);
264
+ const minAmount =
265
+ market.minAmount === undefined
266
+ ? undefined
267
+ : new BigNumber(market.minAmount);
268
+ const minNotional =
269
+ market.minNotional === undefined
270
+ ? undefined
271
+ : new BigNumber(market.minNotional);
272
+ const price = floorToStep(rawPrice, priceStep);
273
+ const amount = floorToStep(rawAmount, amountStep);
274
+
275
+ // normalizeOrderInput rejects non-finite input gracefully (see the
276
+ // isFinite checks below), so its echoed numeric fields fall back to the
277
+ // raw string instead of throwing the way toCanonical now does.
278
+ const echoDecimal = (value: BigNumber): string =>
279
+ value.isFinite() ? toCanonical(value) : value.toString();
267
280
 
268
281
  const normalized: NormalizedOrderInput = {
269
- price: normalizeDecimalInput(price),
270
- amount: normalizeDecimalInput(amount),
271
- rawPrice: normalizeDecimalInput(rawPrice),
272
- rawAmount: normalizeDecimalInput(rawAmount),
282
+ price: echoDecimal(price),
283
+ amount: echoDecimal(amount),
284
+ rawPrice: echoDecimal(rawPrice),
285
+ rawAmount: echoDecimal(rawAmount),
273
286
  adjusted: !price.isEqualTo(rawPrice) || !amount.isEqualTo(rawAmount),
274
287
  accepted: true,
275
- priceStep: market.priceStep.toFixed(),
276
- amountStep: market.amountStep.toFixed(),
277
- minAmount: market.minAmount?.toFixed(),
278
- minNotional: market.minNotional?.toFixed(),
288
+ priceStep: market.priceStep,
289
+ amountStep: market.amountStep,
290
+ minAmount: market.minAmount,
291
+ minNotional: market.minNotional,
279
292
  };
280
293
 
281
294
  if (!price.isFinite() || price.isLessThanOrEqualTo(0)) {
@@ -294,7 +307,7 @@ export class MarketManagerImpl
294
307
  };
295
308
  }
296
309
 
297
- if (market.minAmount && amount.isLessThan(market.minAmount)) {
310
+ if (minAmount && amount.isLessThan(minAmount)) {
298
311
  return {
299
312
  ...normalized,
300
313
  accepted: false,
@@ -302,9 +315,9 @@ export class MarketManagerImpl
302
315
  };
303
316
  }
304
317
 
305
- if (market.minNotional) {
318
+ if (minNotional) {
306
319
  const notional = amount.multipliedBy(price);
307
- if (notional.isLessThan(market.minNotional)) {
320
+ if (notional.isLessThan(minNotional)) {
308
321
  return {
309
322
  ...normalized,
310
323
  accepted: false,
@@ -774,10 +787,10 @@ export class MarketManagerImpl
774
787
  return {
775
788
  venue,
776
789
  symbol,
777
- bidPrice: new BigNumber(input.bidPrice),
778
- bidSize: new BigNumber(input.bidSize),
779
- askPrice: new BigNumber(input.askPrice),
780
- askSize: new BigNumber(input.askSize),
790
+ bidPrice: toCanonical(input.bidPrice),
791
+ bidSize: toCanonical(input.bidSize),
792
+ askPrice: toCanonical(input.askPrice),
793
+ askSize: toCanonical(input.askSize),
781
794
  exchangeTs: input.exchangeTs,
782
795
  receivedAt: input.receivedAt,
783
796
  updatedAt: input.receivedAt,
@@ -801,12 +814,10 @@ export class MarketManagerImpl
801
814
  return {
802
815
  venue,
803
816
  symbol,
804
- fundingRate: new BigNumber(input.fundingRate),
817
+ fundingRate: toCanonical(input.fundingRate),
805
818
  nextFundingTime: input.nextFundingTime,
806
- markPrice: input.markPrice ? new BigNumber(input.markPrice) : undefined,
807
- indexPrice: input.indexPrice
808
- ? new BigNumber(input.indexPrice)
809
- : undefined,
819
+ markPrice: input.markPrice ? toCanonical(input.markPrice) : undefined,
820
+ indexPrice: input.indexPrice ? toCanonical(input.indexPrice) : undefined,
810
821
  exchangeTs: input.exchangeTs,
811
822
  receivedAt: input.receivedAt,
812
823
  updatedAt: input.receivedAt,
@@ -10,6 +10,7 @@ import type {
10
10
  } from "../client/context.ts";
11
11
  import { AcexError } from "../errors.ts";
12
12
  import { AsyncEventBus } from "../internal/async-event-bus.ts";
13
+ import { toCanonical } from "../internal/decimal.ts";
13
14
  import { matchesOrderFilter } from "../internal/filters.ts";
14
15
  import type {
15
16
  CancelAllOrdersInput,
@@ -546,22 +547,20 @@ export class OrderManagerImpl
546
547
  type: input.type,
547
548
  status: input.status,
548
549
  price:
549
- input.price === undefined
550
- ? previous?.price
551
- : new BigNumber(input.price),
550
+ input.price === undefined ? previous?.price : toCanonical(input.price),
552
551
  triggerPrice:
553
552
  input.triggerPrice === undefined
554
553
  ? previous?.triggerPrice
555
- : new BigNumber(input.triggerPrice),
556
- amount,
557
- filled,
558
- remaining,
554
+ : toCanonical(input.triggerPrice),
555
+ amount: toCanonical(amount),
556
+ filled: toCanonical(filled),
557
+ remaining: toCanonical(remaining),
559
558
  reduceOnly: input.reduceOnly ?? previous?.reduceOnly,
560
559
  positionSide: input.positionSide ?? previous?.positionSide,
561
560
  avgFillPrice:
562
561
  input.avgFillPrice === undefined
563
562
  ? previous?.avgFillPrice
564
- : new BigNumber(input.avgFillPrice),
563
+ : toCanonical(input.avgFillPrice),
565
564
  exchangeTs: input.exchangeTs,
566
565
  receivedAt: input.receivedAt,
567
566
  updatedAt: input.receivedAt,
@@ -1,4 +1,3 @@
1
- import type BigNumber from "bignumber.js";
2
1
  import type {
3
2
  PrivateRuntimeReason,
4
3
  PrivateRuntimeStatus,
@@ -52,9 +51,9 @@ export interface BalanceSnapshot {
52
51
  accountId: string;
53
52
  venue: Venue;
54
53
  asset: string;
55
- free: BigNumber;
56
- used: BigNumber;
57
- total: BigNumber;
54
+ free: string;
55
+ used: string;
56
+ total: string;
58
57
  exchangeTs?: number;
59
58
  receivedAt: number;
60
59
  updatedAt: number;
@@ -63,12 +62,12 @@ export interface BalanceSnapshot {
63
62
  }
64
63
 
65
64
  export interface LendingBalanceFacet {
66
- supplied: BigNumber;
67
- borrowed: BigNumber;
68
- interest: BigNumber;
69
- netAsset: BigNumber;
70
- supplyAPY?: BigNumber;
71
- borrowAPY?: BigNumber;
65
+ supplied: string;
66
+ borrowed: string;
67
+ interest: string;
68
+ netAsset: string;
69
+ supplyAPY?: string;
70
+ borrowAPY?: string;
72
71
  }
73
72
 
74
73
  export interface PositionSnapshot {
@@ -76,12 +75,12 @@ export interface PositionSnapshot {
76
75
  venue: Venue;
77
76
  symbol: string;
78
77
  side: PositionSide;
79
- size: BigNumber;
80
- entryPrice?: BigNumber;
81
- markPrice?: BigNumber;
82
- unrealizedPnl?: BigNumber;
83
- leverage?: BigNumber;
84
- liquidationPrice?: BigNumber;
78
+ size: string;
79
+ entryPrice?: string;
80
+ markPrice?: string;
81
+ unrealizedPnl?: string;
82
+ leverage?: string;
83
+ liquidationPrice?: string;
85
84
  exchangeTs?: number;
86
85
  receivedAt: number;
87
86
  updatedAt: number;
@@ -91,12 +90,12 @@ export interface PositionSnapshot {
91
90
  export interface RiskSnapshot {
92
91
  accountId: string;
93
92
  venue: Venue;
94
- netEquity?: BigNumber;
95
- riskEquity?: BigNumber;
96
- riskRatio?: BigNumber;
97
- riskLeverage?: BigNumber;
98
- initialMargin?: BigNumber;
99
- maintenanceMargin?: BigNumber;
93
+ netEquity?: string;
94
+ riskEquity?: string;
95
+ riskRatio?: string;
96
+ riskLeverage?: string;
97
+ initialMargin?: string;
98
+ maintenanceMargin?: string;
100
99
  exchangeTs?: number;
101
100
  receivedAt: number;
102
101
  updatedAt: number;
@@ -105,12 +104,12 @@ export interface RiskSnapshot {
105
104
  }
106
105
 
107
106
  export interface LendingRiskFacet {
108
- marginLevel?: BigNumber;
109
- healthFactor?: BigNumber;
110
- ltv?: BigNumber;
111
- liquidationThreshold?: BigNumber;
112
- totalCollateralUSD?: BigNumber;
113
- totalDebtUSD?: BigNumber;
107
+ marginLevel?: string;
108
+ healthFactor?: string;
109
+ ltv?: string;
110
+ liquidationThreshold?: string;
111
+ totalCollateralUSD?: string;
112
+ totalDebtUSD?: string;
114
113
  }
115
114
 
116
115
  export interface AccountSnapshot {
@@ -15,13 +15,13 @@ export interface MarketDefinition {
15
15
  contract: boolean;
16
16
  linear?: boolean;
17
17
  inverse?: boolean;
18
- contractSize?: BigNumber;
18
+ contractSize?: string;
19
19
  pricePrecision: number;
20
20
  amountPrecision: number;
21
- priceStep: BigNumber;
22
- amountStep: BigNumber;
23
- minAmount?: BigNumber;
24
- minNotional?: BigNumber;
21
+ priceStep: string;
22
+ amountStep: string;
23
+ minAmount?: string;
24
+ minNotional?: string;
25
25
  expiry?: number;
26
26
  raw: Record<string, unknown>;
27
27
  }
@@ -92,10 +92,10 @@ export interface MarketEventFilter {
92
92
  export interface L1Book {
93
93
  venue: Venue;
94
94
  symbol: string;
95
- bidPrice: BigNumber;
96
- bidSize: BigNumber;
97
- askPrice: BigNumber;
98
- askSize: BigNumber;
95
+ bidPrice: string;
96
+ bidSize: string;
97
+ askPrice: string;
98
+ askSize: string;
99
99
  exchangeTs?: number;
100
100
  receivedAt: number;
101
101
  updatedAt: number;
@@ -106,10 +106,10 @@ export interface L1Book {
106
106
  export interface FundingRateSnapshot {
107
107
  venue: Venue;
108
108
  symbol: string;
109
- fundingRate: BigNumber;
109
+ fundingRate: string;
110
110
  nextFundingTime?: number;
111
- markPrice?: BigNumber;
112
- indexPrice?: BigNumber;
111
+ markPrice?: string;
112
+ indexPrice?: string;
113
113
  exchangeTs?: number;
114
114
  receivedAt: number;
115
115
  updatedAt: number;
@@ -1,4 +1,3 @@
1
- import type BigNumber from "bignumber.js";
2
1
  import type { PositionSide } from "./account.ts";
3
2
  import type {
4
3
  PrivateRuntimeReason,
@@ -102,14 +101,14 @@ export interface OrderSnapshot {
102
101
  side: OrderSide;
103
102
  type: string;
104
103
  status: OrderStatus;
105
- price?: BigNumber;
106
- triggerPrice?: BigNumber;
107
- amount: BigNumber;
108
- filled: BigNumber;
109
- remaining?: BigNumber;
104
+ price?: string;
105
+ triggerPrice?: string;
106
+ amount: string;
107
+ filled: string;
108
+ remaining?: string;
110
109
  reduceOnly?: boolean;
111
110
  positionSide?: PositionSide;
112
- avgFillPrice?: BigNumber;
111
+ avgFillPrice?: string;
113
112
  exchangeTs?: number;
114
113
  receivedAt: number;
115
114
  updatedAt: number;