@imbingox/acex 0.4.0-beta.6 → 0.4.0-beta.7
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/package.json +1 -1
- package/src/adapters/binance/adapter.ts +9 -0
- package/src/adapters/binance/server-time.ts +106 -0
- package/src/adapters/types.ts +2 -0
- package/src/client/venue-capabilities.ts +1 -0
- package/src/errors.ts +1 -0
- package/src/managers/market-manager.ts +34 -0
- package/src/types/client.ts +1 -0
- package/src/types/market.ts +14 -0
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
MarketDefinition,
|
|
4
4
|
RateLimiter,
|
|
5
5
|
VenueMarketCapabilities,
|
|
6
|
+
VenueServerTime,
|
|
6
7
|
} from "../../types/index.ts";
|
|
7
8
|
import type {
|
|
8
9
|
FundingRateStreamCallbacks,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
type BinanceMarketDefinition,
|
|
17
18
|
loadBinanceMarkets,
|
|
18
19
|
} from "./market-catalog.ts";
|
|
20
|
+
import { fetchBinanceServerTime } from "./server-time.ts";
|
|
19
21
|
import {
|
|
20
22
|
type BinanceStreamDescriptor,
|
|
21
23
|
type BinanceStreamMessage,
|
|
@@ -45,6 +47,7 @@ export class BinanceMarketAdapter implements MarketAdapter {
|
|
|
45
47
|
readonly venue = "binance" as const;
|
|
46
48
|
readonly marketCapabilities: VenueMarketCapabilities = {
|
|
47
49
|
catalog: "supported",
|
|
50
|
+
serverTime: "supported",
|
|
48
51
|
l1Book: "supported",
|
|
49
52
|
fundingRate: "market_dependent",
|
|
50
53
|
marketTypes: ["spot", "swap", "future"],
|
|
@@ -73,6 +76,12 @@ export class BinanceMarketAdapter implements MarketAdapter {
|
|
|
73
76
|
return markets;
|
|
74
77
|
}
|
|
75
78
|
|
|
79
|
+
async fetchServerTime(): Promise<VenueServerTime> {
|
|
80
|
+
return await fetchBinanceServerTime({
|
|
81
|
+
rateLimiter: this.options.rateLimiter,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
createL1BookStream(
|
|
77
86
|
market: MarketDefinition,
|
|
78
87
|
callbacks: L1BookStreamCallbacks,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type HttpClientMessages,
|
|
3
|
+
httpRequest,
|
|
4
|
+
isTransportError,
|
|
5
|
+
} from "../../internal/http-client.ts";
|
|
6
|
+
import type {
|
|
7
|
+
RateLimiter,
|
|
8
|
+
RateLimitScope,
|
|
9
|
+
VenueServerTime,
|
|
10
|
+
} from "../../types/index.ts";
|
|
11
|
+
import { parseBinanceRateLimitUsage } from "./rate-limit.ts";
|
|
12
|
+
|
|
13
|
+
type FetchLike = (
|
|
14
|
+
input: string | URL | Request,
|
|
15
|
+
init?: RequestInit,
|
|
16
|
+
) => Promise<Response>;
|
|
17
|
+
|
|
18
|
+
interface BinanceServerTimeResponse {
|
|
19
|
+
serverTime?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FetchBinanceServerTimeOptions {
|
|
23
|
+
readonly rateLimiter?: RateLimiter;
|
|
24
|
+
readonly fetchFn?: FetchLike;
|
|
25
|
+
readonly now?: () => number;
|
|
26
|
+
readonly monotonicNow?: () => number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const BINANCE_USDM_SERVER_TIME_URL = "https://fapi.binance.com/fapi/v1/time";
|
|
30
|
+
const DEFAULT_HTTP_TIMEOUT_MS = 10_000;
|
|
31
|
+
const BINANCE_SERVER_TIME_HTTP_MESSAGES: HttpClientMessages = {
|
|
32
|
+
http: ({ status, statusText }) =>
|
|
33
|
+
`Binance server time request failed: ${status} ${statusText ?? ""}`,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export async function fetchBinanceServerTime(
|
|
37
|
+
options: FetchBinanceServerTimeOptions = {},
|
|
38
|
+
): Promise<VenueServerTime> {
|
|
39
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
40
|
+
const now = options.now ?? Date.now;
|
|
41
|
+
const monotonicNow = options.monotonicNow ?? (() => performance.now());
|
|
42
|
+
const scope: RateLimitScope = {
|
|
43
|
+
venue: "binance",
|
|
44
|
+
endpointKey: "GET /fapi/v1/time",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await options.rateLimiter?.beforeRequest({ scope });
|
|
48
|
+
|
|
49
|
+
const requestSentAt = now();
|
|
50
|
+
const startMono = monotonicNow();
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const response = await httpRequest<BinanceServerTimeResponse>({
|
|
54
|
+
fetchFn,
|
|
55
|
+
url: BINANCE_USDM_SERVER_TIME_URL,
|
|
56
|
+
timeoutMs: DEFAULT_HTTP_TIMEOUT_MS,
|
|
57
|
+
parseAs: "json",
|
|
58
|
+
jsonParseMode: "response",
|
|
59
|
+
retryPolicy: {
|
|
60
|
+
idempotent: true,
|
|
61
|
+
maxAttempts: 1,
|
|
62
|
+
},
|
|
63
|
+
messages: BINANCE_SERVER_TIME_HTTP_MESSAGES,
|
|
64
|
+
});
|
|
65
|
+
const responseReceivedAt = now();
|
|
66
|
+
const endMono = monotonicNow();
|
|
67
|
+
|
|
68
|
+
await options.rateLimiter?.afterResponse(
|
|
69
|
+
{ scope },
|
|
70
|
+
{
|
|
71
|
+
status: response.status,
|
|
72
|
+
headers: response.headers,
|
|
73
|
+
usage: parseBinanceRateLimitUsage(response.headers),
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const { serverTime } = response.body;
|
|
78
|
+
if (typeof serverTime !== "number" || !Number.isFinite(serverTime)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Binance server time response missing numeric serverTime",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
serverTime,
|
|
86
|
+
requestSentAt,
|
|
87
|
+
responseReceivedAt,
|
|
88
|
+
roundTripMs: endMono - startMono,
|
|
89
|
+
estimatedOffsetMs: serverTime - (requestSentAt + responseReceivedAt) / 2,
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (isTransportError(error)) {
|
|
93
|
+
await options.rateLimiter?.onTransportError(
|
|
94
|
+
{ scope },
|
|
95
|
+
{
|
|
96
|
+
status: error.status,
|
|
97
|
+
headers: error.headers,
|
|
98
|
+
retryAfterMs: error.retryAfterMs,
|
|
99
|
+
usage: parseBinanceRateLimitUsage(error.headers),
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/adapters/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
VenueAccountCapabilities,
|
|
10
10
|
VenueMarketCapabilities,
|
|
11
11
|
VenueOrderCapabilities,
|
|
12
|
+
VenueServerTime,
|
|
12
13
|
} from "../types/index.ts";
|
|
13
14
|
|
|
14
15
|
export interface StreamHandle {
|
|
@@ -74,6 +75,7 @@ export interface MarketAdapter {
|
|
|
74
75
|
readonly venue: Venue;
|
|
75
76
|
readonly marketCapabilities: VenueMarketCapabilities;
|
|
76
77
|
loadMarkets(): Promise<MarketDefinition[]>;
|
|
78
|
+
fetchServerTime?(): Promise<VenueServerTime>;
|
|
77
79
|
createL1BookStream(
|
|
78
80
|
market: MarketDefinition,
|
|
79
81
|
callbacks: L1BookStreamCallbacks,
|
package/src/errors.ts
CHANGED
|
@@ -38,6 +38,7 @@ import type {
|
|
|
38
38
|
SubscribeL1BookInput,
|
|
39
39
|
SubscriptionActivity,
|
|
40
40
|
Venue,
|
|
41
|
+
VenueServerTime,
|
|
41
42
|
} from "../types/index.ts";
|
|
42
43
|
|
|
43
44
|
export interface MarketManagerOptions {
|
|
@@ -201,6 +202,24 @@ export class MarketManagerImpl
|
|
|
201
202
|
return summaries;
|
|
202
203
|
}
|
|
203
204
|
|
|
205
|
+
async fetchServerTime(venue: Venue): Promise<VenueServerTime> {
|
|
206
|
+
const adapter = this.getMarketAdapter(venue);
|
|
207
|
+
if (!adapter.fetchServerTime) {
|
|
208
|
+
throw this.createError(
|
|
209
|
+
"VENUE_NOT_SUPPORTED",
|
|
210
|
+
`Venue is not supported yet: ${venue}`,
|
|
211
|
+
{ venue },
|
|
212
|
+
"client",
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
return await adapter.fetchServerTime();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw this.createServerTimeFetchError(venue, error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
204
223
|
async subscribeL1Book(input: SubscribeL1BookInput): Promise<void> {
|
|
205
224
|
this.context.assertStarted();
|
|
206
225
|
const market = await this.resolveMarketDefinition(input);
|
|
@@ -601,6 +620,21 @@ export class MarketManagerImpl
|
|
|
601
620
|
return wrapped;
|
|
602
621
|
}
|
|
603
622
|
|
|
623
|
+
private createServerTimeFetchError(venue: Venue, error: unknown): AcexError {
|
|
624
|
+
const wrapped = new AcexError(
|
|
625
|
+
"MARKET_SERVER_TIME_FETCH_FAILED",
|
|
626
|
+
`Failed to fetch server time from ${venue}`,
|
|
627
|
+
);
|
|
628
|
+
this.context.publishRuntimeError(
|
|
629
|
+
"adapter",
|
|
630
|
+
error instanceof Error
|
|
631
|
+
? error
|
|
632
|
+
: new Error("Unknown server time fetch failure"),
|
|
633
|
+
{ venue },
|
|
634
|
+
);
|
|
635
|
+
return wrapped;
|
|
636
|
+
}
|
|
637
|
+
|
|
604
638
|
private async resolveMarketDefinition(input: {
|
|
605
639
|
venue: Venue;
|
|
606
640
|
symbol: string;
|
package/src/types/client.ts
CHANGED
|
@@ -83,6 +83,7 @@ export type OrderTimeInForceCapability = "gtc" | "post_only";
|
|
|
83
83
|
|
|
84
84
|
export interface VenueMarketCapabilities {
|
|
85
85
|
catalog: VenueCapabilitySupport;
|
|
86
|
+
serverTime: VenueCapabilitySupport;
|
|
86
87
|
l1Book: VenueCapabilitySupport;
|
|
87
88
|
fundingRate: FundingRateCapability;
|
|
88
89
|
marketTypes: MarketType[];
|
package/src/types/market.ts
CHANGED
|
@@ -36,6 +36,19 @@ export interface MarketCatalogReloadSummary {
|
|
|
36
36
|
error?: AcexError;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export interface VenueServerTime {
|
|
40
|
+
/** Exchange server time in epoch milliseconds. Binance currently measures the USDM cluster. */
|
|
41
|
+
serverTime: number;
|
|
42
|
+
/** Local wall-clock timestamp captured immediately before the HTTP request is sent. */
|
|
43
|
+
requestSentAt: number;
|
|
44
|
+
/** Local wall-clock timestamp captured immediately after the HTTP response is received. */
|
|
45
|
+
responseReceivedAt: number;
|
|
46
|
+
/** Round trip duration measured with a monotonic clock, in milliseconds. */
|
|
47
|
+
roundTripMs: number;
|
|
48
|
+
/** NTP-style offset estimate: serverTime - midpoint(requestSentAt, responseReceivedAt). */
|
|
49
|
+
estimatedOffsetMs: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
export interface MarketDataStatus {
|
|
40
53
|
venue: Venue;
|
|
41
54
|
symbol: string;
|
|
@@ -170,6 +183,7 @@ export interface MarketManager {
|
|
|
170
183
|
|
|
171
184
|
loadMarkets(): Promise<void>;
|
|
172
185
|
reloadMarkets(venue?: Venue): Promise<MarketCatalogReloadSummary[]>;
|
|
186
|
+
fetchServerTime(venue: Venue): Promise<VenueServerTime>;
|
|
173
187
|
subscribeL1Book(input: SubscribeL1BookInput): Promise<void>;
|
|
174
188
|
unsubscribeL1Book(input: SubscribeL1BookInput): Promise<void>;
|
|
175
189
|
subscribeFundingRate(input: SubscribeFundingRateInput): Promise<void>;
|