@tigeropenapi/tigeropen 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -486,8 +486,61 @@ const chain = await qc.optionChain('AAPL', '2024-01-19');
486
486
 
487
487
  // 期货交易所
488
488
  const exchanges = await qc.futureExchange();
489
+
490
+ // 逐笔成交
491
+ const ticks = await qc.getTradeTick({ symbols: ['AAPL'] });
492
+ // ticks[0].items[i].partCode — 交易所短名,如 "NYSE"、"NSDQ"
493
+ // ticks[0].items[i].partName — 交易所全名,如 "New York Stock Exchange, LLC (NYSE)"
494
+ // ticks[0].items[i].cond — 已转换的成交条件,如 "US_REGULAR_SALE",参见下方说明
489
495
  ```
490
496
 
497
+ ### 逐笔成交 `cond` 字段说明
498
+
499
+ `trade_tick` 接口和 push 层返回的 `cond` 字段已由 SDK 转换为可读字符串,含义如下:
500
+
501
+ **美股(US)**
502
+
503
+ | 字符 | 含义 |
504
+ |------|------|
505
+ | ` `(空格)| 常规交易(Regular Sale) |
506
+ | `B` | 批量交易(Bunched Trade) |
507
+ | `C` | 现金交易(Cash Trade) |
508
+ | `F` | 跨市场交易(Intermarket Sweep) |
509
+ | `G` | 批量卖出(Bunched Sold Trade) |
510
+ | `H` | 离价交易(Price Variation Trade) |
511
+ | `I` | 碎股交易(Odd Lot Trade) |
512
+ | `K` | 纽交所第 127/155 条交易 |
513
+ | `L` | 延迟交易(Sold Last) |
514
+ | `M` | 中央收市价(Market Center Close Price) |
515
+ | `N` | 隔日交易(Next Day Trade) |
516
+ | `O` | 中央开盘价交易(Market Center Opening Trade) |
517
+ | `P` | 前参考价(Prior Reference Price) |
518
+ | `Q` | 中央开盘价(Market Center Open Price) |
519
+ | `R` | 卖方(Seller) |
520
+ | `T` | 盘前盘后交易(Form T) |
521
+ | `U` | 延长交易时段(Extended Trading Hours) |
522
+ | `V` | 合单交易(Contingent Trade) |
523
+ | `W` | 均价交易(Average Price Trade) |
524
+ | `X` | 跨市场交易(Cross Trade) |
525
+ | `Z` | 场外售出(Sold Out of Sequence) |
526
+ | `4` | 衍生工具定价(Derivatively Priced) |
527
+ | `7` | 合单交易(Qualified Contingent Trade) |
528
+
529
+ **港股(HK)**
530
+
531
+ | 字符 | 含义 |
532
+ |------|------|
533
+ | ` `(空格)| 自动对盘(Automatch Normal) |
534
+ | `D` | 碎股交易(Odd Lot Trade) |
535
+ | `U` | 竞价交易(Auction Trade) |
536
+ | `*` | 场外交易(Overseas Trade) |
537
+ | `P` | 开市前成交(Late Trade Off Exchange) |
538
+ | `M` | 非自动对盘(Non-Direct Off Exchange Trade) |
539
+ | `X` | 同券商自动对盘(Direct Off Exchange Trade) |
540
+ | `Y` | 同券商非自动对盘(Automatic Internalized) |
541
+
542
+ 如上表所示,`cond` 值因市场(美股/港股)不同而有所区别。
543
+
491
544
  ## 交易
492
545
 
493
546
  ```typescript
