@tigeropenapi/tigeropen 0.1.0 → 0.3.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 +475 -104
- package/dist/cjs/client/api-request.js +16 -10
- package/dist/cjs/client/api-response.js +23 -3
- package/dist/cjs/client/case-convert.js +35 -0
- package/dist/cjs/client/http-client.js +46 -20
- package/dist/cjs/client/index.js +6 -2
- package/dist/cjs/config/client-config.js +130 -49
- package/dist/cjs/config/domain.js +21 -2
- package/dist/cjs/config/index.js +3 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/model/contract.js +1 -2
- package/dist/cjs/model/order-helpers.js +9 -9
- package/dist/cjs/model/order.js +5 -2
- package/dist/cjs/model/position.js +1 -2
- package/dist/cjs/model/quote.js +7 -0
- package/dist/cjs/model/trade.js +2 -0
- package/dist/cjs/push/index.js +6 -3
- package/dist/cjs/push/pb/AssetData.js +208 -0
- package/dist/cjs/push/pb/KlineData.js +184 -0
- package/dist/cjs/push/pb/OptionTopData.js +427 -0
- package/dist/cjs/push/pb/OrderStatusData.js +568 -0
- package/dist/cjs/push/pb/OrderTransactionData.js +256 -0
- package/dist/cjs/push/pb/PositionData.js +316 -0
- package/dist/cjs/push/pb/PushData.js +219 -0
- package/dist/cjs/push/pb/QuoteBBOData.js +160 -0
- package/dist/cjs/push/pb/QuoteBasicData.js +329 -0
- package/dist/cjs/push/pb/QuoteData.js +507 -0
- package/dist/cjs/push/pb/QuoteDepthData.js +220 -0
- package/dist/cjs/push/pb/Request.js +278 -0
- package/dist/cjs/push/pb/Response.js +97 -0
- package/dist/cjs/push/pb/SocketCommon.js +86 -0
- package/dist/cjs/push/pb/StockTopData.js +199 -0
- package/dist/cjs/push/pb/TickData.js +202 -0
- package/dist/cjs/push/pb/TradeTickData.js +308 -0
- package/dist/cjs/push/pb/index.js +49 -0
- package/dist/cjs/push/proto-message.js +129 -0
- package/dist/cjs/push/push-client.js +292 -164
- package/dist/cjs/push/push-message.js +4 -60
- package/dist/cjs/push/varint.js +76 -0
- package/dist/cjs/quote/index.js +3 -2
- package/dist/cjs/quote/quote-client.js +128 -94
- package/dist/cjs/signer/signer.js +10 -10
- package/dist/cjs/trade/trade-client.js +56 -62
- package/dist/esm/client/api-request.js +14 -8
- package/dist/esm/client/api-response.js +22 -3
- package/dist/esm/client/case-convert.js +31 -0
- package/dist/esm/client/http-client.js +47 -21
- package/dist/esm/client/index.js +3 -2
- package/dist/esm/config/client-config.js +131 -51
- package/dist/esm/config/domain.js +20 -2
- package/dist/esm/config/index.js +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/model/contract.js +1 -2
- package/dist/esm/model/order-helpers.js +9 -9
- package/dist/esm/model/order.js +5 -2
- package/dist/esm/model/position.js +1 -2
- package/dist/esm/model/quote.js +6 -0
- package/dist/esm/model/trade.js +1 -0
- package/dist/esm/push/index.js +4 -2
- package/dist/esm/push/pb/AssetData.js +205 -0
- package/dist/esm/push/pb/KlineData.js +181 -0
- package/dist/esm/push/pb/OptionTopData.js +424 -0
- package/dist/esm/push/pb/OrderStatusData.js +565 -0
- package/dist/esm/push/pb/OrderTransactionData.js +253 -0
- package/dist/esm/push/pb/PositionData.js +313 -0
- package/dist/esm/push/pb/PushData.js +216 -0
- package/dist/esm/push/pb/QuoteBBOData.js +157 -0
- package/dist/esm/push/pb/QuoteBasicData.js +326 -0
- package/dist/esm/push/pb/QuoteData.js +504 -0
- package/dist/esm/push/pb/QuoteDepthData.js +217 -0
- package/dist/esm/push/pb/Request.js +275 -0
- package/dist/esm/push/pb/Response.js +94 -0
- package/dist/esm/push/pb/SocketCommon.js +83 -0
- package/dist/esm/push/pb/StockTopData.js +196 -0
- package/dist/esm/push/pb/TickData.js +199 -0
- package/dist/esm/push/pb/TradeTickData.js +305 -0
- package/dist/esm/push/pb/index.js +25 -0
- package/dist/esm/push/proto-message.js +121 -0
- package/dist/esm/push/push-client.js +260 -165
- package/dist/esm/push/push-message.js +3 -56
- package/dist/esm/push/varint.js +72 -0
- package/dist/esm/quote/index.js +2 -2
- package/dist/esm/quote/quote-client.js +127 -94
- package/dist/esm/signer/signer.js +10 -10
- package/dist/esm/trade/trade-client.js +56 -62
- package/dist/types/client/api-request.d.ts +12 -11
- package/dist/types/client/api-response.d.ts +15 -7
- package/dist/types/client/case-convert.d.ts +16 -0
- package/dist/types/client/http-client.d.ts +26 -14
- package/dist/types/client/index.d.ts +3 -2
- package/dist/types/config/client-config.d.ts +29 -13
- package/dist/types/config/domain.d.ts +9 -0
- package/dist/types/config/index.d.ts +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model/contract.d.ts +13 -29
- package/dist/types/model/order-helpers.d.ts +21 -20
- package/dist/types/model/order.d.ts +101 -55
- package/dist/types/model/position.d.ts +26 -24
- package/dist/types/model/quote.d.ts +311 -0
- package/dist/types/model/trade.d.ts +113 -0
- package/dist/types/push/callbacks.d.ts +32 -51
- package/dist/types/push/index.d.ts +5 -3
- package/dist/types/push/pb/AssetData.d.ts +47 -0
- package/dist/types/push/pb/KlineData.d.ts +43 -0
- package/dist/types/push/pb/OptionTopData.d.ts +76 -0
- package/dist/types/push/pb/OrderStatusData.d.ts +96 -0
- package/dist/types/push/pb/OrderTransactionData.d.ts +51 -0
- package/dist/types/push/pb/PositionData.d.ts +63 -0
- package/dist/types/push/pb/PushData.d.ts +46 -0
- package/dist/types/push/pb/QuoteBBOData.d.ts +35 -0
- package/dist/types/push/pb/QuoteBasicData.d.ts +66 -0
- package/dist/types/push/pb/QuoteData.d.ts +94 -0
- package/dist/types/push/pb/QuoteDepthData.d.ts +37 -0
- package/dist/types/push/pb/Request.d.ts +44 -0
- package/dist/types/push/pb/Response.d.ts +30 -0
- package/dist/types/push/pb/SocketCommon.d.ts +64 -0
- package/dist/types/push/pb/StockTopData.d.ts +37 -0
- package/dist/types/push/pb/TickData.d.ts +44 -0
- package/dist/types/push/pb/TradeTickData.d.ts +53 -0
- package/dist/types/push/pb/index.d.ts +39 -0
- package/dist/types/push/proto-message.d.ts +33 -0
- package/dist/types/push/push-client.d.ts +69 -68
- package/dist/types/push/push-message.d.ts +3 -147
- package/dist/types/push/varint.d.ts +23 -0
- package/dist/types/quote/index.d.ts +2 -2
- package/dist/types/quote/quote-client.d.ts +41 -47
- package/dist/types/signer/signer.d.ts +6 -6
- package/dist/types/trade/trade-client.d.ts +34 -38
- package/package.json +11 -6
|
@@ -1,16 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PushClient - TCP + TLS push client (Protobuf protocol)
|
|
3
|
+
*
|
|
4
|
+
* Connects to the push server via raw TCP + TLS to receive real-time
|
|
5
|
+
* market data and account push notifications.
|
|
6
|
+
* Uses varint32 length-prefixed + Protobuf binary frame format,
|
|
7
|
+
* aligned with the Java SDK's Netty + Protobuf implementation.
|
|
8
|
+
* Supports connection authentication, subscribe/unsubscribe, callbacks,
|
|
9
|
+
* heartbeat keep-alive, and automatic reconnection.
|
|
10
|
+
*/
|
|
11
|
+
import * as tls from 'tls';
|
|
1
12
|
import { signWithRSA } from '../signer/signer';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
13
|
+
import { SubjectType } from './push-message';
|
|
14
|
+
import { Request } from './pb/Request';
|
|
15
|
+
import { Response } from './pb/Response';
|
|
16
|
+
import { SocketCommon_Command, SocketCommon_DataType } from './pb/SocketCommon';
|
|
17
|
+
import { encodeVarint32, decodeVarint32 } from './varint';
|
|
18
|
+
import { buildConnectMessage, buildHeartBeatMessage, buildSubscribeMessage, buildUnSubscribeMessage, buildDisconnectMessage, subjectToDataType, } from './proto-message';
|
|
19
|
+
/** Default push server host */
|
|
20
|
+
const DEFAULT_PUSH_HOST = 'openapi.tigerfintech.com';
|
|
21
|
+
/** Default push server port */
|
|
22
|
+
const DEFAULT_PUSH_PORT = 9883;
|
|
23
|
+
/** Default heartbeat interval (ms) */
|
|
6
24
|
const DEFAULT_HEARTBEAT_INTERVAL = 10000;
|
|
7
|
-
/**
|
|
25
|
+
/** Default reconnect interval (ms) */
|
|
8
26
|
const DEFAULT_RECONNECT_INTERVAL = 5000;
|
|
9
|
-
/**
|
|
27
|
+
/** Max reconnect interval (ms) */
|
|
10
28
|
const MAX_RECONNECT_INTERVAL = 60000;
|
|
11
|
-
/**
|
|
29
|
+
/** Default connect timeout (ms) */
|
|
12
30
|
const DEFAULT_CONNECT_TIMEOUT = 30000;
|
|
13
|
-
/**
|
|
31
|
+
/** SDK version identifier */
|
|
32
|
+
const SDK_VERSION = 'typescript/0.1.0';
|
|
33
|
+
/** Protocol version */
|
|
34
|
+
const ACCEPT_VERSION = '1.0';
|
|
35
|
+
/** Default send heartbeat interval (ms) */
|
|
36
|
+
const DEFAULT_SEND_INTERVAL = 10000;
|
|
37
|
+
/** Default receive heartbeat interval (ms) */
|
|
38
|
+
const DEFAULT_RECEIVE_INTERVAL = 10000;
|
|
39
|
+
/** Connection state */
|
|
14
40
|
export var ConnectionState;
|
|
15
41
|
(function (ConnectionState) {
|
|
16
42
|
ConnectionState[ConnectionState["Disconnected"] = 0] = "Disconnected";
|
|
@@ -18,151 +44,220 @@ export var ConnectionState;
|
|
|
18
44
|
ConnectionState[ConnectionState["Connected"] = 2] = "Connected";
|
|
19
45
|
})(ConnectionState || (ConnectionState = {}));
|
|
20
46
|
/**
|
|
21
|
-
* PushClient
|
|
47
|
+
* PushClient - TCP + TLS push client (Protobuf protocol)
|
|
22
48
|
*/
|
|
23
49
|
export class PushClient {
|
|
24
50
|
constructor(config, options) {
|
|
25
|
-
this.
|
|
51
|
+
this.socket = null;
|
|
26
52
|
this._state = ConnectionState.Disconnected;
|
|
27
53
|
this.callbacks = {};
|
|
28
|
-
/**
|
|
54
|
+
/** Subscription state: subject -> Set<symbol> */
|
|
29
55
|
this.subscriptions = new Map();
|
|
30
|
-
/**
|
|
56
|
+
/** Account-level subscriptions */
|
|
31
57
|
this.accountSubs = new Set();
|
|
32
58
|
this.heartbeatTimer = null;
|
|
33
59
|
this.reconnectTimer = null;
|
|
34
60
|
this.stopped = false;
|
|
35
|
-
/**
|
|
36
|
-
this.
|
|
61
|
+
/** TCP stream receive buffer */
|
|
62
|
+
this.recvBuffer = Buffer.alloc(0);
|
|
63
|
+
/** Injectable TLS socket factory (for testing) */
|
|
64
|
+
this.socketFactory = null;
|
|
37
65
|
this.config = config;
|
|
38
|
-
this.
|
|
66
|
+
this.pushHost = options?.pushHost ?? DEFAULT_PUSH_HOST;
|
|
67
|
+
this.pushPort = options?.pushPort ?? DEFAULT_PUSH_PORT;
|
|
39
68
|
this.heartbeatInterval = options?.heartbeatInterval ?? DEFAULT_HEARTBEAT_INTERVAL;
|
|
40
69
|
this.reconnectInterval = options?.reconnectInterval ?? DEFAULT_RECONNECT_INTERVAL;
|
|
41
70
|
this.autoReconnect = options?.autoReconnect ?? true;
|
|
42
71
|
this.connectTimeout = options?.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
|
|
72
|
+
this.useFullTick = options?.useFullTick ?? false;
|
|
43
73
|
}
|
|
44
|
-
/**
|
|
74
|
+
/** Get current connection state */
|
|
45
75
|
get state() {
|
|
46
76
|
return this._state;
|
|
47
77
|
}
|
|
48
|
-
/**
|
|
78
|
+
/** Set callback functions */
|
|
49
79
|
setCallbacks(cb) {
|
|
50
80
|
this.callbacks = cb;
|
|
51
81
|
}
|
|
52
|
-
/**
|
|
82
|
+
/** Connect to the push server and authenticate */
|
|
53
83
|
connect() {
|
|
54
84
|
return new Promise((resolve, reject) => {
|
|
55
85
|
if (this._state !== ConnectionState.Disconnected) {
|
|
56
|
-
reject(new Error('
|
|
86
|
+
reject(new Error('Client is already connected or connecting'));
|
|
57
87
|
return;
|
|
58
88
|
}
|
|
59
89
|
this._state = ConnectionState.Connecting;
|
|
60
90
|
this.stopped = false;
|
|
61
|
-
|
|
62
|
-
this.
|
|
91
|
+
this.recvBuffer = Buffer.alloc(0);
|
|
92
|
+
const socket = this.createSocket(this.pushHost, this.pushPort);
|
|
93
|
+
this.socket = socket;
|
|
94
|
+
let settled = false;
|
|
63
95
|
const timeout = setTimeout(() => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
96
|
+
if (!settled) {
|
|
97
|
+
settled = true;
|
|
98
|
+
socket.destroy();
|
|
99
|
+
this._state = ConnectionState.Disconnected;
|
|
100
|
+
this.socket = null;
|
|
101
|
+
reject(new Error('Connect timeout'));
|
|
102
|
+
}
|
|
68
103
|
}, this.connectTimeout);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
104
|
+
// Resolve once we receive CONNECTED response from server
|
|
105
|
+
const onConnected = () => {
|
|
106
|
+
if (!settled) {
|
|
107
|
+
settled = true;
|
|
108
|
+
clearTimeout(timeout);
|
|
74
109
|
this._state = ConnectionState.Connected;
|
|
75
110
|
this.startHeartbeat();
|
|
76
111
|
this.callbacks.onConnect?.();
|
|
77
112
|
resolve();
|
|
78
113
|
}
|
|
114
|
+
};
|
|
115
|
+
socket.on('connect', () => {
|
|
116
|
+
// TLS handshake complete, send authentication message
|
|
117
|
+
try {
|
|
118
|
+
this.authenticate();
|
|
119
|
+
}
|
|
79
120
|
catch (err) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
121
|
+
if (!settled) {
|
|
122
|
+
settled = true;
|
|
123
|
+
clearTimeout(timeout);
|
|
124
|
+
socket.destroy();
|
|
125
|
+
this._state = ConnectionState.Disconnected;
|
|
126
|
+
this.socket = null;
|
|
127
|
+
reject(err);
|
|
128
|
+
}
|
|
84
129
|
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
};
|
|
89
|
-
|
|
130
|
+
});
|
|
131
|
+
socket.on('data', (chunk) => {
|
|
132
|
+
this.handleData(chunk, onConnected);
|
|
133
|
+
});
|
|
134
|
+
socket.on('close', () => {
|
|
90
135
|
if (this.stopped)
|
|
91
136
|
return;
|
|
92
137
|
this.stopHeartbeat();
|
|
93
138
|
const wasConnected = this._state === ConnectionState.Connected;
|
|
94
139
|
this._state = ConnectionState.Disconnected;
|
|
95
|
-
this.
|
|
140
|
+
this.socket = null;
|
|
96
141
|
if (wasConnected && this.autoReconnect && !this.stopped) {
|
|
97
142
|
this.scheduleReconnect();
|
|
98
143
|
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (
|
|
144
|
+
});
|
|
145
|
+
socket.on('error', (err) => {
|
|
146
|
+
if (!settled) {
|
|
147
|
+
settled = true;
|
|
102
148
|
clearTimeout(timeout);
|
|
103
149
|
this._state = ConnectionState.Disconnected;
|
|
104
|
-
this.
|
|
105
|
-
reject(new Error(
|
|
150
|
+
this.socket = null;
|
|
151
|
+
reject(new Error(`TLS connection failed: ${err.message}`));
|
|
106
152
|
}
|
|
107
153
|
else {
|
|
108
|
-
this.callbacks.onError?.(new Error(
|
|
154
|
+
this.callbacks.onError?.(new Error(`TLS error: ${err.message}`));
|
|
109
155
|
}
|
|
110
|
-
};
|
|
156
|
+
});
|
|
111
157
|
});
|
|
112
158
|
}
|
|
113
|
-
/**
|
|
159
|
+
/** Disconnect from the push server */
|
|
114
160
|
disconnect() {
|
|
115
161
|
this.stopped = true;
|
|
116
162
|
this.stopHeartbeat();
|
|
117
163
|
this.clearReconnectTimer();
|
|
118
|
-
if (this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
164
|
+
if (this.socket) {
|
|
165
|
+
// Send DISCONNECT message before closing
|
|
166
|
+
try {
|
|
167
|
+
const req = buildDisconnectMessage();
|
|
168
|
+
this.sendMessage(req);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Ignore send failure, continue closing
|
|
172
|
+
}
|
|
173
|
+
this.socket.destroy();
|
|
174
|
+
this.socket = null;
|
|
124
175
|
}
|
|
125
176
|
this._state = ConnectionState.Disconnected;
|
|
177
|
+
this.recvBuffer = Buffer.alloc(0);
|
|
126
178
|
this.callbacks.onDisconnect?.();
|
|
127
179
|
}
|
|
128
|
-
/**
|
|
180
|
+
/** Send authentication message */
|
|
129
181
|
authenticate() {
|
|
130
|
-
|
|
182
|
+
// Sign content is the tigerId string itself (aligned with Java SDK)
|
|
131
183
|
const sign = signWithRSA(this.config.privateKey, this.config.tigerId);
|
|
132
|
-
const req =
|
|
133
|
-
|
|
134
|
-
sign,
|
|
135
|
-
timestamp: ts,
|
|
136
|
-
version: '2.0',
|
|
137
|
-
};
|
|
138
|
-
const msg = createPushMessage(MessageType.Connect, undefined, req);
|
|
139
|
-
this.sendMessage(msg);
|
|
184
|
+
const req = buildConnectMessage(this.config.tigerId, sign, SDK_VERSION, ACCEPT_VERSION, DEFAULT_SEND_INTERVAL, DEFAULT_RECEIVE_INTERVAL, this.useFullTick);
|
|
185
|
+
this.sendMessage(req);
|
|
140
186
|
}
|
|
141
|
-
/**
|
|
187
|
+
/** Send a Protobuf Request message (varint32 length prefix + binary) */
|
|
142
188
|
sendMessage(msg) {
|
|
143
|
-
if (!this.
|
|
144
|
-
throw new Error('
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
if (!this.socket)
|
|
190
|
+
throw new Error('TLS connection not established');
|
|
191
|
+
const encoded = Request.encode(msg).finish();
|
|
192
|
+
const framed = encodeVarint32(encoded);
|
|
193
|
+
this.socket.write(framed);
|
|
194
|
+
}
|
|
195
|
+
/** Create a TLS socket instance */
|
|
196
|
+
createSocket(host, port) {
|
|
197
|
+
if (this.socketFactory)
|
|
198
|
+
return this.socketFactory(host, port);
|
|
199
|
+
return tls.connect({ host, port, rejectUnauthorized: false });
|
|
200
|
+
}
|
|
201
|
+
/** Handle incoming TCP data: buffer + decode varint32 frames */
|
|
202
|
+
handleData(chunk, onConnected) {
|
|
203
|
+
this.recvBuffer = Buffer.concat([this.recvBuffer, chunk]);
|
|
204
|
+
while (this.recvBuffer.length > 0) {
|
|
205
|
+
const frame = decodeVarint32(new Uint8Array(this.recvBuffer));
|
|
206
|
+
if (!frame)
|
|
207
|
+
break; // Incomplete frame, wait for more data
|
|
208
|
+
this.recvBuffer = Buffer.from(frame.remaining);
|
|
209
|
+
// Deserialize Protobuf Response
|
|
210
|
+
let response;
|
|
211
|
+
try {
|
|
212
|
+
response = Response.decode(frame.message);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
this.callbacks.onError?.(new Error('Protobuf deserialization failed'));
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
this.handleResponse(response, onConnected);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** Handle a decoded Response message */
|
|
222
|
+
handleResponse(response, onConnected) {
|
|
223
|
+
const cb = this.callbacks;
|
|
224
|
+
switch (response.command) {
|
|
225
|
+
case SocketCommon_Command.CONNECTED:
|
|
226
|
+
// Server confirmed authentication
|
|
227
|
+
onConnected?.();
|
|
228
|
+
break;
|
|
229
|
+
case SocketCommon_Command.HEARTBEAT:
|
|
230
|
+
// Heartbeat response, ignore
|
|
231
|
+
break;
|
|
232
|
+
case SocketCommon_Command.MESSAGE:
|
|
233
|
+
if (response.body) {
|
|
234
|
+
this.dispatchPushData(response.body);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case SocketCommon_Command.ERROR:
|
|
238
|
+
cb.onError?.(new Error(`Server error: ${response.msg ?? 'unknown error'}`));
|
|
239
|
+
// Check for kickout
|
|
240
|
+
if (response.msg && response.msg.toLowerCase().includes('kickout')) {
|
|
241
|
+
cb.onKickout?.(response.msg);
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case SocketCommon_Command.DISCONNECT:
|
|
245
|
+
cb.onDisconnect?.();
|
|
246
|
+
break;
|
|
247
|
+
default:
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Heartbeat keep-alive */
|
|
157
252
|
startHeartbeat() {
|
|
158
253
|
this.stopHeartbeat();
|
|
159
254
|
this.heartbeatTimer = setInterval(() => {
|
|
160
255
|
try {
|
|
161
|
-
const
|
|
162
|
-
this.sendMessage(
|
|
256
|
+
const req = buildHeartBeatMessage();
|
|
257
|
+
this.sendMessage(req);
|
|
163
258
|
}
|
|
164
259
|
catch {
|
|
165
|
-
//
|
|
260
|
+
// Heartbeat send failed, connection may be broken
|
|
166
261
|
}
|
|
167
262
|
}, this.heartbeatInterval);
|
|
168
263
|
}
|
|
@@ -172,7 +267,7 @@ export class PushClient {
|
|
|
172
267
|
this.heartbeatTimer = null;
|
|
173
268
|
}
|
|
174
269
|
}
|
|
175
|
-
/**
|
|
270
|
+
/** Auto reconnect with exponential backoff */
|
|
176
271
|
scheduleReconnect() {
|
|
177
272
|
let interval = this.reconnectInterval;
|
|
178
273
|
const attempt = () => {
|
|
@@ -195,7 +290,7 @@ export class PushClient {
|
|
|
195
290
|
this.reconnectTimer = null;
|
|
196
291
|
}
|
|
197
292
|
}
|
|
198
|
-
/**
|
|
293
|
+
/** Resubscribe after reconnection */
|
|
199
294
|
resubscribe() {
|
|
200
295
|
for (const [subject, symbols] of this.subscriptions) {
|
|
201
296
|
this.doSubscribe(subject, Array.from(symbols));
|
|
@@ -204,81 +299,81 @@ export class PushClient {
|
|
|
204
299
|
this.doSubscribe(subject, undefined, this.config.account);
|
|
205
300
|
}
|
|
206
301
|
}
|
|
207
|
-
/**
|
|
208
|
-
|
|
209
|
-
let msg;
|
|
210
|
-
try {
|
|
211
|
-
msg = deserializeMessage(raw);
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
this.callbacks.onError?.(new Error('反序列化消息失败'));
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
302
|
+
/** Dispatch push data to the appropriate callback based on dataType */
|
|
303
|
+
dispatchPushData(pushData) {
|
|
217
304
|
const cb = this.callbacks;
|
|
218
|
-
switch (
|
|
219
|
-
case
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
case MessageType.Error:
|
|
223
|
-
cb.onError?.(new Error(`服务端错误: ${msg.data}`));
|
|
224
|
-
break;
|
|
225
|
-
case MessageType.Quote:
|
|
226
|
-
cb.onQuote?.(msg.data);
|
|
227
|
-
break;
|
|
228
|
-
case MessageType.Tick:
|
|
229
|
-
cb.onTick?.(msg.data);
|
|
305
|
+
switch (pushData.dataType) {
|
|
306
|
+
case SocketCommon_DataType.Quote:
|
|
307
|
+
if (pushData.quoteData)
|
|
308
|
+
cb.onQuote?.(pushData.quoteData);
|
|
230
309
|
break;
|
|
231
|
-
case
|
|
232
|
-
|
|
310
|
+
case SocketCommon_DataType.Option:
|
|
311
|
+
if (pushData.quoteData)
|
|
312
|
+
cb.onOption?.(pushData.quoteData);
|
|
233
313
|
break;
|
|
234
|
-
case
|
|
235
|
-
|
|
314
|
+
case SocketCommon_DataType.Future:
|
|
315
|
+
if (pushData.quoteData)
|
|
316
|
+
cb.onFuture?.(pushData.quoteData);
|
|
236
317
|
break;
|
|
237
|
-
case
|
|
238
|
-
|
|
318
|
+
case SocketCommon_DataType.QuoteDepth:
|
|
319
|
+
if (pushData.quoteDepthData)
|
|
320
|
+
cb.onDepth?.(pushData.quoteDepthData);
|
|
239
321
|
break;
|
|
240
|
-
case
|
|
241
|
-
|
|
322
|
+
case SocketCommon_DataType.TradeTick:
|
|
323
|
+
if (pushData.tradeTickData)
|
|
324
|
+
cb.onTick?.(pushData.tradeTickData);
|
|
242
325
|
break;
|
|
243
|
-
case
|
|
244
|
-
|
|
326
|
+
case SocketCommon_DataType.Asset:
|
|
327
|
+
if (pushData.assetData)
|
|
328
|
+
cb.onAsset?.(pushData.assetData);
|
|
245
329
|
break;
|
|
246
|
-
case
|
|
247
|
-
|
|
330
|
+
case SocketCommon_DataType.Position:
|
|
331
|
+
if (pushData.positionData)
|
|
332
|
+
cb.onPosition?.(pushData.positionData);
|
|
248
333
|
break;
|
|
249
|
-
case
|
|
250
|
-
|
|
334
|
+
case SocketCommon_DataType.OrderStatus:
|
|
335
|
+
if (pushData.orderStatusData)
|
|
336
|
+
cb.onOrder?.(pushData.orderStatusData);
|
|
251
337
|
break;
|
|
252
|
-
case
|
|
253
|
-
|
|
338
|
+
case SocketCommon_DataType.OrderTransaction:
|
|
339
|
+
if (pushData.orderTransactionData)
|
|
340
|
+
cb.onTransaction?.(pushData.orderTransactionData);
|
|
254
341
|
break;
|
|
255
|
-
case
|
|
256
|
-
|
|
342
|
+
case SocketCommon_DataType.StockTop:
|
|
343
|
+
if (pushData.stockTopData)
|
|
344
|
+
cb.onStockTop?.(pushData.stockTopData);
|
|
257
345
|
break;
|
|
258
|
-
case
|
|
259
|
-
|
|
346
|
+
case SocketCommon_DataType.OptionTop:
|
|
347
|
+
if (pushData.optionTopData)
|
|
348
|
+
cb.onOptionTop?.(pushData.optionTopData);
|
|
260
349
|
break;
|
|
261
|
-
case
|
|
262
|
-
|
|
350
|
+
case SocketCommon_DataType.Kline:
|
|
351
|
+
if (pushData.klineData)
|
|
352
|
+
cb.onKline?.(pushData.klineData);
|
|
263
353
|
break;
|
|
264
|
-
|
|
265
|
-
|
|
354
|
+
default:
|
|
355
|
+
// FullTick and QuoteBBO use TradeTick/Quote dataType
|
|
356
|
+
// TickData (fullTick) via tickData field
|
|
357
|
+
if (pushData.tickData)
|
|
358
|
+
cb.onFullTick?.(pushData.tickData);
|
|
266
359
|
break;
|
|
267
360
|
}
|
|
268
361
|
}
|
|
269
|
-
/**
|
|
362
|
+
/** Internal subscribe method */
|
|
270
363
|
doSubscribe(subject, symbols, account, market) {
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
364
|
+
const dataType = subjectToDataType(subject);
|
|
365
|
+
const symbolsStr = symbols ? symbols.join(',') : undefined;
|
|
366
|
+
const req = buildSubscribeMessage(dataType, symbolsStr, account, market);
|
|
367
|
+
this.sendMessage(req);
|
|
274
368
|
}
|
|
275
|
-
/**
|
|
369
|
+
/** Internal unsubscribe method */
|
|
276
370
|
doUnsubscribe(subject, symbols) {
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
371
|
+
const dataType = subjectToDataType(subject);
|
|
372
|
+
const symbolsStr = symbols ? symbols.join(',') : undefined;
|
|
373
|
+
const req = buildUnSubscribeMessage(dataType, symbolsStr);
|
|
374
|
+
this.sendMessage(req);
|
|
280
375
|
}
|
|
281
|
-
/**
|
|
376
|
+
/** Add subscription record */
|
|
282
377
|
addSubscription(subject, symbols) {
|
|
283
378
|
if (!this.subscriptions.has(subject)) {
|
|
284
379
|
this.subscriptions.set(subject, new Set());
|
|
@@ -287,7 +382,7 @@ export class PushClient {
|
|
|
287
382
|
for (const s of symbols)
|
|
288
383
|
set.add(s);
|
|
289
384
|
}
|
|
290
|
-
/**
|
|
385
|
+
/** Remove subscription record */
|
|
291
386
|
removeSubscription(subject, symbols) {
|
|
292
387
|
if (!symbols) {
|
|
293
388
|
this.subscriptions.delete(subject);
|
|
@@ -301,110 +396,110 @@ export class PushClient {
|
|
|
301
396
|
this.subscriptions.delete(subject);
|
|
302
397
|
}
|
|
303
398
|
}
|
|
304
|
-
// =====
|
|
305
|
-
/**
|
|
399
|
+
// ===== Market data subscribe/unsubscribe =====
|
|
400
|
+
/** Subscribe to quotes */
|
|
306
401
|
subscribeQuote(symbols) {
|
|
307
402
|
this.doSubscribe(SubjectType.Quote, symbols);
|
|
308
403
|
this.addSubscription(SubjectType.Quote, symbols);
|
|
309
404
|
}
|
|
310
|
-
/**
|
|
405
|
+
/** Unsubscribe from quotes */
|
|
311
406
|
unsubscribeQuote(symbols) {
|
|
312
407
|
this.doUnsubscribe(SubjectType.Quote, symbols);
|
|
313
408
|
this.removeSubscription(SubjectType.Quote, symbols);
|
|
314
409
|
}
|
|
315
|
-
/**
|
|
410
|
+
/** Subscribe to trade ticks */
|
|
316
411
|
subscribeTick(symbols) {
|
|
317
412
|
this.doSubscribe(SubjectType.Tick, symbols);
|
|
318
413
|
this.addSubscription(SubjectType.Tick, symbols);
|
|
319
414
|
}
|
|
320
|
-
/**
|
|
415
|
+
/** Unsubscribe from trade ticks */
|
|
321
416
|
unsubscribeTick(symbols) {
|
|
322
417
|
this.doUnsubscribe(SubjectType.Tick, symbols);
|
|
323
418
|
this.removeSubscription(SubjectType.Tick, symbols);
|
|
324
419
|
}
|
|
325
|
-
/**
|
|
420
|
+
/** Subscribe to depth data */
|
|
326
421
|
subscribeDepth(symbols) {
|
|
327
422
|
this.doSubscribe(SubjectType.Depth, symbols);
|
|
328
423
|
this.addSubscription(SubjectType.Depth, symbols);
|
|
329
424
|
}
|
|
330
|
-
/**
|
|
425
|
+
/** Unsubscribe from depth data */
|
|
331
426
|
unsubscribeDepth(symbols) {
|
|
332
427
|
this.doUnsubscribe(SubjectType.Depth, symbols);
|
|
333
428
|
this.removeSubscription(SubjectType.Depth, symbols);
|
|
334
429
|
}
|
|
335
|
-
/**
|
|
430
|
+
/** Subscribe to option quotes */
|
|
336
431
|
subscribeOption(symbols) {
|
|
337
432
|
this.doSubscribe(SubjectType.Option, symbols);
|
|
338
433
|
this.addSubscription(SubjectType.Option, symbols);
|
|
339
434
|
}
|
|
340
|
-
/**
|
|
435
|
+
/** Unsubscribe from option quotes */
|
|
341
436
|
unsubscribeOption(symbols) {
|
|
342
437
|
this.doUnsubscribe(SubjectType.Option, symbols);
|
|
343
438
|
this.removeSubscription(SubjectType.Option, symbols);
|
|
344
439
|
}
|
|
345
|
-
/**
|
|
440
|
+
/** Subscribe to futures quotes */
|
|
346
441
|
subscribeFuture(symbols) {
|
|
347
442
|
this.doSubscribe(SubjectType.Future, symbols);
|
|
348
443
|
this.addSubscription(SubjectType.Future, symbols);
|
|
349
444
|
}
|
|
350
|
-
/**
|
|
445
|
+
/** Unsubscribe from futures quotes */
|
|
351
446
|
unsubscribeFuture(symbols) {
|
|
352
447
|
this.doUnsubscribe(SubjectType.Future, symbols);
|
|
353
448
|
this.removeSubscription(SubjectType.Future, symbols);
|
|
354
449
|
}
|
|
355
|
-
/**
|
|
450
|
+
/** Subscribe to kline data */
|
|
356
451
|
subscribeKline(symbols) {
|
|
357
452
|
this.doSubscribe(SubjectType.Kline, symbols);
|
|
358
453
|
this.addSubscription(SubjectType.Kline, symbols);
|
|
359
454
|
}
|
|
360
|
-
/**
|
|
455
|
+
/** Unsubscribe from kline data */
|
|
361
456
|
unsubscribeKline(symbols) {
|
|
362
457
|
this.doUnsubscribe(SubjectType.Kline, symbols);
|
|
363
458
|
this.removeSubscription(SubjectType.Kline, symbols);
|
|
364
459
|
}
|
|
365
|
-
// =====
|
|
366
|
-
/**
|
|
460
|
+
// ===== Account push subscribe/unsubscribe =====
|
|
461
|
+
/** Subscribe to asset changes */
|
|
367
462
|
subscribeAsset(account) {
|
|
368
463
|
this.doSubscribe(SubjectType.Asset, undefined, account || this.config.account);
|
|
369
464
|
this.accountSubs.add(SubjectType.Asset);
|
|
370
465
|
}
|
|
371
|
-
/**
|
|
466
|
+
/** Unsubscribe from asset changes */
|
|
372
467
|
unsubscribeAsset() {
|
|
373
468
|
this.doUnsubscribe(SubjectType.Asset);
|
|
374
469
|
this.accountSubs.delete(SubjectType.Asset);
|
|
375
470
|
}
|
|
376
|
-
/**
|
|
471
|
+
/** Subscribe to position changes */
|
|
377
472
|
subscribePosition(account) {
|
|
378
473
|
this.doSubscribe(SubjectType.Position, undefined, account || this.config.account);
|
|
379
474
|
this.accountSubs.add(SubjectType.Position);
|
|
380
475
|
}
|
|
381
|
-
/**
|
|
476
|
+
/** Unsubscribe from position changes */
|
|
382
477
|
unsubscribePosition() {
|
|
383
478
|
this.doUnsubscribe(SubjectType.Position);
|
|
384
479
|
this.accountSubs.delete(SubjectType.Position);
|
|
385
480
|
}
|
|
386
|
-
/**
|
|
481
|
+
/** Subscribe to order status */
|
|
387
482
|
subscribeOrder(account) {
|
|
388
483
|
this.doSubscribe(SubjectType.Order, undefined, account || this.config.account);
|
|
389
484
|
this.accountSubs.add(SubjectType.Order);
|
|
390
485
|
}
|
|
391
|
-
/**
|
|
486
|
+
/** Unsubscribe from order status */
|
|
392
487
|
unsubscribeOrder() {
|
|
393
488
|
this.doUnsubscribe(SubjectType.Order);
|
|
394
489
|
this.accountSubs.delete(SubjectType.Order);
|
|
395
490
|
}
|
|
396
|
-
/**
|
|
491
|
+
/** Subscribe to transaction details */
|
|
397
492
|
subscribeTransaction(account) {
|
|
398
493
|
this.doSubscribe(SubjectType.Transaction, undefined, account || this.config.account);
|
|
399
494
|
this.accountSubs.add(SubjectType.Transaction);
|
|
400
495
|
}
|
|
401
|
-
/**
|
|
496
|
+
/** Unsubscribe from transaction details */
|
|
402
497
|
unsubscribeTransaction() {
|
|
403
498
|
this.doUnsubscribe(SubjectType.Transaction);
|
|
404
499
|
this.accountSubs.delete(SubjectType.Transaction);
|
|
405
500
|
}
|
|
406
|
-
// =====
|
|
407
|
-
/**
|
|
501
|
+
// ===== Query subscription state =====
|
|
502
|
+
/** Get current market data subscriptions */
|
|
408
503
|
getSubscriptions() {
|
|
409
504
|
const result = new Map();
|
|
410
505
|
for (const [subject, symbols] of this.subscriptions) {
|
|
@@ -412,7 +507,7 @@ export class PushClient {
|
|
|
412
507
|
}
|
|
413
508
|
return result;
|
|
414
509
|
}
|
|
415
|
-
/**
|
|
510
|
+
/** Get account-level subscriptions */
|
|
416
511
|
getAccountSubscriptions() {
|
|
417
512
|
return Array.from(this.accountSubs);
|
|
418
513
|
}
|