@tigeropenapi/tigeropen 0.5.4 → 0.5.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.
Files changed (73) hide show
  1. package/README.md +80 -0
  2. package/dist/cjs/auth/authentication.js +74 -0
  3. package/dist/cjs/auth/index.js +24 -0
  4. package/dist/cjs/auth/oauth2/client-registration.js +44 -0
  5. package/dist/cjs/auth/oauth2/device-authorization.js +40 -0
  6. package/dist/cjs/auth/oauth2/errors.js +64 -0
  7. package/dist/cjs/auth/oauth2/http-utils.js +112 -0
  8. package/dist/cjs/auth/oauth2/index.js +42 -0
  9. package/dist/cjs/auth/oauth2/loopback-receiver.js +215 -0
  10. package/dist/cjs/auth/oauth2/metadata.js +34 -0
  11. package/dist/cjs/auth/oauth2/oauth2-authentication.js +74 -0
  12. package/dist/cjs/auth/oauth2/session-manager.js +762 -0
  13. package/dist/cjs/auth/oauth2/token-store.js +252 -0
  14. package/dist/cjs/auth/oauth2/token.js +76 -0
  15. package/dist/cjs/push/index.js +1 -0
  16. package/dist/cjs/push/pb/Request.js +12 -1
  17. package/dist/cjs/push/proto-message.js +5 -1
  18. package/dist/cjs/push/push-client.js +34 -16
  19. package/dist/cjs/push/tick-util.js +172 -0
  20. package/dist/cjs/quote/quote-client.js +59 -12
  21. package/dist/cjs/trade/trade-client.js +55 -6
  22. package/dist/cjs/version.js +1 -1
  23. package/dist/esm/auth/authentication.js +68 -0
  24. package/dist/esm/auth/index.js +3 -0
  25. package/dist/esm/auth/oauth2/client-registration.js +40 -0
  26. package/dist/esm/auth/oauth2/device-authorization.js +36 -0
  27. package/dist/esm/auth/oauth2/errors.js +59 -0
  28. package/dist/esm/auth/oauth2/http-utils.js +105 -0
  29. package/dist/esm/auth/oauth2/index.js +11 -0
  30. package/dist/esm/auth/oauth2/loopback-receiver.js +178 -0
  31. package/dist/esm/auth/oauth2/metadata.js +30 -0
  32. package/dist/esm/auth/oauth2/oauth2-authentication.js +70 -0
  33. package/dist/esm/auth/oauth2/session-manager.js +725 -0
  34. package/dist/esm/auth/oauth2/token-store.js +214 -0
  35. package/dist/esm/auth/oauth2/token.js +71 -0
  36. package/dist/esm/package.json +3 -0
  37. package/dist/esm/push/index.js +1 -0
  38. package/dist/esm/push/pb/Request.js +12 -1
  39. package/dist/esm/push/proto-message.js +5 -1
  40. package/dist/esm/push/push-client.js +34 -16
  41. package/dist/esm/push/tick-util.js +165 -0
  42. package/dist/esm/quote/quote-client.js +59 -12
  43. package/dist/esm/trade/trade-client.js +55 -6
  44. package/dist/esm/version.js +1 -1
  45. package/dist/types/auth/authentication.d.ts +98 -0
  46. package/dist/types/auth/index.d.ts +4 -0
  47. package/dist/types/auth/oauth2/client-registration.d.ts +32 -0
  48. package/dist/types/auth/oauth2/device-authorization.d.ts +40 -0
  49. package/dist/types/auth/oauth2/errors.d.ts +46 -0
  50. package/dist/types/auth/oauth2/http-utils.d.ts +21 -0
  51. package/dist/types/auth/oauth2/index.d.ts +16 -0
  52. package/dist/types/auth/oauth2/loopback-receiver.d.ts +29 -0
  53. package/dist/types/auth/oauth2/metadata.d.ts +32 -0
  54. package/dist/types/auth/oauth2/oauth2-authentication.d.ts +31 -0
  55. package/dist/types/auth/oauth2/session-manager.d.ts +241 -0
  56. package/dist/types/auth/oauth2/token-store.d.ts +49 -0
  57. package/dist/types/auth/oauth2/token.d.ts +56 -0
  58. package/dist/types/index.d.ts +2 -2
  59. package/dist/types/model/order.d.ts +62 -11
  60. package/dist/types/model/quote-requests.d.ts +21 -2
  61. package/dist/types/model/quote.d.ts +72 -14
  62. package/dist/types/model/trade-requests.d.ts +17 -4
  63. package/dist/types/model/trade.d.ts +11 -1
  64. package/dist/types/push/callbacks.d.ts +3 -7
  65. package/dist/types/push/index.d.ts +2 -1
  66. package/dist/types/push/pb/Request.d.ts +1 -0
  67. package/dist/types/push/proto-message.d.ts +4 -1
  68. package/dist/types/push/push-client.d.ts +33 -0
  69. package/dist/types/push/tick-util.d.ts +30 -0
  70. package/dist/types/quote/quote-client.d.ts +27 -6
  71. package/dist/types/trade/trade-client.d.ts +27 -3
  72. package/dist/types/version.d.ts +1 -1
  73. package/package.json +8 -4
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.OAuth2TokenStore = exports.DEFAULT_SURFACE = exports.ENV_HOME = void 0;
37
+ exports.hashIssuer = hashIssuer;
38
+ /**
39
+ * File-backed OAuth 2.0 authorization state.
40
+ *
41
+ * Storage layout:
42
+ *
43
+ * ~/.tiger/openapi/
44
+ * tokens/{clientId}.json authorization state, mode 0600, removed on logout
45
+ * clients/{issuerHash}-{surface}.json dynamic registration, retained across logout
46
+ *
47
+ * Registration files are keyed by issuer because the client ID is not yet available
48
+ * during lookup, and by surface so that each entry point registers its own client.
49
+ * Without the surface, whichever entry point ran first would own the registration and
50
+ * every other one would silently inherit its identity: one name on the user's
51
+ * authorization page, one revocation switch for all of them, and one shared token file
52
+ * whose refresh-token rotation they would race over.
53
+ *
54
+ * Token files need no surface: each surface registers a distinct client ID, so their
55
+ * token files are already distinct. A manually configured client ID is shared on
56
+ * purpose -- one client ID means one application.
57
+ *
58
+ * Storage root precedence is: explicit argument, `TIGEROPEN_HOME`, then
59
+ * `~/.tiger/openapi`. The format is shared with the Java and Python SDKs.
60
+ */
61
+ const crypto_1 = require("crypto");
62
+ const fs = __importStar(require("fs"));
63
+ const os_1 = require("os");
64
+ const path = __importStar(require("path"));
65
+ const client_registration_1 = require("./client-registration");
66
+ const errors_1 = require("./errors");
67
+ const token_1 = require("./token");
68
+ exports.ENV_HOME = 'TIGEROPEN_HOME';
69
+ /**
70
+ * Entry point owning a registration. Tools built on this SDK must pass their own value,
71
+ * otherwise they would be filed as the TypeScript SDK and share its client. Keep in
72
+ * step with the Java SDK's `sdk-java` and the Python SDK's `sdk-python`.
73
+ */
74
+ exports.DEFAULT_SURFACE = 'sdk-typescript';
75
+ const SAFE_CHARS = /[^a-zA-Z0-9\-_.]/g;
76
+ /**
77
+ * Surfaces become part of a file name, so they are restricted rather than sanitised: a
78
+ * silently rewritten surface would file two entry points under one name.
79
+ */
80
+ const SURFACE_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
81
+ class OAuth2TokenStore {
82
+ /**
83
+ * @param home storage root; defaults to `TIGEROPEN_HOME` then `~/.tiger/openapi`
84
+ * @param surface entry point owning the registration, e.g. `sdk-typescript`, `cli`.
85
+ * Lowercase alphanumerics and hyphens only
86
+ */
87
+ constructor(home, surface = exports.DEFAULT_SURFACE) {
88
+ const base = home || process.env[exports.ENV_HOME] || path.join((0, os_1.homedir)(), '.tiger', 'openapi');
89
+ this.home = path.resolve(expandHome(base));
90
+ this.surface = validateSurface(surface);
91
+ }
92
+ /** Load a token, treating a missing file or issuer mismatch as a cache miss. */
93
+ loadToken(clientId, expectedIssuer) {
94
+ const file = this.tokenFile(clientId);
95
+ if (!fs.existsSync(file))
96
+ return null;
97
+ let token;
98
+ try {
99
+ token = token_1.OAuth2Token.fromJSON(JSON.parse(fs.readFileSync(file, 'utf-8')));
100
+ }
101
+ catch (e) {
102
+ // A corrupt file tells us nothing; treat it as no authorization rather than crash.
103
+ console.error(`[TigerOpen] read token file fail: ${file}`, e);
104
+ return null;
105
+ }
106
+ if (!token)
107
+ return null;
108
+ if (token.schemaVersion !== token_1.TOKEN_SCHEMA_VERSION) {
109
+ // Unknown schema versions are not interpreted.
110
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, `unsupported token schema_version: ${token.schemaVersion}`);
111
+ }
112
+ if (expectedIssuer !== undefined && expectedIssuer !== token.issuer) {
113
+ return null;
114
+ }
115
+ return token;
116
+ }
117
+ saveToken(token) {
118
+ if (!token || !token.clientId) {
119
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, 'token or clientId is empty, cannot save');
120
+ }
121
+ writeAtomic(this.tokenFile(token.clientId), JSON.stringify(token.toJSON(), null, 2));
122
+ }
123
+ /**
124
+ * Delete authorization state while retaining client registration.
125
+ *
126
+ * @returns the path removed, or null when there was nothing to remove. A clientId is
127
+ * not its own file name, so a caller reporting what it deleted has to be told rather
128
+ * than build the path itself
129
+ */
130
+ deleteToken(clientId) {
131
+ if (!clientId)
132
+ return null;
133
+ const file = this.tokenFile(clientId);
134
+ return deleteFile(file) ? file : null;
135
+ }
136
+ loadClientId(issuer) {
137
+ return this.loadClientRegistration(issuer)?.clientId ?? null;
138
+ }
139
+ loadClientRegistration(issuer) {
140
+ const file = this.clientFile(issuer);
141
+ if (!fs.existsSync(file))
142
+ return null;
143
+ try {
144
+ return client_registration_1.OAuth2ClientRegistration.fromJSON(JSON.parse(fs.readFileSync(file, 'utf-8')));
145
+ }
146
+ catch (e) {
147
+ console.error(`[TigerOpen] read client registration file fail: ${file}`, e);
148
+ return null;
149
+ }
150
+ }
151
+ saveClientRegistration(registration) {
152
+ if (!registration || !registration.issuer) {
153
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, 'registration or issuer is empty, cannot save');
154
+ }
155
+ writeAtomic(this.clientFile(registration.issuer), JSON.stringify(registration.toJSON(), null, 2));
156
+ }
157
+ /** Delete registration state after an `invalid_client` response. */
158
+ deleteClientRegistration(issuer) {
159
+ if (!issuer)
160
+ return;
161
+ deleteFile(this.clientFile(issuer));
162
+ }
163
+ tokenFile(clientId) {
164
+ return path.join(this.home, 'tokens', `${safeFileName(clientId)}.json`);
165
+ }
166
+ clientFile(issuer) {
167
+ return path.join(this.home, 'clients', `${hashIssuer(issuer)}-${this.surface}.json`);
168
+ }
169
+ }
170
+ exports.OAuth2TokenStore = OAuth2TokenStore;
171
+ /** Return the Java-compatible 32-character SHA-256 issuer file key. */
172
+ function hashIssuer(issuer) {
173
+ return (0, crypto_1.createHash)('sha256').update(issuer || '', 'utf-8').digest('hex').slice(0, 32);
174
+ }
175
+ /** Sanitize an untrusted client ID for use as a file name. */
176
+ function safeFileName(raw) {
177
+ let name = (raw || '').replace(SAFE_CHARS, '_');
178
+ while (name.includes('..')) {
179
+ name = name.replace(/\.\./g, '__');
180
+ }
181
+ return name || '_';
182
+ }
183
+ /** Reject a surface that cannot be used verbatim as part of a file name. */
184
+ function validateSurface(surface) {
185
+ if (typeof surface !== 'string' || !SURFACE_PATTERN.test(surface)) {
186
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, `invalid surface '${surface}': expected lowercase alphanumerics and hyphens, e.g. 'cli'`);
187
+ }
188
+ return surface;
189
+ }
190
+ function expandHome(value) {
191
+ if (value === '~')
192
+ return (0, os_1.homedir)();
193
+ if (value.startsWith('~/') || value.startsWith(`~${path.sep}`)) {
194
+ return path.join((0, os_1.homedir)(), value.slice(2));
195
+ }
196
+ return value;
197
+ }
198
+ /** @returns true when the file was there and is now gone */
199
+ function deleteFile(file) {
200
+ try {
201
+ if (fs.existsSync(file)) {
202
+ fs.unlinkSync(file);
203
+ return true;
204
+ }
205
+ }
206
+ catch (e) {
207
+ console.error(`[TigerOpen] delete file fail: ${file}`, e);
208
+ }
209
+ return false;
210
+ }
211
+ /**
212
+ * Atomically replace a file through a same-directory temporary file.
213
+ *
214
+ * Same-filesystem replacement prevents partial JSON writes. Directories and files are
215
+ * restricted to modes 0700 and 0600 where supported.
216
+ */
217
+ function writeAtomic(file, content) {
218
+ const dir = path.dirname(file);
219
+ try {
220
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
221
+ restrict(dir, 0o700);
222
+ // Same directory, so the rename below stays on one filesystem and is atomic.
223
+ const tmp = path.join(dir, `.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
224
+ try {
225
+ fs.writeFileSync(tmp, content, { encoding: 'utf-8', mode: 0o600 });
226
+ restrict(tmp, 0o600);
227
+ fs.renameSync(tmp, file);
228
+ }
229
+ catch (e) {
230
+ try {
231
+ fs.unlinkSync(tmp);
232
+ }
233
+ catch {
234
+ // The temporary file was never created; nothing to clean up.
235
+ }
236
+ throw e;
237
+ }
238
+ restrict(file, 0o600);
239
+ }
240
+ catch (e) {
241
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, `write file fail: ${file}`, e);
242
+ }
243
+ }
244
+ /** Restrict a credential file or directory where the platform supports it. */
245
+ function restrict(target, mode) {
246
+ try {
247
+ fs.chmodSync(target, mode);
248
+ }
249
+ catch {
250
+ // Windows and some network filesystems have no POSIX modes; the write still stands.
251
+ }
252
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /**
3
+ * Persisted OAuth 2.0 authorization state.
4
+ *
5
+ * The `issuer` and `clientId` bind credentials to an authorization server and client,
6
+ * preventing credential reuse across environments. The on-disk field names are
7
+ * snake_case and shared with the Java and Python SDKs.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.OAuth2Token = exports.TOKEN_SCHEMA_VERSION = void 0;
11
+ exports.currentMillis = currentMillis;
12
+ /** On-disk format version. An unrecognised version is an error, not a guess. */
13
+ exports.TOKEN_SCHEMA_VERSION = 1;
14
+ function currentMillis() {
15
+ return Date.now();
16
+ }
17
+ /** An access token and its metadata. */
18
+ class OAuth2Token {
19
+ constructor(init = {}) {
20
+ this.schemaVersion = init.schemaVersion ?? exports.TOKEN_SCHEMA_VERSION;
21
+ this.issuer = init.issuer;
22
+ this.clientId = init.clientId;
23
+ this.accessToken = init.accessToken;
24
+ this.refreshToken = init.refreshToken;
25
+ this.tokenType = init.tokenType || 'Bearer';
26
+ this.scope = init.scope;
27
+ this.expiresAt = init.expiresAt ?? 0;
28
+ }
29
+ /** Time until expiry; negative once already expired. */
30
+ remainingMillis() {
31
+ return this.expiresAt - currentMillis();
32
+ }
33
+ /**
34
+ * @param aheadMillis how early to refresh. The window exists because time passes
35
+ * between sending a request and the server validating it, so using a token right up
36
+ * to its expiry produces intermittent 401s
37
+ */
38
+ needsRefresh(aheadMillis) {
39
+ return this.remainingMillis() <= aheadMillis;
40
+ }
41
+ hasRefreshToken() {
42
+ return !!this.refreshToken;
43
+ }
44
+ toJSON() {
45
+ return {
46
+ schema_version: this.schemaVersion,
47
+ issuer: this.issuer,
48
+ client_id: this.clientId,
49
+ access_token: this.accessToken,
50
+ refresh_token: this.refreshToken,
51
+ token_type: this.tokenType,
52
+ scope: this.scope,
53
+ expires_at: this.expiresAt,
54
+ };
55
+ }
56
+ static fromJSON(data) {
57
+ if (!data)
58
+ return null;
59
+ return new OAuth2Token({
60
+ schemaVersion: data.schema_version ?? exports.TOKEN_SCHEMA_VERSION,
61
+ issuer: data.issuer,
62
+ clientId: data.client_id,
63
+ accessToken: data.access_token,
64
+ refreshToken: data.refresh_token,
65
+ tokenType: data.token_type || 'Bearer',
66
+ scope: data.scope,
67
+ expiresAt: data.expires_at ?? 0,
68
+ });
69
+ }
70
+ /** Prints no token values. */
71
+ toString() {
72
+ return `OAuth2Token{clientId=${this.clientId}, issuer=${this.issuer}, scope=${this.scope}, `
73
+ + `expiresAt=${this.expiresAt}, hasRefreshToken=${this.hasRefreshToken()}}`;
74
+ }
75
+ }
76
+ exports.OAuth2Token = OAuth2Token;
@@ -22,6 +22,7 @@ var push_message_1 = require("./push-message");
22
22
  Object.defineProperty(exports, "SubjectType", { enumerable: true, get: function () { return push_message_1.SubjectType; } });
23
23
  __exportStar(require("./callbacks"), exports);
24
24
  __exportStar(require("./varint"), exports);
25
+ __exportStar(require("./tick-util"), exports);
25
26
  __exportStar(require("./proto-message"), exports);
26
27
  var push_client_1 = require("./push-client");
27
28
  Object.defineProperty(exports, "PushClient", { enumerable: true, get: function () { return push_client_1.PushClient; } });
@@ -203,7 +203,7 @@ exports.Request_Connect = {
203
203
  },
204
204
  };
205
205
  function createBaseRequest_Subscribe() {
206
- return { dataType: 0, symbols: undefined, account: undefined, market: undefined };
206
+ return { dataType: 0, symbols: undefined, account: undefined, market: undefined, assetQuoteType: undefined };
207
207
  }
208
208
  exports.Request_Subscribe = {
209
209
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -219,6 +219,9 @@ exports.Request_Subscribe = {
219
219
  if (message.market !== undefined) {
220
220
  writer.uint32(34).string(message.market);
221
221
  }
222
+ if (message.assetQuoteType !== undefined) {
223
+ writer.uint32(42).string(message.assetQuoteType);
224
+ }
222
225
  return writer;
223
226
  },
224
227
  decode(input, length) {
@@ -256,6 +259,13 @@ exports.Request_Subscribe = {
256
259
  message.market = reader.string();
257
260
  continue;
258
261
  }
262
+ case 5: {
263
+ if (tag !== 42) {
264
+ break;
265
+ }
266
+ message.assetQuoteType = reader.string();
267
+ continue;
268
+ }
259
269
  }
260
270
  if ((tag & 7) === 4 || tag === 0) {
261
271
  break;
@@ -273,6 +283,7 @@ exports.Request_Subscribe = {
273
283
  message.symbols = object.symbols ?? undefined;
274
284
  message.account = object.account ?? undefined;
275
285
  message.market = object.market ?? undefined;
286
+ message.assetQuoteType = object.assetQuoteType ?? undefined;
276
287
  return message;
277
288
  },
278
289
  };
@@ -53,8 +53,11 @@ function buildHeartBeatMessage() {
53
53
  }
54
54
  /**
55
55
  * 构建 SUBSCRIBE 请求消息
56
+ *
57
+ * @param assetQuoteType 资产行情类型枚举名(ETH / RTH / OVERNIGHT),
58
+ * 仅对 Asset / Position 两种订阅有意义,其余场景传 undefined
56
59
  */
57
- function buildSubscribeMessage(dataType, symbols, account, market) {
60
+ function buildSubscribeMessage(dataType, symbols, account, market, assetQuoteType) {
58
61
  return Request_1.Request.fromPartial({
59
62
  command: SocketCommon_1.SocketCommon_Command.SUBSCRIBE,
60
63
  id: nextRequestId(),
@@ -63,6 +66,7 @@ function buildSubscribeMessage(dataType, symbols, account, market) {
63
66
  symbols,
64
67
  account,
65
68
  market,
69
+ assetQuoteType,
66
70
  }),
67
71
  });
68
72
  }
@@ -47,6 +47,7 @@ exports.PushClient = exports.ConnectionState = void 0;
47
47
  const tls = __importStar(require("tls"));
48
48
  const signer_1 = require("../signer/signer");
49
49
  const push_message_1 = require("./push-message");
50
+ const tick_util_1 = require("./tick-util");
50
51
  const Request_1 = require("./pb/Request");
51
52
  const Response_1 = require("./pb/Response");
52
53
  const SocketCommon_1 = require("./pb/SocketCommon");
@@ -94,6 +95,8 @@ class PushClient {
94
95
  this.subscriptions = new Map();
95
96
  /** Account-level subscriptions: subject -> account string used at subscribe time */
96
97
  this.accountSubs = new Map();
98
+ /** Account-level subscriptions: subject -> assetQuoteType used at subscribe time (reconnect replay) */
99
+ this.accountSubQuoteTypes = new Map();
97
100
  this.heartbeatTimer = null;
98
101
  this.reconnectTimer = null;
99
102
  this.stopped = false;
@@ -354,7 +357,7 @@ class PushClient {
354
357
  this.doSubscribe(subject, Array.from(symbols));
355
358
  }
356
359
  for (const [subject, account] of this.accountSubs) {
357
- this.doSubscribe(subject, undefined, account);
360
+ this.doSubscribe(subject, undefined, account, undefined, this.accountSubQuoteTypes.get(subject));
358
361
  }
359
362
  }
360
363
  /** Dispatch push data to the appropriate callback based on dataType */
@@ -378,8 +381,10 @@ class PushClient {
378
381
  cb.onDepth?.(pushData.quoteDepthData);
379
382
  break;
380
383
  case SocketCommon_1.SocketCommon_DataType.TradeTick:
384
+ if (pushData.tickData)
385
+ cb.onFullTick?.(pushData.tickData);
381
386
  if (pushData.tradeTickData)
382
- cb.onTick?.(pushData.tradeTickData);
387
+ cb.onTick?.((0, tick_util_1.convertTradeTick)(pushData.tradeTickData));
383
388
  break;
384
389
  case SocketCommon_1.SocketCommon_DataType.Asset:
385
390
  if (pushData.assetData)
@@ -415,18 +420,17 @@ class PushClient {
415
420
  cb.onQuote?.(pushData.quoteData);
416
421
  break;
417
422
  default:
418
- // FullTick and QuoteBBO use TradeTick/Quote dataType
419
- // TickData (fullTick) via tickData field
423
+ // QuoteBBO and some full-tick payloads reuse an existing dataType.
420
424
  if (pushData.tickData)
421
425
  cb.onFullTick?.(pushData.tickData);
422
426
  break;
423
427
  }
424
428
  }
425
429
  /** Internal subscribe method */
426
- doSubscribe(subject, symbols, account, market) {
430
+ doSubscribe(subject, symbols, account, market, assetQuoteType) {
427
431
  const dataType = (0, proto_message_1.subjectToDataType)(subject);
428
432
  const symbolsStr = symbols ? symbols.join(',') : undefined;
429
- const req = (0, proto_message_1.buildSubscribeMessage)(dataType, symbolsStr, account, market);
433
+ const req = (0, proto_message_1.buildSubscribeMessage)(dataType, symbolsStr, account, market, assetQuoteType);
430
434
  this.sendMessage(req);
431
435
  }
432
436
  /** Internal unsubscribe method */
@@ -586,27 +590,41 @@ class PushClient {
586
590
  this.removeSubscription(push_message_1.SubjectType.Market, [market]);
587
591
  }
588
592
  // ===== Account push subscribe/unsubscribe =====
589
- /** Subscribe to asset changes */
590
- subscribeAsset(account) {
591
- const acct = account || this.config.account;
592
- this.doSubscribe(push_message_1.SubjectType.Asset, undefined, acct);
593
- this.accountSubs.set(push_message_1.SubjectType.Asset, acct);
593
+ /**
594
+ * Shared implementation for the account/options overloads of
595
+ * subscribeAsset / subscribePosition.
596
+ */
597
+ subscribeAccount(subject, accountOrOptions) {
598
+ const opts = typeof accountOrOptions === 'string'
599
+ ? { account: accountOrOptions }
600
+ : (accountOrOptions ?? {});
601
+ const acct = opts.account || this.config.account;
602
+ this.doSubscribe(subject, undefined, acct, undefined, opts.assetQuoteType);
603
+ this.accountSubs.set(subject, acct);
604
+ if (opts.assetQuoteType) {
605
+ this.accountSubQuoteTypes.set(subject, opts.assetQuoteType);
606
+ }
607
+ else {
608
+ this.accountSubQuoteTypes.delete(subject);
609
+ }
610
+ }
611
+ subscribeAsset(accountOrOptions) {
612
+ this.subscribeAccount(push_message_1.SubjectType.Asset, accountOrOptions);
594
613
  }
595
614
  /** Unsubscribe from asset changes */
596
615
  unsubscribeAsset() {
597
616
  this.doUnsubscribe(push_message_1.SubjectType.Asset);
598
617
  this.accountSubs.delete(push_message_1.SubjectType.Asset);
618
+ this.accountSubQuoteTypes.delete(push_message_1.SubjectType.Asset);
599
619
  }
600
- /** Subscribe to position changes */
601
- subscribePosition(account) {
602
- const acct = account || this.config.account;
603
- this.doSubscribe(push_message_1.SubjectType.Position, undefined, acct);
604
- this.accountSubs.set(push_message_1.SubjectType.Position, acct);
620
+ subscribePosition(accountOrOptions) {
621
+ this.subscribeAccount(push_message_1.SubjectType.Position, accountOrOptions);
605
622
  }
606
623
  /** Unsubscribe from position changes */
607
624
  unsubscribePosition() {
608
625
  this.doUnsubscribe(push_message_1.SubjectType.Position);
609
626
  this.accountSubs.delete(push_message_1.SubjectType.Position);
627
+ this.accountSubQuoteTypes.delete(push_message_1.SubjectType.Position);
610
628
  }
611
629
  /** Subscribe to order status */
612
630
  subscribeOrder(account) {
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPartNameByCode = getPartNameByCode;
4
+ exports.getPartShortNameByCode = getPartShortNameByCode;
5
+ exports.getTradeCondByCode = getTradeCondByCode;
6
+ exports.isUsStockSymbol = isUsStockSymbol;
7
+ exports.convertTradeTick = convertTradeTick;
8
+ const PART_CODE_NAME_MAP = {
9
+ a: 'NYSE American, LLC (NYSE American)',
10
+ b: 'NASDAQ OMX BX, Inc. (NASDAQ OMX BX)',
11
+ c: 'NYSE National, Inc. (NYSE National)',
12
+ d: 'FINRA Alternative Display Facility (ADF)',
13
+ h: 'MIAX Pearl Exchange, LLC (MIAX)',
14
+ i: 'International Securities Exchange, LLC (ISE)',
15
+ j: 'Cboe EDGA Exchange, Inc. (Cboe EDGA)',
16
+ k: 'Cboe EDGX Exchange, Inc. (Cboe EDGX)',
17
+ l: 'Long-Term Stock Exchange, Inc. (LTSE)',
18
+ m: 'NYSE Chicago, Inc. (NYSE Chicago)',
19
+ n: 'New York Stock Exchange, LLC (NYSE)',
20
+ p: 'NYSE Arca, Inc. (NYSE Arca)',
21
+ s: 'Consolidated Tape System (CTS)',
22
+ t: 'NASDAQ Stock Market, LLC (NASDAQ)',
23
+ u: 'Members Exchange, LLC (MEMX)',
24
+ v: "Investors' Exchange, LLC. (IEX)",
25
+ w: 'CBOE Stock Exchange, Inc. (CBSX)',
26
+ x: 'NASDAQ OMX PSX, Inc. (NASDAQ OMX PSX)',
27
+ y: 'Cboe BYX Exchange, Inc. (Cboe BYX)',
28
+ z: 'Cboe BZX Exchange, Inc. (Cboe BZX)',
29
+ };
30
+ const PART_CODE_SHORT_NAME_MAP = {
31
+ a: 'AMEX',
32
+ b: 'BX',
33
+ c: 'NSX',
34
+ d: 'ADF',
35
+ h: 'MIAX',
36
+ i: 'ISE',
37
+ j: 'EDGA',
38
+ k: 'EDGX',
39
+ l: 'LTSE',
40
+ m: 'CHO',
41
+ n: 'NYSE',
42
+ p: 'ARCA',
43
+ s: 'CTS',
44
+ t: 'NSDQ',
45
+ u: 'MEMX',
46
+ v: 'IEX',
47
+ w: 'CBSX',
48
+ x: 'PSX',
49
+ y: 'BYX',
50
+ z: 'BZX',
51
+ };
52
+ const US_TRADE_COND = {
53
+ ' ': 'US_REGULAR_SALE',
54
+ B: 'US_BUNCHED_TRADE',
55
+ C: 'US_CASH_TRADE',
56
+ F: 'US_INTERMARKET_SWEEP',
57
+ G: 'US_BUNCHED_SOLD_TRADE',
58
+ H: 'US_PRICE_VARIATION_TRADE',
59
+ I: 'US_ODD_LOT_TRADE',
60
+ K: 'US_RULE_127_OR_155_TRADE',
61
+ L: 'US_SOLD_LAST',
62
+ M: 'US_MARKET_CENTER_CLOSE_PRICE',
63
+ N: 'US_NEXT_DAY_TRADE',
64
+ O: 'US_MARKET_CENTER_OPENING_TRADE',
65
+ P: 'US_PRIOR_REFERENCE_PRICE',
66
+ Q: 'US_MARKET_CENTER_OPEN_PRICE',
67
+ R: 'US_SELLER',
68
+ T: 'US_FORM_T',
69
+ U: 'US_EXTENDED_TRADING_HOURS',
70
+ V: 'US_CONTINGENT_TRADE',
71
+ W: 'US_AVERAGE_PRICE_TRADE',
72
+ X: 'US_CROSS_TRADE',
73
+ Z: 'US_SOLD_OUT_OF_SEQUENCE',
74
+ '0': 'US_ODD_LOST_CROSS_TRADE',
75
+ '4': 'US_DERIVATIVELY_PRICED',
76
+ '5': 'US_MARKET_CENTER_RE_OPENING_TRADE',
77
+ '6': 'US_MARKET_CENTER_CLOSING_TRADE',
78
+ '7': 'US_QUALIFIED_CONTINGENT_TRADE',
79
+ '9': 'US_CONSOLIDATED_LAST_PRICE_PER_LISTING_PACKET',
80
+ };
81
+ const HK_TRADE_COND = {
82
+ ' ': 'HK_AUTOMATCH_NORMAL',
83
+ D: 'HK_ODD_LOT_TRADE',
84
+ U: 'HK_AUCTION_TRADE',
85
+ '*': 'HK_OVERSEAS_TRADE',
86
+ P: 'HK_LATE_TRADE_OFF_EXCHG',
87
+ M: 'HK_NON_DIRECT_OFF_EXCHG_TRADE',
88
+ X: 'HK_DIRECT_OFF_EXCHG_TRADE',
89
+ Y: 'HK_AUTOMATIC_INTERNALIZED',
90
+ };
91
+ /** Returns the full exchange name for a partCode letter, or the raw code if unknown. */
92
+ function getPartNameByCode(code) {
93
+ if (!code)
94
+ return code;
95
+ return PART_CODE_NAME_MAP[code] ?? code;
96
+ }
97
+ /** Returns the short exchange name for a partCode letter, or the raw code if unknown. */
98
+ function getPartShortNameByCode(code) {
99
+ if (!code)
100
+ return code;
101
+ return PART_CODE_SHORT_NAME_MAP[code] ?? code;
102
+ }
103
+ /** Resolves a raw cond character to a readable string. */
104
+ function getTradeCondByCode(isUs, ch) {
105
+ const map = isUs ? US_TRADE_COND : HK_TRADE_COND;
106
+ return map[ch] ?? ch;
107
+ }
108
+ const US_SYMBOL_RE = /^[A-Z]+(\.[A-Z0-9]+)?$/;
109
+ /** Reports whether symbol is a US stock (uppercase letters, optional dot suffix). */
110
+ function isUsStockSymbol(symbol) {
111
+ return US_SYMBOL_RE.test(symbol);
112
+ }
113
+ function isUsSymbol(symbol) {
114
+ return US_SYMBOL_RE.test(symbol);
115
+ }
116
+ function getTradeCond(isUs, ch) {
117
+ const map = isUs ? US_TRADE_COND : HK_TRADE_COND;
118
+ return map[ch] ?? ch;
119
+ }
120
+ /** Decodes a TradeTickData pb message into a PushTradeTick with resolved partCode/partName. */
121
+ function convertTradeTick(src) {
122
+ if (src.secType === 'FUT')
123
+ return convertFutureTick(src);
124
+ return convertStockTick(src);
125
+ }
126
+ function convertStockTick(src) {
127
+ const isUs = isUsSymbol(src.symbol);
128
+ const denominator = Math.pow(10, src.priceOffset);
129
+ const ticks = [];
130
+ let currentTime = 0;
131
+ for (let i = 0; i < src.time.length; i++) {
132
+ currentTime += src.time[i];
133
+ const condCh = src.cond.length > i ? src.cond[i] : ' ';
134
+ const rawCode = i < src.partCode.length ? src.partCode[i] : '';
135
+ ticks.push({
136
+ sn: src.sn + i,
137
+ time: currentTime,
138
+ price: (src.priceBase + src.price[i]) / denominator,
139
+ volume: src.volume[i],
140
+ tickType: src.type.length > i ? src.type[i] : '',
141
+ cond: getTradeCond(isUs, condCh),
142
+ partCode: getPartShortNameByCode(rawCode),
143
+ partName: getPartNameByCode(rawCode),
144
+ });
145
+ }
146
+ return { symbol: src.symbol, secType: src.secType, quoteLevel: src.quoteLevel, timestamp: src.timestamp, ticks };
147
+ }
148
+ function convertFutureTick(src) {
149
+ const denominator = Math.pow(10, src.priceOffset);
150
+ const ticks = [];
151
+ let currentTime = 0;
152
+ let startSn = src.sn;
153
+ for (let i = 0; i < src.time.length; i++) {
154
+ currentTime += src.time[i];
155
+ const curPrice = (src.priceBase + src.price[i]) / denominator;
156
+ const mv = src.mergedVols[i];
157
+ for (let j = 0; j < mv.vol.length; j++) {
158
+ ticks.push({
159
+ sn: startSn * 10 + j,
160
+ time: currentTime,
161
+ price: curPrice,
162
+ volume: mv.vol[j],
163
+ tickType: '',
164
+ cond: '',
165
+ partCode: '',
166
+ partName: '',
167
+ });
168
+ }
169
+ startSn++;
170
+ }
171
+ return { symbol: src.symbol, secType: src.secType, quoteLevel: '', timestamp: src.timestamp, ticks };
172
+ }