@@ -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; } });
@@ -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");
@@ -378,8 +379,10 @@ class PushClient {
378
379
  cb.onDepth?.(pushData.quoteDepthData);
379
380
  break;
380
381
  case SocketCommon_1.SocketCommon_DataType.TradeTick:
382
+ if (pushData.tickData)
383
+ cb.onFullTick?.(pushData.tickData);
381
384
  if (pushData.tradeTickData)
382
- cb.onTick?.(pushData.tradeTickData);
385
+ cb.onTick?.((0, tick_util_1.convertTradeTick)(pushData.tradeTickData));
383
386
  break;
384
387
  case SocketCommon_1.SocketCommon_DataType.Asset:
385
388
  if (pushData.assetData)
@@ -415,8 +418,7 @@ class PushClient {
415
418
  cb.onQuote?.(pushData.quoteData);
416
419
  break;
417
420
  default:
418
- // FullTick and QuoteBBO use TradeTick/Quote dataType
419
- // TickData (fullTick) via tickData field
421
+ // QuoteBBO and some full-tick payloads reuse an existing dataType.
420
422
  if (pushData.tickData)
421
423
  cb.onFullTick?.(pushData.tickData);
422
424
  break;
@@ -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
+ }
@@ -46,6 +46,7 @@ const http_client_1 = require("../client/http-client");
46
46
  const api_request_1 = require("../client/api-request");
47
47
  const api_response_1 = require("../client/api-response");
48
48
  const tokenMethods = __importStar(require("../client/token-methods"));
49
+ const tick_util_1 = require("../push/tick-util");
49
50
  /**
50
51
  * resolveOptionTimezone returns the timezone string to use for expiry conversion.
51
52
  * Explicit tz takes precedence; otherwise infer from symbol (.HK suffix → Asia/Hong_Kong, else America/New_York).
@@ -185,12 +186,22 @@ class QuoteClient {
185
186
  async getKline(req) {
186
187
  return this.callInto('kline', req);
187
188
  }
188
- async getTimeline(symbols) {
189
- return this.callInto('timeline', { symbols });
189
+ async getTimeline(symbolsOrRequest) {
190
+ const req = Array.isArray(symbolsOrRequest) ? { symbols: symbolsOrRequest } : symbolsOrRequest;
191
+ return this.callInto('timeline', req, req.secType === 'CC' ? '3.0' : undefined);
190
192
  }
191
193
  /** Tick-by-tick trades. wire: trade_tick */
192
194
  async getTradeTick(req) {
193
- return this.callInto('trade_tick', req);
195
+ const out = await this.callInto('trade_tick', req);
196
+ for (const tick of out) {
197
+ const isUs = (0, tick_util_1.isUsStockSymbol)(tick.symbol);
198
+ for (const item of tick.items) {
199
+ if (item.cond && item.cond.length > 0) {
200
+ item.cond = (0, tick_util_1.getTradeCondByCode)(isUs, item.cond[0]);
201
+ }
202
+ }
203
+ }
204
+ return out;
194
205
  }
195
206
  /** Depth snapshot. wire: quote_depth */
196
207
  async getQuoteDepth(req) {
@@ -356,6 +367,7 @@ class QuoteClient {
356
367
  while (acc.length < totalSize) {
357
368
  const sub = {
358
369
  symbols: req.symbol ? [req.symbol] : undefined,
370
+ secType: req.secType,
359
371
  period: req.period,
360
372
  right: req.right,
361
373
  beginTime,
@@ -406,7 +418,7 @@ class QuoteClient {
406
418
  async getStockIndustry(req) {
407
419
  return this.callInto('stock_industry', req);
408
420
  }
409
- /** Quote permission detail. wire: get_quote_permission */
421
+ /** Market data permission entries. wire: get_quote_permission */
410
422
  async getQuotePermission(req) {
411
423
  return this.callInto('get_quote_permission', req);
412
424
  }
@@ -417,9 +429,16 @@ class QuoteClient {
417
429
  // ==========================================================================
418
430
  // Batch 4: option / future extensions
419
431
  // ==========================================================================
420
- /** Option tick trades. wire: option_trade_tick */
432
+ /** Option tick trades. wire: option_trade_tick
433
+ *
434
+ * Server expects `biz_content` as a top-level JSON array (matches Java's
435
+ * `BatchApiModel` and Python's `MultipleContractParams.to_openapi_dict`).
436
+ * We unwrap the request here so the array is emitted directly instead of
437
+ * `{"contracts": [...]}`. Sending it as an object triggers
438
+ * `biz param error(failed to parse parameters in 'biz_content')`.
439
+ */
421
440
  async getOptionTradeTicks(req) {
422
- return this.callInto('option_trade_tick', req);
441
+ return this.callInto('option_trade_tick', req.contracts ?? []);
423
442
  }
424
443
  /** Option intraday timeline. wire: option_timeline */
425
444
  async getOptionTimeline(req) {
@@ -458,10 +477,16 @@ class QuoteClient {
458
477
  return this.callInto('future_main_contract', req);
459
478
  }
460
479
  /** Futures K-line. wire: future_kline
461
- * beginTime / endTime default to -1 when unset (server requires them present). */
480
+ * beginTime / endTime default to -1 when unset (server requires them present).
481
+ * Server expects `contract_codes` (plural) — normalize singular `contractCode`
482
+ * to the plural form so callers can pass either shape. */
462
483
  async getFutureKline(req) {
484
+ const contractCodes = req.contractCodes
485
+ ?? (req.contractCode ? [req.contractCode] : undefined);
486
+ const { contractCode: _drop, ...rest } = req;
463
487
  const body = {
464
- ...req,
488
+ ...rest,
489
+ ...(contractCodes ? { contractCodes } : {}),
465
490
  beginTime: req.beginTime ?? -1,
466
491
  endTime: req.endTime ?? -1,
467
492
  };
@@ -479,8 +504,9 @@ class QuoteClient {
479
504
  endTime = -1;
480
505
  const acc = [];
481
506
  while (acc.length < totalSize) {
507
+ // Server rejects singular `contract_code` — send the plural form.
482
508
  const sub = {
483
- contractCode: req.contractCode,
509
+ contractCodes: req.contractCode ? [req.contractCode] : undefined,
484
510
  period: req.period,
485
511
  beginTime,
486
512
  endTime,
@@ -559,9 +585,30 @@ class QuoteClient {
559
585
  async getWarrantFilter(req) {
560
586
  return this.callInto('warrant_filter', req);
561
587
  }
562
- /** Industry list. wire: industry_list */
563
- async getIndustryList(req) {
564
- return this.callInto('industry_list', req);
588
+ /** Industry list. wire: industry_list
589
+ *
590
+ * `industryLevel` is required by the server — omitting it returns
591
+ * `industry level error, all supported is: [GSECTOR, GGROUP, GIND, GSUBIND]`.
592
+ * Defaults to `GGROUP` when unset, matching Python SDK's
593
+ * `IndustryLevel.GGROUP` default.
594
+ *
595
+ * Wire fields differ from other endpoints (see `IndustryItem`): server sends
596
+ * `nameCN` / `nameEN` / `industryLevel`. This method hydrates the
597
+ * backwards-compat `name` (from `nameEN || nameCN`) and mirrors
598
+ * `industryLevel` to `level` so existing callers keep working.
599
+ */
600
+ async getIndustryList(req = {}) {
601
+ const payload = { industryLevel: 'GGROUP', ...req };
602
+ const items = await this.callInto('industry_list', payload);
603
+ if (!Array.isArray(items))
604
+ return [];
605
+ for (const it of items) {
606
+ if (it.name === undefined)
607
+ it.name = it.nameEN || it.nameCN;
608
+ if (it.level === undefined)
609
+ it.level = it.industryLevel;
610
+ }
611
+ return items;
565
612
  }
566
613
  /** Stocks inside an industry. wire: industry_stock_list */
567
614
  async getIndustryStocks(req) {
@@ -145,14 +145,54 @@ class TradeClient {
145
145
  });
146
146
  }
147
147
  // === Order operations ===
148
+ /**
149
+ * Prepare an order request for the wire.
150
+ *
151
+ * The gateway expects `algo_params` as a `[{tag, value}, ...]` array
152
+ * (matching the Python SDK's AlgoParams.to_dict serialization), not the
153
+ * plain object shape that reads naturally in TypeScript. This helper
154
+ * normalizes the object form into the array form on outbound payloads
155
+ * so callers can pass the natural object and TWAP/VWAP orders work end
156
+ * to end without wire-format workarounds.
157
+ *
158
+ * Fields with undefined/null values are dropped; empty algoParams
159
+ * becomes an empty array rather than being stripped, matching the
160
+ * Python SDK behavior when the user passes an empty AlgoParams().
161
+ */
162
+ marshalOrderForWire(order) {
163
+ const { algoParams, ...rest } = order;
164
+ const withAccount = { ...rest, account: this.account };
165
+ if (algoParams === undefined)
166
+ return withAccount;
167
+ // Already a tag/value array (advanced callers who want raw control)
168
+ if (Array.isArray(algoParams)) {
169
+ withAccount.algoParams = algoParams;
170
+ return withAccount;
171
+ }
172
+ // Object shape → tag/value array.
173
+ // Keys are converted to snake_case explicitly here (e.g. `startTime` →
174
+ // `start_time`) so they land correctly in the `tag` string that the
175
+ // gateway reads. `keysToSnakeCase` only rewrites object *keys*, not
176
+ // string *values*, so `tag` would be left camelCase if we didn't do
177
+ // this conversion ourselves.
178
+ const tagValues = [];
179
+ for (const [key, value] of Object.entries(algoParams)) {
180
+ if (value === undefined || value === null)
181
+ continue;
182
+ const snakeKey = key.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase());
183
+ tagValues.push({ tag: snakeKey, value });
184
+ }
185
+ withAccount.algoParams = tagValues;
186
+ return withAccount;
187
+ }
148
188
  async placeOrder(order) {
149
- return this.callInto('place_order', { ...order, account: this.account });
189
+ return this.callInto('place_order', this.marshalOrderForWire(order));
150
190
  }
151
191
  async previewOrder(order) {
152
- return this.callInto('preview_order', { ...order, account: this.account });
192
+ return this.callInto('preview_order', this.marshalOrderForWire(order));
153
193
  }
154
194
  async modifyOrder(id, order) {
155
- return this.callInto('modify_order', { ...order, account: this.account, id });
195
+ return this.callInto('modify_order', { ...this.marshalOrderForWire(order), id });
156
196
  }
157
197
  async cancelOrder(id, secretKey) {
158
198
  const params = { account: this.account, id };
@@ -4,5 +4,6 @@
4
4
  export { SubjectType } from './push-message.js';
5
5
  export * from './callbacks.js';
6
6
  export * from './varint.js';
7
+ export * from './tick-util.js';
7
8
  export * from './proto-message.js';
8
9
  export { PushClient, ConnectionState } from './push-client.js';
@@ -11,6 +11,7 @@
11
11
  import * as tls from 'tls';
12
12
  import { signWithRSA } from '../signer/signer.js';
13
13
  import { SubjectType } from './push-message.js';
14
+ import { convertTradeTick } from './tick-util.js';
14
15
  import { Request } from './pb/Request.js';
15
16
  import { Response } from './pb/Response.js';
16
17
  import { SocketCommon_Command, SocketCommon_DataType } from './pb/SocketCommon.js';
@@ -342,8 +343,10 @@ export class PushClient {
342
343
  cb.onDepth?.(pushData.quoteDepthData);
343
344
  break;
344
345
  case SocketCommon_DataType.TradeTick:
346
+ if (pushData.tickData)
347
+ cb.onFullTick?.(pushData.tickData);
345
348
  if (pushData.tradeTickData)
346
- cb.onTick?.(pushData.tradeTickData);
349
+ cb.onTick?.(convertTradeTick(pushData.tradeTickData));
347
350
  break;
348
351
  case SocketCommon_DataType.Asset:
349
352
  if (pushData.assetData)
@@ -379,8 +382,7 @@ export class PushClient {
379
382
  cb.onQuote?.(pushData.quoteData);
380
383
  break;
381
384
  default:
382
- // FullTick and QuoteBBO use TradeTick/Quote dataType
383
- // TickData (fullTick) via tickData field
385
+ // QuoteBBO and some full-tick payloads reuse an existing dataType.
384
386
  if (pushData.tickData)
385
387
  cb.onFullTick?.(pushData.tickData);
386
388
  break;
@@ -0,0 +1,165 @@
1
+ const PART_CODE_NAME_MAP = {
2
+ a: 'NYSE American, LLC (NYSE American)',
3
+ b: 'NASDAQ OMX BX, Inc. (NASDAQ OMX BX)',
4
+ c: 'NYSE National, Inc. (NYSE National)',
5
+ d: 'FINRA Alternative Display Facility (ADF)',
6
+ h: 'MIAX Pearl Exchange, LLC (MIAX)',
7
+ i: 'International Securities Exchange, LLC (ISE)',
8
+ j: 'Cboe EDGA Exchange, Inc. (Cboe EDGA)',
9
+ k: 'Cboe EDGX Exchange, Inc. (Cboe EDGX)',
10
+ l: 'Long-Term Stock Exchange, Inc. (LTSE)',
11
+ m: 'NYSE Chicago, Inc. (NYSE Chicago)',
12
+ n: 'New York Stock Exchange, LLC (NYSE)',
13
+ p: 'NYSE Arca, Inc. (NYSE Arca)',
14
+ s: 'Consolidated Tape System (CTS)',
15
+ t: 'NASDAQ Stock Market, LLC (NASDAQ)',
16
+ u: 'Members Exchange, LLC (MEMX)',
17
+ v: "Investors' Exchange, LLC. (IEX)",
18
+ w: 'CBOE Stock Exchange, Inc. (CBSX)',
19
+ x: 'NASDAQ OMX PSX, Inc. (NASDAQ OMX PSX)',
20
+ y: 'Cboe BYX Exchange, Inc. (Cboe BYX)',
21
+ z: 'Cboe BZX Exchange, Inc. (Cboe BZX)',
22
+ };
23
+ const PART_CODE_SHORT_NAME_MAP = {
24
+ a: 'AMEX',
25
+ b: 'BX',
26
+ c: 'NSX',
27
+ d: 'ADF',
28
+ h: 'MIAX',
29
+ i: 'ISE',
30
+ j: 'EDGA',
31
+ k: 'EDGX',
32
+ l: 'LTSE',
33
+ m: 'CHO',
34
+ n: 'NYSE',
35
+ p: 'ARCA',
36
+ s: 'CTS',
37
+ t: 'NSDQ',
38
+ u: 'MEMX',
39
+ v: 'IEX',
40
+ w: 'CBSX',
41
+ x: 'PSX',
42
+ y: 'BYX',
43
+ z: 'BZX',
44
+ };
45
+ const US_TRADE_COND = {
46
+ ' ': 'US_REGULAR_SALE',
47
+ B: 'US_BUNCHED_TRADE',
48
+ C: 'US_CASH_TRADE',
49
+ F: 'US_INTERMARKET_SWEEP',
50
+ G: 'US_BUNCHED_SOLD_TRADE',
51
+ H: 'US_PRICE_VARIATION_TRADE',
52
+ I: 'US_ODD_LOT_TRADE',
53
+ K: 'US_RULE_127_OR_155_TRADE',
54
+ L: 'US_SOLD_LAST',
55
+ M: 'US_MARKET_CENTER_CLOSE_PRICE',
56
+ N: 'US_NEXT_DAY_TRADE',
57
+ O: 'US_MARKET_CENTER_OPENING_TRADE',
58
+ P: 'US_PRIOR_REFERENCE_PRICE',
59
+ Q: 'US_MARKET_CENTER_OPEN_PRICE',
60
+ R: 'US_SELLER',
61
+ T: 'US_FORM_T',
62
+ U: 'US_EXTENDED_TRADING_HOURS',
63
+ V: 'US_CONTINGENT_TRADE',
64
+ W: 'US_AVERAGE_PRICE_TRADE',
65
+ X: 'US_CROSS_TRADE',
66
+ Z: 'US_SOLD_OUT_OF_SEQUENCE',
67
+ '0': 'US_ODD_LOST_CROSS_TRADE',
68
+ '4': 'US_DERIVATIVELY_PRICED',
69
+ '5': 'US_MARKET_CENTER_RE_OPENING_TRADE',
70
+ '6': 'US_MARKET_CENTER_CLOSING_TRADE',
71
+ '7': 'US_QUALIFIED_CONTINGENT_TRADE',
72
+ '9': 'US_CONSOLIDATED_LAST_PRICE_PER_LISTING_PACKET',
73
+ };
74
+ const HK_TRADE_COND = {
75
+ ' ': 'HK_AUTOMATCH_NORMAL',
76
+ D: 'HK_ODD_LOT_TRADE',
77
+ U: 'HK_AUCTION_TRADE',
78
+ '*': 'HK_OVERSEAS_TRADE',
79
+ P: 'HK_LATE_TRADE_OFF_EXCHG',
80
+ M: 'HK_NON_DIRECT_OFF_EXCHG_TRADE',
81
+ X: 'HK_DIRECT_OFF_EXCHG_TRADE',
82
+ Y: 'HK_AUTOMATIC_INTERNALIZED',
83
+ };
84
+ /** Returns the full exchange name for a partCode letter, or the raw code if unknown. */
85
+ export function getPartNameByCode(code) {
86
+ if (!code)
87
+ return code;
88
+ return PART_CODE_NAME_MAP[code] ?? code;
89
+ }
90
+ /** Returns the short exchange name for a partCode letter, or the raw code if unknown. */
91
+ export function getPartShortNameByCode(code) {
92
+ if (!code)
93
+ return code;
94
+ return PART_CODE_SHORT_NAME_MAP[code] ?? code;
95
+ }
96
+ /** Resolves a raw cond character to a readable string. */
97
+ export function getTradeCondByCode(isUs, ch) {
98
+ const map = isUs ? US_TRADE_COND : HK_TRADE_COND;
99
+ return map[ch] ?? ch;
100
+ }
101
+ const US_SYMBOL_RE = /^[A-Z]+(\.[A-Z0-9]+)?$/;
102
+ /** Reports whether symbol is a US stock (uppercase letters, optional dot suffix). */
103
+ export function isUsStockSymbol(symbol) {
104
+ return US_SYMBOL_RE.test(symbol);
105
+ }
106
+ function isUsSymbol(symbol) {
107
+ return US_SYMBOL_RE.test(symbol);
108
+ }
109
+ function getTradeCond(isUs, ch) {
110
+ const map = isUs ? US_TRADE_COND : HK_TRADE_COND;
111
+ return map[ch] ?? ch;
112
+ }
113
+ /** Decodes a TradeTickData pb message into a PushTradeTick with resolved partCode/partName. */
114
+ export function convertTradeTick(src) {
115
+ if (src.secType === 'FUT')
116
+ return convertFutureTick(src);
117
+ return convertStockTick(src);
118
+ }
119
+ function convertStockTick(src) {
120
+ const isUs = isUsSymbol(src.symbol);
121
+ const denominator = Math.pow(10, src.priceOffset);
122
+ const ticks = [];
123
+ let currentTime = 0;
124
+ for (let i = 0; i < src.time.length; i++) {
125
+ currentTime += src.time[i];
126
+ const condCh = src.cond.length > i ? src.cond[i] : ' ';
127
+ const rawCode = i < src.partCode.length ? src.partCode[i] : '';
128
+ ticks.push({
129
+ sn: src.sn + i,
130
+ time: currentTime,
131
+ price: (src.priceBase + src.price[i]) / denominator,
132
+ volume: src.volume[i],
133
+ tickType: src.type.length > i ? src.type[i] : '',
134
+ cond: getTradeCond(isUs, condCh),
135
+ partCode: getPartShortNameByCode(rawCode),
136
+ partName: getPartNameByCode(rawCode),
137
+ });
138
+ }
139
+ return { symbol: src.symbol, secType: src.secType, quoteLevel: src.quoteLevel, timestamp: src.timestamp, ticks };
140
+ }
141
+ function convertFutureTick(src) {
142
+ const denominator = Math.pow(10, src.priceOffset);
143
+ const ticks = [];
144
+ let currentTime = 0;
145
+ let startSn = src.sn;
146
+ for (let i = 0; i < src.time.length; i++) {
147
+ currentTime += src.time[i];
148
+ const curPrice = (src.priceBase + src.price[i]) / denominator;
149
+ const mv = src.mergedVols[i];
150
+ for (let j = 0; j < mv.vol.length; j++) {
151
+ ticks.push({
152
+ sn: startSn * 10 + j,
153
+ time: currentTime,
154
+ price: curPrice,
155
+ volume: mv.vol[j],
156
+ tickType: '',
157
+ cond: '',
158
+ partCode: '',
159
+ partName: '',
160
+ });
161
+ }
162
+ startSn++;
163
+ }
164
+ return { symbol: src.symbol, secType: src.secType, quoteLevel: '', timestamp: src.timestamp, ticks };
165
+ }