@tigeropenapi/tigeropen 0.5.3 → 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
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryToken = queryToken;
4
+ exports.refreshToken = refreshToken;
5
+ exports.startTokenAutoRefresh = startTokenAutoRefresh;
6
+ /**
7
+ * Call the `user_token_refresh` API and return the new token string.
8
+ * Read-only: does NOT update the stored token.
9
+ * Use `refreshToken()` to also update the in-memory config and optionally persist.
10
+ */
11
+ async function queryToken(httpClient) {
12
+ return httpClient.queryToken();
13
+ }
14
+ /**
15
+ * Refresh the token: call the API, update the in-memory config token, and
16
+ * optionally persist via the provided TokenManager (file + writer callback).
17
+ *
18
+ * @param httpClient - The underlying HTTP client
19
+ * @param tokenManager - optional TokenManager for file persistence and writer callback
20
+ */
21
+ async function refreshToken(httpClient, tokenManager) {
22
+ return httpClient.refreshToken(tokenManager);
23
+ }
24
+ /**
25
+ * Start background token auto-refresh. Returns a TokenManager that can be
26
+ * used to stop the background task via `tm.stopAutoRefresh()`.
27
+ *
28
+ * @param httpClient - The underlying HTTP client
29
+ * @param refreshDurationSecs - Refresh threshold in seconds (minimum 30).
30
+ * When the token age exceeds this value the refresh is triggered.
31
+ * @param checkIntervalMs - How often to check whether a refresh is needed,
32
+ * in milliseconds. Defaults to 300 000 ms (5 minutes).
33
+ * @param tokenWriter - Optional callback invoked with the new token string
34
+ * after each successful refresh.
35
+ */
36
+ function startTokenAutoRefresh(httpClient, refreshDurationSecs, checkIntervalMs, tokenWriter) {
37
+ return httpClient.startTokenAutoRefresh(null, {
38
+ refreshDuration: refreshDurationSecs,
39
+ refreshInterval: checkIntervalMs,
40
+ tokenWriter,
41
+ });
42
+ }
@@ -113,23 +113,32 @@ function createClientConfig(options) {
113
113
  const opts = options ?? {};
114
114
  // Load properties: explicit path > auto-discovery
115
115
  let fileProps = {};
116
+ let configFileDir;
116
117
  if (opts.propertiesFilePath) {
117
118
  // Support both directory path and full file path
118
119
  let filePath = opts.propertiesFilePath;
119
120
  if ((0, fs_1.existsSync)(filePath) && (0, fs_1.statSync)(filePath).isDirectory()) {
121
+ configFileDir = filePath;
120
122
  filePath = (0, path_1.join)(filePath, CONFIG_FILE_NAME);
121
123
  }
122
124
  else if (!(0, fs_1.existsSync)(filePath) && !filePath.endsWith('.properties')) {
123
125
  // Path doesn't exist as-is; try treating it as a directory
124
126
  const asDir = (0, path_1.join)(filePath, CONFIG_FILE_NAME);
125
- if ((0, fs_1.existsSync)(asDir))
127
+ if ((0, fs_1.existsSync)(asDir)) {
128
+ configFileDir = filePath;
126
129
  filePath = asDir;
130
+ }
131
+ }
132
+ else {
133
+ // It's a file path: derive directory from it
134
+ configFileDir = (0, path_1.dirname)(filePath);
127
135
  }
128
136
  fileProps = loadPropertiesFile(filePath);
129
137
  }
130
138
  else {
131
139
  const discovered = discoverConfigFile();
132
140
  if (discovered) {
141
+ configFileDir = (0, path_1.dirname)(discovered);
133
142
  fileProps = loadPropertiesFile(discovered);
134
143
  }
135
144
  }
@@ -189,8 +198,9 @@ function createClientConfig(options) {
189
198
  }
190
199
  }
