@imbingox/acex 0.3.0-beta.0 → 0.3.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
|
@@ -125,6 +125,26 @@ bun run type-check
|
|
|
125
125
|
bun run test
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
### 测试分层
|
|
129
|
+
|
|
130
|
+
默认 `bun run test` 只运行快速、确定性的本地测试,不访问真实交易所:
|
|
131
|
+
|
|
132
|
+
| 命令 | 覆盖范围 | 是否进入默认 CI |
|
|
133
|
+
|------|----------|----------------|
|
|
134
|
+
| `bun run test:unit` | `tests/unit/`,底层工具和无全局副作用的单元测试 | 是 |
|
|
135
|
+
| `bun run test:integration` | `tests/integration/`,fake REST + fake WebSocket 的 SDK 跨层集成测试 | 是 |
|
|
136
|
+
| `bun run test` | `test:unit` + `test:integration` | 是 |
|
|
137
|
+
| `bun run test:soak` | `tests/soak/`,60 秒级稳定性/连续更新测试 | 否 |
|
|
138
|
+
| `bun run test:all` | 默认快速测试 + soak 测试 | 否 |
|
|
139
|
+
|
|
140
|
+
测试 support 结构:
|
|
141
|
+
|
|
142
|
+
- `tests/support/test-utils.ts`:通用 fake WebSocket、事件等待、Response helper 和全局清理。
|
|
143
|
+
- `tests/support/exchanges/binance.ts`:Binance 专用 REST/WS fixtures 与 installer。
|
|
144
|
+
- 新增交易所时,优先新增 `tests/support/exchanges/<exchange>.ts`,复用通用 helper,避免把交易所 payload 写进通用测试工具。
|
|
145
|
+
|
|
146
|
+
GitHub Actions 的 `CI` workflow 会在 PR 和 `main` push 时运行 lint、type-check、unit、integration;release workflow 继续复用 `bun run test`,不会执行 soak/live。
|
|
147
|
+
|
|
128
148
|
### 真实环境 smoke / soak 脚本
|
|
129
149
|
|
|
130
150
|
不进默认 `bun run test`,单独执行:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imbingox/acex",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.1",
|
|
4
4
|
"description": "Multi-exchange trading SDK for market data, account, and order management",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"lint:fix": "biome check --write .",
|
|
27
27
|
"release": "changeset publish",
|
|
28
28
|
"type-check": "tsc --noEmit",
|
|
29
|
-
"test": "bun test --max-concurrency=1",
|
|
29
|
+
"test": "bun test --max-concurrency=1 tests/unit tests/integration",
|
|
30
30
|
"test:live:account": "bun run scripts/live-account-smoke.ts",
|
|
31
31
|
"test:live:account:smoke": "bun run scripts/live-account-smoke.ts --duration 10",
|
|
32
32
|
"test:live:account:soak": "bun run scripts/live-account-smoke.ts --duration 60 --disconnect-after 5",
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
"test:live:order": "bun run scripts/live-order-smoke.ts",
|
|
37
37
|
"test:live:order:smoke": "bun run scripts/live-order-smoke.ts --duration 10",
|
|
38
38
|
"test:live:order:soak": "bun run scripts/live-order-smoke.ts --duration 60 --disconnect-after 5",
|
|
39
|
-
"version-packages": "changeset version && files=\"package.json\"; if [ -f .changeset/pre.json ]; then files=\"$files .changeset/pre.json\"; fi; if [ -f CHANGELOG.md ]; then files=\"$files CHANGELOG.md\"; fi; biome check --write $files"
|
|
39
|
+
"version-packages": "changeset version && files=\"package.json\"; if [ -f .changeset/pre.json ]; then files=\"$files .changeset/pre.json\"; fi; if [ -f CHANGELOG.md ]; then files=\"$files CHANGELOG.md\"; fi; biome check --write $files",
|
|
40
|
+
"test:unit": "bun test tests/unit",
|
|
41
|
+
"test:integration": "bun test --max-concurrency=1 tests/integration",
|
|
42
|
+
"test:soak": "bun test --max-concurrency=1 tests/soak",
|
|
43
|
+
"test:all": "bun run test && bun run test:soak"
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@biomejs/biome": "^2.4.10",
|
|
@@ -551,7 +551,12 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
551
551
|
type: encodeOrderType(request.type),
|
|
552
552
|
quantity: request.amount,
|
|
553
553
|
price: request.price,
|
|
554
|
-
timeInForce:
|
|
554
|
+
timeInForce:
|
|
555
|
+
request.type === "limit"
|
|
556
|
+
? request.postOnly === true
|
|
557
|
+
? "GTX"
|
|
558
|
+
: "GTC"
|
|
559
|
+
: undefined,
|
|
555
560
|
newClientOrderId: request.clientOrderId,
|
|
556
561
|
reduceOnly:
|
|
557
562
|
request.reduceOnly === undefined
|
package/src/adapters/types.ts
CHANGED
package/src/client/runtime.ts
CHANGED
|
@@ -297,6 +297,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
297
297
|
type: input.type,
|
|
298
298
|
amount: input.amount,
|
|
299
299
|
price: input.type === "limit" ? input.price : undefined,
|
|
300
|
+
postOnly: input.type === "limit" ? input.postOnly : undefined,
|
|
300
301
|
clientOrderId: input.clientOrderId,
|
|
301
302
|
reduceOnly: input.reduceOnly,
|
|
302
303
|
positionSide: input.positionSide,
|
|
@@ -31,6 +31,8 @@ import type {
|
|
|
31
31
|
MarketKeyInput,
|
|
32
32
|
MarketManager,
|
|
33
33
|
MarketStatusChangedEvent,
|
|
34
|
+
NormalizedOrderInput,
|
|
35
|
+
NormalizeOrderInputInput,
|
|
34
36
|
SubscribeFundingRateInput,
|
|
35
37
|
SubscribeL1BookInput,
|
|
36
38
|
SubscriptionActivity,
|
|
@@ -91,6 +93,17 @@ function cloneMarketDefinition(definition: MarketDefinition): MarketDefinition {
|
|
|
91
93
|
return { ...definition, raw: { ...definition.raw } };
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
function floorToStep(value: BigNumber, step: BigNumber): BigNumber {
|
|
97
|
+
if (step.isLessThanOrEqualTo(0)) {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
return value.dividedToIntegerBy(step).multipliedBy(step);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeDecimalInput(value: BigNumber): string {
|
|
104
|
+
return value.isFinite() ? value.toFixed() : value.toString();
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
export class MarketManagerImpl
|
|
95
108
|
implements MarketManager, ManagerLifecycle, HealthReporter<MarketDataStatus>
|
|
96
109
|
{
|
|
@@ -241,6 +254,64 @@ export class MarketManagerImpl
|
|
|
241
254
|
.map((market) => cloneMarketDefinition(market));
|
|
242
255
|
}
|
|
243
256
|
|
|
257
|
+
normalizeOrderInput(input: NormalizeOrderInputInput): NormalizedOrderInput {
|
|
258
|
+
const market = this.resolveLoadedMarket(input);
|
|
259
|
+
const rawPrice = new BigNumber(input.price);
|
|
260
|
+
const rawAmount = new BigNumber(input.amount);
|
|
261
|
+
const price = floorToStep(rawPrice, market.priceStep);
|
|
262
|
+
const amount = floorToStep(rawAmount, market.amountStep);
|
|
263
|
+
|
|
264
|
+
const normalized: NormalizedOrderInput = {
|
|
265
|
+
price: normalizeDecimalInput(price),
|
|
266
|
+
amount: normalizeDecimalInput(amount),
|
|
267
|
+
rawPrice: normalizeDecimalInput(rawPrice),
|
|
268
|
+
rawAmount: normalizeDecimalInput(rawAmount),
|
|
269
|
+
adjusted: !price.isEqualTo(rawPrice) || !amount.isEqualTo(rawAmount),
|
|
270
|
+
accepted: true,
|
|
271
|
+
priceStep: market.priceStep.toFixed(),
|
|
272
|
+
amountStep: market.amountStep.toFixed(),
|
|
273
|
+
minAmount: market.minAmount?.toFixed(),
|
|
274
|
+
minNotional: market.minNotional?.toFixed(),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
if (!price.isFinite() || price.isLessThanOrEqualTo(0)) {
|
|
278
|
+
return {
|
|
279
|
+
...normalized,
|
|
280
|
+
accepted: false,
|
|
281
|
+
rejectReason: "price_not_positive",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!amount.isFinite() || amount.isLessThanOrEqualTo(0)) {
|
|
286
|
+
return {
|
|
287
|
+
...normalized,
|
|
288
|
+
accepted: false,
|
|
289
|
+
rejectReason: "amount_not_positive",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (market.minAmount && amount.isLessThan(market.minAmount)) {
|
|
294
|
+
return {
|
|
295
|
+
...normalized,
|
|
296
|
+
accepted: false,
|
|
297
|
+
rejectReason: "amount_below_min",
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (market.minNotional) {
|
|
302
|
+
const notional = amount.multipliedBy(price);
|
|
303
|
+
if (notional.isLessThan(market.minNotional)) {
|
|
304
|
+
return {
|
|
305
|
+
...normalized,
|
|
306
|
+
accepted: false,
|
|
307
|
+
rejectReason: "notional_below_min",
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return normalized;
|
|
313
|
+
}
|
|
314
|
+
|
|
244
315
|
getL1Book(key: MarketKeyInput): L1Book | undefined {
|
|
245
316
|
const book = this.records.get(marketKey(key))?.l1Book;
|
|
246
317
|
return book ? cloneL1Book(book) : undefined;
|
|
@@ -414,6 +485,20 @@ export class MarketManagerImpl
|
|
|
414
485
|
return market;
|
|
415
486
|
}
|
|
416
487
|
|
|
488
|
+
private resolveLoadedMarket(input: MarketKeyInput): MarketDefinition {
|
|
489
|
+
const market = this.definitions.get(marketKey(input));
|
|
490
|
+
if (!market) {
|
|
491
|
+
throw this.createError(
|
|
492
|
+
"MARKET_NOT_FOUND",
|
|
493
|
+
`Unknown market symbol: ${input.symbol}`,
|
|
494
|
+
{ exchange: input.exchange, symbol: input.symbol },
|
|
495
|
+
"market",
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return market;
|
|
500
|
+
}
|
|
501
|
+
|
|
417
502
|
private assertSupportedExchange(exchange: Exchange): void {
|
|
418
503
|
if (exchange === this.adapter.exchange) {
|
|
419
504
|
return;
|
package/src/types/market.ts
CHANGED
|
@@ -57,6 +57,33 @@ export interface MarketKeyInput {
|
|
|
57
57
|
symbol: string;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export type DecimalInput = string | number | BigNumber;
|
|
61
|
+
|
|
62
|
+
export type NormalizeOrderInputRejectReason =
|
|
63
|
+
| "price_not_positive"
|
|
64
|
+
| "amount_not_positive"
|
|
65
|
+
| "amount_below_min"
|
|
66
|
+
| "notional_below_min";
|
|
67
|
+
|
|
68
|
+
export interface NormalizeOrderInputInput extends MarketKeyInput {
|
|
69
|
+
price: DecimalInput;
|
|
70
|
+
amount: DecimalInput;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface NormalizedOrderInput {
|
|
74
|
+
price: string;
|
|
75
|
+
amount: string;
|
|
76
|
+
rawPrice: string;
|
|
77
|
+
rawAmount: string;
|
|
78
|
+
adjusted: boolean;
|
|
79
|
+
accepted: boolean;
|
|
80
|
+
rejectReason?: NormalizeOrderInputRejectReason;
|
|
81
|
+
priceStep: string;
|
|
82
|
+
amountStep: string;
|
|
83
|
+
minAmount?: string;
|
|
84
|
+
minNotional?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
60
87
|
export interface SubscribeL1BookInput extends MarketKeyInput {}
|
|
61
88
|
|
|
62
89
|
export interface SubscribeFundingRateInput extends MarketKeyInput {}
|
|
@@ -144,6 +171,7 @@ export interface MarketManager {
|
|
|
144
171
|
getMarket(exchange: Exchange, symbol: string): MarketDefinition | undefined;
|
|
145
172
|
getMarkets(symbol: string): MarketDefinition[];
|
|
146
173
|
listMarkets(exchange?: Exchange): MarketDefinition[];
|
|
174
|
+
normalizeOrderInput(input: NormalizeOrderInputInput): NormalizedOrderInput;
|
|
147
175
|
getL1Book(key: MarketKeyInput): L1Book | undefined;
|
|
148
176
|
getL1Books(symbol: string): L1Book[];
|
|
149
177
|
getFundingRate(key: MarketKeyInput): FundingRateSnapshot | undefined;
|
package/src/types/order.ts
CHANGED