@imbingox/acex 0.1.0-beta.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.md +65 -0
- package/dist/adapters/ccxt/aster-ccxt-adapter.d.ts +157 -0
- package/dist/adapters/ccxt/aster-ccxt-adapter.js +272 -0
- package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.d.ts +179 -0
- package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.js +537 -0
- package/dist/adapters/fake/fake-aster-adapter.d.ts +130 -0
- package/dist/adapters/fake/fake-aster-adapter.js +283 -0
- package/dist/adapters/types.d.ts +210 -0
- package/dist/adapters/types.js +1 -0
- package/dist/core/client.d.ts +37 -0
- package/dist/core/client.js +45 -0
- package/dist/core/recovery.d.ts +22 -0
- package/dist/core/recovery.js +18 -0
- package/dist/core/runtime.d.ts +26 -0
- package/dist/core/runtime.js +150 -0
- package/dist/errors/acex-error.d.ts +25 -0
- package/dist/errors/acex-error.js +54 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/managers/account-manager.d.ts +41 -0
- package/dist/managers/account-manager.js +80 -0
- package/dist/managers/market-manager.d.ts +16 -0
- package/dist/managers/market-manager.js +28 -0
- package/dist/managers/order-manager.d.ts +87 -0
- package/dist/managers/order-manager.js +122 -0
- package/dist/runtime/async-queue.d.ts +8 -0
- package/dist/runtime/async-queue.js +88 -0
- package/dist/runtime/request-id.d.ts +1 -0
- package/dist/runtime/request-id.js +5 -0
- package/dist/store/account-store.d.ts +52 -0
- package/dist/store/account-store.js +18 -0
- package/dist/store/health-store.d.ts +16 -0
- package/dist/store/health-store.js +29 -0
- package/dist/store/market-store.d.ts +42 -0
- package/dist/store/market-store.js +51 -0
- package/dist/store/order-store.d.ts +38 -0
- package/dist/store/order-store.js +49 -0
- package/dist/testing/create-fake-runtime.d.ts +5 -0
- package/dist/testing/create-fake-runtime.js +7 -0
- package/dist/types/public.d.ts +11 -0
- package/dist/types/public.js +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# acex
|
|
2
|
+
|
|
3
|
+
Bun-only trading SDK. Current official verification target: `binance`.
|
|
4
|
+
|
|
5
|
+
## Current Direction
|
|
6
|
+
|
|
7
|
+
- Official release verification now focuses on the Binance USD-M testnet path.
|
|
8
|
+
- `aster` remains in the repo as an exploration sample (via CCXT / CCXT Pro) but is not part of the current official support commitment.
|
|
9
|
+
- The Aster branch was kept to preserve the generic runtime and adapter validation work without blocking the Binance verification focus.
|
|
10
|
+
|
|
11
|
+
## Verification Direction
|
|
12
|
+
|
|
13
|
+
The current official verification path is Binance USD-M testnet. The current validation target scope consists of:
|
|
14
|
+
|
|
15
|
+
- market WebSocket connectivity
|
|
16
|
+
- private WebSocket connectivity
|
|
17
|
+
- baseline bootstrap for runtime readiness
|
|
18
|
+
- market `placeOrder`
|
|
19
|
+
- reduce-only market `placeOrder`
|
|
20
|
+
|
|
21
|
+
Run the verification script via:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun run scripts/verify-binance-usdm-testnet.ts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The current script exercises bootstrap, subscription setup, and a real demo market open / reduce-only market close round-trip.
|
|
28
|
+
|
|
29
|
+
The script expects the following environment variables for Binance USD-M testnet:
|
|
30
|
+
|
|
31
|
+
- `BINANCE_USDM_API_KEY`
|
|
32
|
+
- `BINANCE_USDM_API_SECRET`
|
|
33
|
+
- `BINANCE_USDM_SYMBOL`
|
|
34
|
+
- `BINANCE_USDM_ORDER_SIZE`
|
|
35
|
+
- `BINANCE_USDM_ORDER_PRICE`
|
|
36
|
+
|
|
37
|
+
`BINANCE_USDM_ORDER_PRICE` is currently kept as a reference configuration field for notional planning and result logging; the verification orders themselves are submitted as market orders.
|
|
38
|
+
|
|
39
|
+
## Verified Scope
|
|
40
|
+
|
|
41
|
+
- Verified exchange: `binance`
|
|
42
|
+
- Verified product: `USD-M`
|
|
43
|
+
- Verified environment: `demo trading / testnet-style`
|
|
44
|
+
- Verified date: `2026-03-31`
|
|
45
|
+
- Verified runtime paths: `subscribeL1Book`, `fetchAccountBaseline`, `fetchOpenOrdersBaseline`, market `placeOrder`, reduce-only market `placeOrder`
|
|
46
|
+
- Verified transports with direct evidence: `public WS (bookTicker)`, `private WS (runtime-level order confirmation + balance confirmation)`, `REST baseline`
|
|
47
|
+
- Supplementary low-level evidence: direct `ccxt.pro.binanceusdm.watchOrders()` and `watchBalance()` both returned during the real demo open/close verification flow on `2026-03-31`
|
|
48
|
+
- Deferred coverage: `amendOrder`, `cancelAllOrders`
|
|
49
|
+
|
|
50
|
+
## Current Caveats
|
|
51
|
+
|
|
52
|
+
- Binance `watchTicker()` is not suitable for L1 mapping on USD-M because it returns a 24h ticker payload without `bid` / `ask`; the adapter now uses `watchBidsAsks()` / `bookTicker`.
|
|
53
|
+
- `fetchOpenOrders()` without `symbol` requires `warnOnFetchOpenOrdersWithoutSymbol = false` on the CCXT Binance USD-M exchange instance.
|
|
54
|
+
- Under Bun + CCXT Pro, websocket handles can remain alive after verification. The CLI script now exits explicitly after emitting the result so the verification command terminates deterministically.
|
|
55
|
+
- A simple `place/cancel` flow was insufficient to trigger `watchBalance()` on Binance demo during the 2026-03-31 investigation; the verification script now uses a real market open followed by a reduce-only market close so balance private-WS confirmation is part of the success gate.
|
|
56
|
+
|
|
57
|
+
## Aster Exploration Notes
|
|
58
|
+
|
|
59
|
+
The repository still contains the earlier Aster verification script skeleton that can be used for exploratory checks:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
bun run scripts/verify-aster-live.ts
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The Aster assets are retained for experimentation and validation reference but are not the current official release gate.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { AdapterAccountBaseline, ExchangeCapabilities, NormalizedAccountEvent, NormalizedMarketEvent, NormalizedOrderEvent } from "../types.js";
|
|
2
|
+
interface CcxtTickerLike {
|
|
3
|
+
symbol: string;
|
|
4
|
+
bid: number | string | undefined;
|
|
5
|
+
bidVolume: number | string | undefined;
|
|
6
|
+
ask: number | string | undefined;
|
|
7
|
+
askVolume: number | string | undefined;
|
|
8
|
+
timestamp?: number;
|
|
9
|
+
}
|
|
10
|
+
interface CcxtMarketLike {
|
|
11
|
+
symbol: string;
|
|
12
|
+
base: string;
|
|
13
|
+
quote: string;
|
|
14
|
+
type: string;
|
|
15
|
+
precision: {
|
|
16
|
+
price: number;
|
|
17
|
+
amount: number;
|
|
18
|
+
};
|
|
19
|
+
active: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface CcxtOrderLike {
|
|
22
|
+
id?: string;
|
|
23
|
+
clientOrderId?: string;
|
|
24
|
+
symbol?: string;
|
|
25
|
+
side?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
status?: string;
|
|
28
|
+
amount?: number | string;
|
|
29
|
+
filled?: number | string;
|
|
30
|
+
price?: number | string;
|
|
31
|
+
timestamp?: number;
|
|
32
|
+
}
|
|
33
|
+
interface CcxtBalanceLike {
|
|
34
|
+
free?: Record<string, number | string | undefined>;
|
|
35
|
+
used?: Record<string, number | string | undefined>;
|
|
36
|
+
total?: Record<string, number | string | undefined>;
|
|
37
|
+
}
|
|
38
|
+
interface CcxtExchangeLike {
|
|
39
|
+
watchTicker(symbol: string): Promise<CcxtTickerLike>;
|
|
40
|
+
fetchMarkets(): Promise<CcxtMarketLike[]>;
|
|
41
|
+
fetchBalance(params?: Record<string, unknown>): Promise<CcxtBalanceLike>;
|
|
42
|
+
fetchOpenOrders(symbol?: string, since?: number, limit?: number, params?: Record<string, unknown>): Promise<CcxtOrderLike[]>;
|
|
43
|
+
createOrder(symbol: string, type: string, side: "buy" | "sell", amount: number, price?: number, params?: Record<string, unknown>): Promise<CcxtOrderLike>;
|
|
44
|
+
cancelOrder(id?: string, symbol?: string, params?: Record<string, unknown>): Promise<CcxtOrderLike>;
|
|
45
|
+
}
|
|
46
|
+
export interface CreateCcxtAsterAdapterInput {
|
|
47
|
+
exchange: CcxtExchangeLike;
|
|
48
|
+
}
|
|
49
|
+
export declare class CcxtAsterAdapter {
|
|
50
|
+
#private;
|
|
51
|
+
readonly exchange = "aster";
|
|
52
|
+
readonly capabilities: ExchangeCapabilities;
|
|
53
|
+
constructor(input: CreateCcxtAsterAdapterInput);
|
|
54
|
+
start(): Promise<void>;
|
|
55
|
+
stop(): Promise<void>;
|
|
56
|
+
subscribeL1Book(input: {
|
|
57
|
+
exchange: string;
|
|
58
|
+
symbol: string;
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
subscribeFundingRate(): Promise<void>;
|
|
61
|
+
watchMarketEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedMarketEvent>;
|
|
62
|
+
setMarketEventSink(sink: ((event: NormalizedMarketEvent) => void) | undefined): void;
|
|
63
|
+
watchInternalErrors(): import("../../runtime/async-queue.js").AsyncQueue<Error>;
|
|
64
|
+
subscribeOrders(): Promise<void>;
|
|
65
|
+
watchOrderEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedOrderEvent>;
|
|
66
|
+
setOrderEventSink(sink: ((event: NormalizedOrderEvent) => void) | undefined): void;
|
|
67
|
+
watchAccountEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedAccountEvent>;
|
|
68
|
+
setAccountEventSink(sink: ((event: NormalizedAccountEvent) => void) | undefined): void;
|
|
69
|
+
mapTickerToL1(symbol: string): Promise<{
|
|
70
|
+
receivedAt: number;
|
|
71
|
+
exchangeTs?: number;
|
|
72
|
+
exchange: string;
|
|
73
|
+
symbol: string;
|
|
74
|
+
bidPrice: string;
|
|
75
|
+
bidSize: string;
|
|
76
|
+
askPrice: string;
|
|
77
|
+
askSize: string;
|
|
78
|
+
}>;
|
|
79
|
+
fetchMarketInfo(): Promise<{
|
|
80
|
+
exchange: string;
|
|
81
|
+
symbol: string;
|
|
82
|
+
baseAsset: string;
|
|
83
|
+
quoteAsset: string;
|
|
84
|
+
marketType: string;
|
|
85
|
+
pricePrecision: number;
|
|
86
|
+
amountPrecision: number;
|
|
87
|
+
active: boolean;
|
|
88
|
+
}[]>;
|
|
89
|
+
fetchAccountBaseline(accountId: string): Promise<AdapterAccountBaseline>;
|
|
90
|
+
fetchOpenOrdersBaseline(accountId: string): Promise<{
|
|
91
|
+
clientOrderId?: string;
|
|
92
|
+
orderId?: string;
|
|
93
|
+
seq: number;
|
|
94
|
+
receivedAt: number;
|
|
95
|
+
updatedAt: number;
|
|
96
|
+
price?: string;
|
|
97
|
+
accountId: string;
|
|
98
|
+
exchange: string;
|
|
99
|
+
symbol: string;
|
|
100
|
+
side: "buy" | "sell";
|
|
101
|
+
type: string;
|
|
102
|
+
status: string;
|
|
103
|
+
amount: string;
|
|
104
|
+
filled: string;
|
|
105
|
+
}[]>;
|
|
106
|
+
placeOrder(input: {
|
|
107
|
+
accountId: string;
|
|
108
|
+
exchange: string;
|
|
109
|
+
symbol: string;
|
|
110
|
+
side: "buy" | "sell";
|
|
111
|
+
amount: string;
|
|
112
|
+
clientOrderId: string;
|
|
113
|
+
type: string;
|
|
114
|
+
price?: string;
|
|
115
|
+
reduceOnly?: boolean;
|
|
116
|
+
}): Promise<{
|
|
117
|
+
receivedAt: number;
|
|
118
|
+
orderId?: string;
|
|
119
|
+
clientOrderId: string;
|
|
120
|
+
}>;
|
|
121
|
+
amendOrder(input: {
|
|
122
|
+
accountId: string;
|
|
123
|
+
exchange: string;
|
|
124
|
+
clientOrderId?: string;
|
|
125
|
+
orderId?: string;
|
|
126
|
+
symbol?: string;
|
|
127
|
+
newPrice?: string;
|
|
128
|
+
}): Promise<{
|
|
129
|
+
receivedAt: number;
|
|
130
|
+
orderId?: string;
|
|
131
|
+
clientOrderId?: string;
|
|
132
|
+
}>;
|
|
133
|
+
cancelOrder(input: {
|
|
134
|
+
accountId: string;
|
|
135
|
+
exchange: string;
|
|
136
|
+
clientOrderId?: string;
|
|
137
|
+
orderId?: string;
|
|
138
|
+
}): Promise<{
|
|
139
|
+
receivedAt: number;
|
|
140
|
+
orderId?: string;
|
|
141
|
+
clientOrderId?: string;
|
|
142
|
+
}>;
|
|
143
|
+
cancelAllOrders(input: {
|
|
144
|
+
accountId: string;
|
|
145
|
+
exchange: string;
|
|
146
|
+
}): Promise<{
|
|
147
|
+
accountId: string;
|
|
148
|
+
exchange: string;
|
|
149
|
+
canceledCount: number;
|
|
150
|
+
}>;
|
|
151
|
+
getHealth(): {
|
|
152
|
+
exchange: string;
|
|
153
|
+
status: string;
|
|
154
|
+
wsConnected: boolean;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export {};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { createAsyncQueue } from "../../runtime/async-queue.js";
|
|
2
|
+
export class CcxtAsterAdapter {
|
|
3
|
+
exchange = "aster";
|
|
4
|
+
capabilities = {
|
|
5
|
+
publicWs: true,
|
|
6
|
+
privateWs: true,
|
|
7
|
+
l1BookStream: true,
|
|
8
|
+
fundingRateStream: false,
|
|
9
|
+
accountStream: true,
|
|
10
|
+
orderStream: true,
|
|
11
|
+
fetchMarketInfo: true,
|
|
12
|
+
fetchBalances: true,
|
|
13
|
+
fetchPositions: false,
|
|
14
|
+
fetchRisk: false,
|
|
15
|
+
fetchOpenOrders: true,
|
|
16
|
+
fetchMyTrades: false,
|
|
17
|
+
fetchOrderById: false,
|
|
18
|
+
};
|
|
19
|
+
#exchange;
|
|
20
|
+
#started = false;
|
|
21
|
+
#marketQueue = createAsyncQueue({ maxBufferSize: 100 });
|
|
22
|
+
#marketEventSink;
|
|
23
|
+
#internalErrorQueue = createAsyncQueue({ maxBufferSize: 20 });
|
|
24
|
+
#orderQueue = createAsyncQueue({ maxBufferSize: 100 });
|
|
25
|
+
#orderEventSink;
|
|
26
|
+
#accountQueue = createAsyncQueue({ maxBufferSize: 100 });
|
|
27
|
+
#accountEventSink;
|
|
28
|
+
constructor(input) {
|
|
29
|
+
this.#exchange = input.exchange;
|
|
30
|
+
}
|
|
31
|
+
async start() {
|
|
32
|
+
this.#started = true;
|
|
33
|
+
}
|
|
34
|
+
async stop() {
|
|
35
|
+
this.#started = false;
|
|
36
|
+
this.#marketQueue.close();
|
|
37
|
+
this.#internalErrorQueue.close();
|
|
38
|
+
this.#orderQueue.close();
|
|
39
|
+
this.#accountQueue.close();
|
|
40
|
+
}
|
|
41
|
+
async subscribeL1Book(input) {
|
|
42
|
+
try {
|
|
43
|
+
const snapshot = await this.mapTickerToL1(input.symbol);
|
|
44
|
+
this.#emitMarketEvent({
|
|
45
|
+
type: "l1_book.updated",
|
|
46
|
+
exchange: snapshot.exchange,
|
|
47
|
+
symbol: snapshot.symbol,
|
|
48
|
+
bidPrice: snapshot.bidPrice,
|
|
49
|
+
bidSize: snapshot.bidSize,
|
|
50
|
+
askPrice: snapshot.askPrice,
|
|
51
|
+
askSize: snapshot.askSize,
|
|
52
|
+
...(snapshot.exchangeTs === undefined ? {} : { exchangeTs: snapshot.exchangeTs }),
|
|
53
|
+
receivedAt: snapshot.receivedAt,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.#emitInternalError(asError(error));
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async subscribeFundingRate() { }
|
|
62
|
+
watchMarketEvents() {
|
|
63
|
+
return this.#marketQueue;
|
|
64
|
+
}
|
|
65
|
+
setMarketEventSink(sink) {
|
|
66
|
+
this.#marketEventSink = sink;
|
|
67
|
+
}
|
|
68
|
+
watchInternalErrors() {
|
|
69
|
+
return this.#internalErrorQueue;
|
|
70
|
+
}
|
|
71
|
+
async subscribeOrders() { }
|
|
72
|
+
watchOrderEvents() {
|
|
73
|
+
return this.#orderQueue;
|
|
74
|
+
}
|
|
75
|
+
setOrderEventSink(sink) {
|
|
76
|
+
this.#orderEventSink = sink;
|
|
77
|
+
}
|
|
78
|
+
watchAccountEvents() {
|
|
79
|
+
return this.#accountQueue;
|
|
80
|
+
}
|
|
81
|
+
setAccountEventSink(sink) {
|
|
82
|
+
this.#accountEventSink = sink;
|
|
83
|
+
}
|
|
84
|
+
async mapTickerToL1(symbol) {
|
|
85
|
+
const ticker = await this.#exchange.watchTicker(symbol);
|
|
86
|
+
if (ticker.bid === undefined ||
|
|
87
|
+
ticker.bidVolume === undefined ||
|
|
88
|
+
ticker.ask === undefined ||
|
|
89
|
+
ticker.askVolume === undefined) {
|
|
90
|
+
throw new Error("missing required ticker fields");
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
exchange: this.exchange,
|
|
94
|
+
symbol: ticker.symbol,
|
|
95
|
+
bidPrice: String(ticker.bid),
|
|
96
|
+
bidSize: String(ticker.bidVolume),
|
|
97
|
+
askPrice: String(ticker.ask),
|
|
98
|
+
askSize: String(ticker.askVolume),
|
|
99
|
+
...(ticker.timestamp === undefined ? {} : { exchangeTs: ticker.timestamp }),
|
|
100
|
+
receivedAt: Date.now(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async fetchMarketInfo() {
|
|
104
|
+
const markets = await this.#exchange.fetchMarkets();
|
|
105
|
+
return markets.map((market) => {
|
|
106
|
+
return {
|
|
107
|
+
exchange: this.exchange,
|
|
108
|
+
symbol: market.symbol,
|
|
109
|
+
baseAsset: market.base,
|
|
110
|
+
quoteAsset: market.quote,
|
|
111
|
+
marketType: market.type,
|
|
112
|
+
pricePrecision: market.precision.price,
|
|
113
|
+
amountPrecision: market.precision.amount,
|
|
114
|
+
active: market.active,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async fetchAccountBaseline(accountId) {
|
|
119
|
+
const balance = await this.#exchange.fetchBalance();
|
|
120
|
+
const assets = new Set([
|
|
121
|
+
...Object.keys(balance.free ?? {}),
|
|
122
|
+
...Object.keys(balance.used ?? {}),
|
|
123
|
+
...Object.keys(balance.total ?? {}),
|
|
124
|
+
]);
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
const balances = Object.fromEntries([...assets].map((asset) => {
|
|
127
|
+
return [
|
|
128
|
+
asset,
|
|
129
|
+
{
|
|
130
|
+
accountId,
|
|
131
|
+
exchange: this.exchange,
|
|
132
|
+
asset,
|
|
133
|
+
free: valueToString(balance.free?.[asset]),
|
|
134
|
+
used: valueToString(balance.used?.[asset]),
|
|
135
|
+
total: valueToString(balance.total?.[asset]),
|
|
136
|
+
seq: 0,
|
|
137
|
+
receivedAt: now,
|
|
138
|
+
updatedAt: now,
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
}));
|
|
142
|
+
return {
|
|
143
|
+
balances,
|
|
144
|
+
positions: [],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async fetchOpenOrdersBaseline(accountId) {
|
|
148
|
+
const orders = await this.#exchange.fetchOpenOrders();
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
return orders.map((order) => {
|
|
151
|
+
const symbol = requiredString(order.symbol, "symbol");
|
|
152
|
+
const side = requiredOrderSide(order.side);
|
|
153
|
+
const type = requiredString(order.type, "type");
|
|
154
|
+
const status = requiredString(order.status, "status");
|
|
155
|
+
const amount = valueToRequiredString(order.amount, "amount");
|
|
156
|
+
const filled = valueToRequiredString(order.filled, "filled");
|
|
157
|
+
const updatedAt = order.timestamp ?? now;
|
|
158
|
+
return {
|
|
159
|
+
accountId,
|
|
160
|
+
exchange: this.exchange,
|
|
161
|
+
symbol,
|
|
162
|
+
side,
|
|
163
|
+
type,
|
|
164
|
+
status,
|
|
165
|
+
amount,
|
|
166
|
+
filled,
|
|
167
|
+
...(order.price === undefined ? {} : { price: String(order.price) }),
|
|
168
|
+
seq: 0,
|
|
169
|
+
receivedAt: now,
|
|
170
|
+
updatedAt,
|
|
171
|
+
...(order.id === undefined ? {} : { orderId: order.id }),
|
|
172
|
+
...(order.clientOrderId === undefined ? {} : { clientOrderId: order.clientOrderId }),
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async placeOrder(input) {
|
|
177
|
+
const amount = parseFiniteNumber(input.amount, "amount");
|
|
178
|
+
const price = input.price === undefined ? undefined : parseFiniteNumber(input.price, "price");
|
|
179
|
+
const order = await this.#exchange.createOrder(input.symbol, input.type, input.side, amount, price, {
|
|
180
|
+
clientOrderId: input.clientOrderId,
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
...(order.clientOrderId === undefined
|
|
184
|
+
? { clientOrderId: input.clientOrderId }
|
|
185
|
+
: { clientOrderId: order.clientOrderId }),
|
|
186
|
+
...(order.id === undefined ? {} : { orderId: order.id }),
|
|
187
|
+
receivedAt: Date.now(),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
async amendOrder(input) {
|
|
191
|
+
return {
|
|
192
|
+
...(input.clientOrderId === undefined ? {} : { clientOrderId: input.clientOrderId }),
|
|
193
|
+
...(input.orderId === undefined ? {} : { orderId: input.orderId }),
|
|
194
|
+
receivedAt: Date.now(),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async cancelOrder(input) {
|
|
198
|
+
if (input.orderId === undefined && input.clientOrderId === undefined) {
|
|
199
|
+
return { receivedAt: Date.now() };
|
|
200
|
+
}
|
|
201
|
+
const order = await this.#exchange.cancelOrder(input.orderId, undefined, {
|
|
202
|
+
...(input.clientOrderId === undefined ? {} : { clientOrderId: input.clientOrderId }),
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
...(order.clientOrderId === undefined
|
|
206
|
+
? input.clientOrderId === undefined
|
|
207
|
+
? {}
|
|
208
|
+
: { clientOrderId: input.clientOrderId }
|
|
209
|
+
: { clientOrderId: order.clientOrderId }),
|
|
210
|
+
...(order.id === undefined ? {} : { orderId: order.id }),
|
|
211
|
+
receivedAt: Date.now(),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async cancelAllOrders(input) {
|
|
215
|
+
return {
|
|
216
|
+
accountId: input.accountId,
|
|
217
|
+
exchange: input.exchange,
|
|
218
|
+
canceledCount: 0,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
getHealth() {
|
|
222
|
+
return {
|
|
223
|
+
exchange: this.exchange,
|
|
224
|
+
status: this.#started ? "healthy" : "idle",
|
|
225
|
+
wsConnected: this.#started,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
#emitMarketEvent(event) {
|
|
229
|
+
if (this.#marketEventSink !== undefined) {
|
|
230
|
+
this.#marketEventSink(event);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this.#marketQueue.push(event);
|
|
234
|
+
}
|
|
235
|
+
#emitInternalError(error) {
|
|
236
|
+
this.#internalErrorQueue.push(error);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function asError(error) {
|
|
240
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
241
|
+
}
|
|
242
|
+
function requiredString(value, field) {
|
|
243
|
+
if (value === undefined || value.trim().length === 0) {
|
|
244
|
+
throw new Error(`missing required order fields: ${field}`);
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
function requiredOrderSide(value) {
|
|
249
|
+
if (value !== "buy" && value !== "sell") {
|
|
250
|
+
throw new Error("missing required order fields: side");
|
|
251
|
+
}
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
function valueToRequiredString(value, field) {
|
|
255
|
+
if (value === undefined) {
|
|
256
|
+
throw new Error(`missing required order fields: ${field}`);
|
|
257
|
+
}
|
|
258
|
+
return String(value);
|
|
259
|
+
}
|
|
260
|
+
function valueToString(value) {
|
|
261
|
+
return value === undefined ? "0" : String(value);
|
|
262
|
+
}
|
|
263
|
+
function parseFiniteNumber(value, field) {
|
|
264
|
+
if (value.trim().length === 0) {
|
|
265
|
+
throw new Error(`invalid order ${field}`);
|
|
266
|
+
}
|
|
267
|
+
const parsed = Number(value);
|
|
268
|
+
if (!Number.isFinite(parsed)) {
|
|
269
|
+
throw new Error(`invalid order ${field}`);
|
|
270
|
+
}
|
|
271
|
+
return parsed;
|
|
272
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { AdapterAccountBaseline, ExchangeAdapter, ExchangeCapabilities, NormalizedAccountEvent, NormalizedMarketEvent, NormalizedOrderEvent } from "../types.js";
|
|
2
|
+
interface CcxtTickerLike {
|
|
3
|
+
symbol: string;
|
|
4
|
+
bid?: number | string;
|
|
5
|
+
bidVolume?: number | string;
|
|
6
|
+
ask?: number | string;
|
|
7
|
+
askVolume?: number | string;
|
|
8
|
+
timestamp?: number;
|
|
9
|
+
}
|
|
10
|
+
type CcxtBidAskMap = Record<string, CcxtTickerLike | undefined>;
|
|
11
|
+
interface CcxtMarketLike {
|
|
12
|
+
symbol: string;
|
|
13
|
+
base: string;
|
|
14
|
+
quote: string;
|
|
15
|
+
type: string;
|
|
16
|
+
precision: {
|
|
17
|
+
price: number;
|
|
18
|
+
amount: number;
|
|
19
|
+
};
|
|
20
|
+
active: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface CcxtBalanceLike {
|
|
23
|
+
free?: Record<string, number | string | undefined>;
|
|
24
|
+
used?: Record<string, number | string | undefined>;
|
|
25
|
+
total?: Record<string, number | string | undefined>;
|
|
26
|
+
info?: {
|
|
27
|
+
totalWalletBalance?: number | string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface CcxtPositionLike {
|
|
31
|
+
symbol?: string;
|
|
32
|
+
side?: string;
|
|
33
|
+
contracts?: number | string;
|
|
34
|
+
amount?: number | string;
|
|
35
|
+
positionAmt?: number | string;
|
|
36
|
+
timestamp?: number;
|
|
37
|
+
}
|
|
38
|
+
interface CcxtOrderLike {
|
|
39
|
+
symbol?: string;
|
|
40
|
+
side?: string;
|
|
41
|
+
type?: string;
|
|
42
|
+
status?: string;
|
|
43
|
+
amount?: number | string;
|
|
44
|
+
filled?: number | string;
|
|
45
|
+
price?: number | string;
|
|
46
|
+
timestamp?: number;
|
|
47
|
+
id?: string;
|
|
48
|
+
clientOrderId?: string;
|
|
49
|
+
}
|
|
50
|
+
interface CcxtCreateOrderResult {
|
|
51
|
+
id?: string;
|
|
52
|
+
clientOrderId?: string;
|
|
53
|
+
}
|
|
54
|
+
interface CcxtExchangeLike {
|
|
55
|
+
watchBidsAsks(symbols?: string[]): Promise<CcxtBidAskMap>;
|
|
56
|
+
fetchMarkets(): Promise<CcxtMarketLike[]>;
|
|
57
|
+
fetchBalance(params?: Record<string, unknown>): Promise<CcxtBalanceLike>;
|
|
58
|
+
fetchPositions(symbols: string[], params?: Record<string, unknown>): Promise<CcxtPositionLike[]>;
|
|
59
|
+
fetchOpenOrders(symbol?: string, since?: number, limit?: number, params?: Record<string, unknown>): Promise<CcxtOrderLike[]>;
|
|
60
|
+
createOrder(symbol: string, type: string, side: "buy" | "sell", amount: number, price?: number, params?: Record<string, unknown>): Promise<CcxtCreateOrderResult>;
|
|
61
|
+
close?(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
export interface CreateCcxtBinanceUsdMAdapterInput {
|
|
64
|
+
exchange: CcxtExchangeLike;
|
|
65
|
+
}
|
|
66
|
+
export declare class CcxtBinanceUsdMAdapter implements ExchangeAdapter {
|
|
67
|
+
#private;
|
|
68
|
+
readonly exchange = "binance";
|
|
69
|
+
readonly capabilities: ExchangeCapabilities;
|
|
70
|
+
constructor(input: CreateCcxtBinanceUsdMAdapterInput);
|
|
71
|
+
start(): Promise<void>;
|
|
72
|
+
stop(): Promise<void>;
|
|
73
|
+
subscribeL1Book(input: {
|
|
74
|
+
exchange: string;
|
|
75
|
+
symbol: string;
|
|
76
|
+
}): Promise<void>;
|
|
77
|
+
subscribeFundingRate(_input: {
|
|
78
|
+
exchange: string;
|
|
79
|
+
symbol: string;
|
|
80
|
+
}): Promise<void>;
|
|
81
|
+
watchMarketEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedMarketEvent>;
|
|
82
|
+
setMarketEventSink(sink: ((event: NormalizedMarketEvent) => void) | undefined): void;
|
|
83
|
+
watchInternalErrors(): import("../../runtime/async-queue.js").AsyncQueue<Error>;
|
|
84
|
+
subscribeOrders(_input: {
|
|
85
|
+
accountId: string;
|
|
86
|
+
}): Promise<void>;
|
|
87
|
+
watchOrderEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedOrderEvent>;
|
|
88
|
+
setOrderEventSink(sink: ((event: NormalizedOrderEvent) => void) | undefined): void;
|
|
89
|
+
watchAccountEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedAccountEvent>;
|
|
90
|
+
setAccountEventSink(sink: ((event: NormalizedAccountEvent) => void) | undefined): void;
|
|
91
|
+
mapTickerToL1(symbol: string): Promise<{
|
|
92
|
+
receivedAt: number;
|
|
93
|
+
exchangeTs?: number;
|
|
94
|
+
exchange: string;
|
|
95
|
+
symbol: string;
|
|
96
|
+
bidPrice: string;
|
|
97
|
+
bidSize: string;
|
|
98
|
+
askPrice: string;
|
|
99
|
+
askSize: string;
|
|
100
|
+
}>;
|
|
101
|
+
fetchMarketInfo(): Promise<{
|
|
102
|
+
exchange: string;
|
|
103
|
+
symbol: string;
|
|
104
|
+
baseAsset: string;
|
|
105
|
+
quoteAsset: string;
|
|
106
|
+
marketType: string;
|
|
107
|
+
pricePrecision: number;
|
|
108
|
+
amountPrecision: number;
|
|
109
|
+
active: boolean;
|
|
110
|
+
}[]>;
|
|
111
|
+
fetchOpenOrdersBaseline(accountId: string): Promise<{
|
|
112
|
+
clientOrderId?: string;
|
|
113
|
+
orderId?: string;
|
|
114
|
+
seq: number;
|
|
115
|
+
receivedAt: number;
|
|
116
|
+
updatedAt: number;
|
|
117
|
+
price?: string;
|
|
118
|
+
accountId: string;
|
|
119
|
+
exchange: string;
|
|
120
|
+
symbol: string;
|
|
121
|
+
side: "buy" | "sell";
|
|
122
|
+
type: string;
|
|
123
|
+
status: string;
|
|
124
|
+
amount: string;
|
|
125
|
+
filled: string;
|
|
126
|
+
}[]>;
|
|
127
|
+
fetchAccountBaseline(accountId: string): Promise<AdapterAccountBaseline>;
|
|
128
|
+
placeOrder(input: {
|
|
129
|
+
accountId: string;
|
|
130
|
+
exchange: string;
|
|
131
|
+
symbol: string;
|
|
132
|
+
side: "buy" | "sell";
|
|
133
|
+
amount: string;
|
|
134
|
+
clientOrderId: string;
|
|
135
|
+
type: string;
|
|
136
|
+
price?: string;
|
|
137
|
+
reduceOnly?: boolean;
|
|
138
|
+
}): Promise<{
|
|
139
|
+
receivedAt: number;
|
|
140
|
+
orderId?: string;
|
|
141
|
+
clientOrderId: string;
|
|
142
|
+
}>;
|
|
143
|
+
amendOrder(input: {
|
|
144
|
+
accountId: string;
|
|
145
|
+
exchange: string;
|
|
146
|
+
clientOrderId?: string;
|
|
147
|
+
orderId?: string;
|
|
148
|
+
symbol?: string;
|
|
149
|
+
newPrice?: string;
|
|
150
|
+
}): Promise<{
|
|
151
|
+
receivedAt: number;
|
|
152
|
+
orderId?: string;
|
|
153
|
+
clientOrderId?: string;
|
|
154
|
+
}>;
|
|
155
|
+
cancelOrder(input: {
|
|
156
|
+
accountId: string;
|
|
157
|
+
exchange: string;
|
|
158
|
+
clientOrderId?: string;
|
|
159
|
+
orderId?: string;
|
|
160
|
+
}): Promise<{
|
|
161
|
+
receivedAt: number;
|
|
162
|
+
orderId?: string;
|
|
163
|
+
clientOrderId?: string;
|
|
164
|
+
}>;
|
|
165
|
+
cancelAllOrders(_input: {
|
|
166
|
+
accountId: string;
|
|
167
|
+
exchange: string;
|
|
168
|
+
}): Promise<{
|
|
169
|
+
accountId: string;
|
|
170
|
+
exchange: string;
|
|
171
|
+
canceledCount: number;
|
|
172
|
+
}>;
|
|
173
|
+
getHealth(): {
|
|
174
|
+
exchange: string;
|
|
175
|
+
status: string;
|
|
176
|
+
wsConnected: boolean;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export {};
|