191
200
  else {
192
- // Load from token file: TIGEROPEN_TOKEN_FILE env var > default file
193
- const tokenFilePath = process.env[ENV_TOKEN_FILE] || 'tiger_openapi_token.properties';
201
+ // Load from token file: TIGEROPEN_TOKEN_FILE env var > config dir > default
202
+ const tokenFilePath = process.env[ENV_TOKEN_FILE]
203
+ || (configFileDir ? (0, path_1.join)(configFileDir, 'tiger_openapi_token.properties') : 'tiger_openapi_token.properties');
194
204
  const tm = new token_manager_1.TokenManager({ filePath: tokenFilePath });
195
205
  try {
196
206
  token = tm.loadTokenSync();
@@ -9,8 +9,14 @@ exports.auctionLimitOrder = auctionLimitOrder;
9
9
  exports.auctionMarketOrder = auctionMarketOrder;
10
10
  exports.algoOrder = algoOrder;
11
11
  exports.icebergOrder = icebergOrder;
12
- exports.icebergOrderFull = icebergOrderFull;
13
12
  exports.orderLeg = orderLeg;
13
+ exports.marketOrderByAmount = marketOrderByAmount;
14
+ exports.limitOrderByAmount = limitOrderByAmount;
15
+ exports.trailOrderByPrice = trailOrderByPrice;
16
+ exports.limitOrderWithLegs = limitOrderWithLegs;
17
+ exports.comboOrder = comboOrder;
18
+ exports.ocaOrder = ocaOrder;
19
+ exports.contractLeg = contractLeg;
14
20
  const enums_1 = require("./enums");
15
21
  /** Market order */
16
22
  function marketOrder(account, symbol, secType, action, quantity) {
@@ -101,26 +107,11 @@ function algoOrder(account, symbol, secType, action, quantity, limitPrice, algoT
101
107
  };
102
108
  }
103
109
  /**
104
- * Iceberg order (basic) — only displaySize required.
105
- */
106
- function icebergOrder(account, symbol, secType, action, quantity, limitPrice, displaySize) {
107
- return {
108
- account, symbol, secType, action,
109
- orderType: enums_1.OrderType.ICEBERG,
110
- totalQuantity: quantity,
111
- limitPrice,
112
- timeInForce: enums_1.TimeInForce.DAY,
113
- outsideRth: false,
114
- displaySize,
115
- priceType: enums_1.PriceType.LIMIT_PRICE,
116
- };
117
- }
118
- /**
119
- * Iceberg order (full params).
120
- * Pass `priceType` as empty string to use server default (LIMIT_PRICE).
110
+ * Iceberg order. `displaySize` is required; all other parameters are optional.
111
+ * Pass `priceType` as empty string to use the server default (LIMIT_PRICE).
121
112
  * Pass `startTime` / `endTime` as 0 to omit the time window.
122
113
  */
123
- function icebergOrderFull(account, symbol, secType, action, quantity, limitPrice, displaySize, minDisplaySize, checkIntervals, priceType, startTime, endTime) {
114
+ function icebergOrder(account, symbol, secType, action, quantity, limitPrice, displaySize, minDisplaySize, checkIntervals, priceType, startTime, endTime) {
124
115
  const req = {
125
116
  account, symbol, secType, action,
126
117
  orderType: enums_1.OrderType.ICEBERG,
@@ -129,13 +120,12 @@ function icebergOrderFull(account, symbol, secType, action, quantity, limitPrice
129
120
  timeInForce: enums_1.TimeInForce.DAY,
130
121
  outsideRth: false,
131
122
  displaySize,
123
+ priceType: priceType || enums_1.PriceType.LIMIT_PRICE,
132
124
  };
133
125
  if (minDisplaySize !== undefined && minDisplaySize > 0)
134
126
  req.minDisplaySize = minDisplaySize;
135
127
  if (checkIntervals !== undefined && checkIntervals > 0)
136
128
  req.checkIntervals = checkIntervals;
137
- if (priceType)
138
- req.priceType = priceType;
139
129
  if (startTime && startTime > 0)
140
130
  req.startTime = startTime;
141
131
  if (endTime && endTime > 0)
@@ -146,3 +136,118 @@ function icebergOrderFull(account, symbol, secType, action, quantity, limitPrice
146
136
  function orderLeg(legType, price, timeInForce) {
147
137
  return { legType, price, timeInForce };
148
138
  }
139
+ /**
140
+ * Market order by cash amount (instead of quantity).
141
+ * Server uses `isQuantityByAmount=true` + `amount` field.
142
+ */
143
+ function marketOrderByAmount(account, symbol, secType, action, amount) {
144
+ return {
145
+ account, symbol, secType, action,
146
+ orderType: enums_1.OrderType.MKT,
147
+ totalQuantity: 0,
148
+ cashAmount: amount,
149
+ timeInForce: enums_1.TimeInForce.DAY,
150
+ outsideRth: false,
151
+ };
152
+ }
153
+ /**
154
+ * Limit order by cash amount (instead of quantity).
155
+ */
156
+ function limitOrderByAmount(account, symbol, secType, action, amount, limitPrice) {
157
+ return {
158
+ account, symbol, secType, action,
159
+ orderType: enums_1.OrderType.LMT,
160
+ totalQuantity: 0,
161
+ cashAmount: amount,
162
+ limitPrice,
163
+ timeInForce: enums_1.TimeInForce.DAY,
164
+ outsideRth: false,
165
+ };
166
+ }
167
+ /**
168
+ * Trailing stop order using a fixed price offset (auxPrice) instead of a percentage.
169
+ * Use `trailOrder()` for percentage-based trailing stops.
170
+ */
171
+ function trailOrderByPrice(account, symbol, secType, action, quantity, auxPrice) {
172
+ return {
173
+ account, symbol, secType, action,
174
+ orderType: enums_1.OrderType.TRAIL,
175
+ totalQuantity: quantity,
176
+ auxPrice,
177
+ timeInForce: enums_1.TimeInForce.DAY,
178
+ outsideRth: false,
179
+ };
180
+ }
181
+ /**
182
+ * Limit order with attached profit/loss legs (bracket order).
183
+ * At most 2 legs are supported.
184
+ *
185
+ * @param orderLegs - Array of `OrderLegRequest` (use `orderLeg()` helper)
186
+ */
187
+ function limitOrderWithLegs(account, symbol, secType, action, quantity, limitPrice, orderLegs) {
188
+ if (orderLegs.length === 0) {
189
+ throw new Error('At least 1 order leg is required for a bracket order');
190
+ }
191
+ if (orderLegs.length > 2) {
192
+ throw new Error('At most 2 order legs are supported');
193
+ }
194
+ return {
195
+ account, symbol, secType, action,
196
+ orderType: enums_1.OrderType.LMT,
197
+ totalQuantity: quantity,
198
+ limitPrice,
199
+ orderLegs,
200
+ timeInForce: enums_1.TimeInForce.DAY,
201
+ outsideRth: false,
202
+ };
203
+ }
204
+ /**
205
+ * Multi-leg (combo/MLEG) order for options combos.
206
+ *
207
+ * @param contractLegs - Array of `ContractLegRequest` (use `contractLeg()` helper)
208
+ * @param comboType - e.g. `'MLEG'`
209
+ */
210
+ function comboOrder(account, action, quantity, orderType, contractLegs, comboType = 'MLEG', limitPrice, auxPrice, trailingPercent) {
211
+ return {
212
+ account,
213
+ symbol: '', // not used for combo orders
214
+ secType: 'MLEG',
215
+ action,
216
+ orderType,
217
+ totalQuantity: quantity,
218
+ limitPrice,
219
+ auxPrice,
220
+ trailingPercent,
221
+ contractLegs,
222
+ comboType,
223
+ timeInForce: enums_1.TimeInForce.DAY,
224
+ outsideRth: false,
225
+ };
226
+ }
227
+ /**
228
+ * OCA (One-Cancels-All) order.
229
+ * Server expects `orderType: 'OCA'` and an `ocaOrders` list.
230
+ *
231
+ * @param ocaOrders - The contained orders (use any order helper to build each)
232
+ */
233
+ function ocaOrder(account, symbol, secType, action, quantity, ocaOrders) {
234
+ return {
235
+ account, symbol, secType, action,
236
+ orderType: enums_1.OrderType.OCA,
237
+ totalQuantity: quantity,
238
+ ocaOrders,
239
+ timeInForce: enums_1.TimeInForce.DAY,
240
+ outsideRth: false,
241
+ };
242
+ }
243
+ /**
244
+ * Build a single leg for a multi-leg (MLEG / combo) order.
245
+ *
246
+ * @param symbol - Underlying symbol
247
+ * @param secType - Security type (e.g. `'OPT'`)
248
+ * @param action - `'BUY'` or `'SELL'`
249
+ * @param ratio - Number of contracts for this leg
250
+ */
251
+ function contractLeg(symbol, secType, action, ratio, expiry, strike, right) {
252
+ return { symbol, secType, action, ratio, expiry, strike, right };
253
+ }
@@ -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
+ }