@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.
@@ -1,4 +1,37 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.QuoteClient = void 0;
4
37
  exports.parseOptionIdentifier = parseOptionIdentifier;
@@ -12,6 +45,8 @@ exports.parseOptionIdentifier = parseOptionIdentifier;
12
45
  const http_client_1 = require("../client/http-client");
13
46
  const api_request_1 = require("../client/api-request");
14
47
  const api_response_1 = require("../client/api-response");
48
+ const tokenMethods = __importStar(require("../client/token-methods"));
49
+ const tick_util_1 = require("../push/tick-util");
15
50
  /**
16
51
  * resolveOptionTimezone returns the timezone string to use for expiry conversion.
17
52
  * Explicit tz takes precedence; otherwise infer from symbol (.HK suffix → Asia/Hong_Kong, else America/New_York).
@@ -94,6 +129,25 @@ class QuoteClient {
94
129
  static fromConfig(config) {
95
130
  return new QuoteClient(new http_client_1.HttpClient(config, undefined, { useQuoteServerUrl: true }));
96
131
  }
132
+ // ── Token management ──────────────────────────────────────────────────────
133
+ /** Call the `user_token_refresh` API and return the new token. Read-only. */
134
+ async queryToken() {
135
+ return tokenMethods.queryToken(this.httpClient);
136
+ }
137
+ /** Refresh the token and update the in-memory config token. */
138
+ async refreshToken(tokenManager) {
139
+ return tokenMethods.refreshToken(this.httpClient, tokenManager);
140
+ }
141
+ /**
142
+ * Start background token auto-refresh. Returns a TokenManager to stop with `tm.stopAutoRefresh()`.
143
+ *
144
+ * @param refreshDurationSecs - Refresh threshold in seconds (minimum 30).
145
+ * @param checkIntervalMs - How often to check, in milliseconds (default 300 000 / 5 min).
146
+ * @param tokenWriter - Optional callback invoked after each successful refresh.
147
+ */
148
+ startTokenAutoRefresh(refreshDurationSecs, checkIntervalMs, tokenWriter) {
149
+ return tokenMethods.startTokenAutoRefresh(this.httpClient, refreshDurationSecs, checkIntervalMs, tokenWriter);
150
+ }
97
151
  async callInto(method, bizParams, version) {
98
152
  const request = (0, api_request_1.createApiRequest)(method, bizParams, version);
99
153
  const response = await this.httpClient.executeRequest(request);
@@ -132,12 +186,22 @@ class QuoteClient {
132
186
  async getKline(req) {
133
187
  return this.callInto('kline', req);
134
188
  }
135
- async getTimeline(symbols) {
136
- 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);
137
192
  }
138
193
  /** Tick-by-tick trades. wire: trade_tick */
139
194
  async getTradeTick(req) {
140
- 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;
141
205
  }
142
206
  /** Depth snapshot. wire: quote_depth */
143
207
  async getQuoteDepth(req) {
@@ -303,6 +367,7 @@ class QuoteClient {
303
367
  while (acc.length < totalSize) {
304
368
  const sub = {
305
369
  symbols: req.symbol ? [req.symbol] : undefined,
370
+ secType: req.secType,
306
371
  period: req.period,
307
372
  right: req.right,
308
373
  beginTime,
@@ -353,7 +418,7 @@ class QuoteClient {
353
418
  async getStockIndustry(req) {
354
419
  return this.callInto('stock_industry', req);
355
420
  }
356
- /** Quote permission detail. wire: get_quote_permission */
421
+ /** Market data permission entries. wire: get_quote_permission */
357
422
  async getQuotePermission(req) {
358
423
  return this.callInto('get_quote_permission', req);
359
424
  }
@@ -364,9 +429,16 @@ class QuoteClient {
364
429
  // ==========================================================================
365
430
  // Batch 4: option / future extensions
366
431
  // ==========================================================================
367
- /** 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
+ */
368
440
  async getOptionTradeTicks(req) {
369
- return this.callInto('option_trade_tick', req);
441
+ return this.callInto('option_trade_tick', req.contracts ?? []);
370
442
  }
371
443
  /** Option intraday timeline. wire: option_timeline */
372
444
  async getOptionTimeline(req) {
@@ -405,10 +477,16 @@ class QuoteClient {
405
477
  return this.callInto('future_main_contract', req);
406
478
  }
407
479
  /** Futures K-line. wire: future_kline
408
- * 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. */
409
483
  async getFutureKline(req) {
484
+ const contractCodes = req.contractCodes
485
+ ?? (req.contractCode ? [req.contractCode] : undefined);
486
+ const { contractCode: _drop, ...rest } = req;
410
487
  const body = {
411
- ...req,
488
+ ...rest,
489
+ ...(contractCodes ? { contractCodes } : {}),
412
490
  beginTime: req.beginTime ?? -1,
413
491
  endTime: req.endTime ?? -1,
414
492
  };
@@ -426,8 +504,9 @@ class QuoteClient {
426
504
  endTime = -1;
427
505
  const acc = [];
428
506
  while (acc.length < totalSize) {
507
+ // Server rejects singular `contract_code` — send the plural form.
429
508
  const sub = {
430
- contractCode: req.contractCode,
509
+ contractCodes: req.contractCode ? [req.contractCode] : undefined,
431
510
  period: req.period,
432
511
  beginTime,
433
512
  endTime,
@@ -506,9 +585,30 @@ class QuoteClient {
506
585
  async getWarrantFilter(req) {
507
586
  return this.callInto('warrant_filter', req);
508
587
  }
509
- /** Industry list. wire: industry_list */
510
- async getIndustryList(req) {
511
- 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;
512
612
  }
513
613
  /** Stocks inside an industry. wire: industry_stock_list */
514
614
  async getIndustryStocks(req) {
@@ -1,4 +1,37 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.TradeClient = void 0;
4
37
  /**
@@ -13,6 +46,7 @@ exports.TradeClient = void 0;
13
46
  const http_client_1 = require("../client/http-client");
14
47
  const api_request_1 = require("../client/api-request");
15
48
  const api_response_1 = require("../client/api-response");
49
+ const tokenMethods = __importStar(require("../client/token-methods"));
16
50
  const order_status_1 = require("../model/order-status");
17
51
  /** Normalize `Order.status` field in-place (handles int → string). */
18
52
  function normalizeOrderInPlace(o) {
@@ -44,6 +78,31 @@ class TradeClient {
44
78
  static fromConfig(config, account, secretKey) {
45
79
  return new TradeClient(new http_client_1.HttpClient(config), account, secretKey);
46
80
  }
81
+ // ── Token management ──────────────────────────────────────────────────────
82
+ /** Call the `user_token_refresh` API and return the new token. Read-only. */
83
+ async queryToken() {
84
+ return tokenMethods.queryToken(this.httpClient);
85
+ }
86
+ /**
87
+ * Refresh the token: call the API, update the in-memory config token, and
88
+ * optionally persist via the provided TokenManager (file + writer callback).
89
+ *
90
+ * @param tokenManager - optional TokenManager for file persistence and writer callback
91
+ */
92
+ async refreshToken(tokenManager) {
93
+ return tokenMethods.refreshToken(this.httpClient, tokenManager);
94
+ }
95
+ /**
96
+ * Start background token auto-refresh. Returns a TokenManager that can be
97
+ * used to stop the background task via `tm.stopAutoRefresh()`.
98
+ *
99
+ * @param refreshDurationSecs - Refresh threshold in seconds (minimum 30).
100
+ * @param checkIntervalMs - How often to check, in milliseconds (default 300 000 / 5 min).
101
+ * @param tokenWriter - Optional callback invoked after each successful refresh.
102
+ */
103
+ startTokenAutoRefresh(refreshDurationSecs, checkIntervalMs, tokenWriter) {
104
+ return tokenMethods.startTokenAutoRefresh(this.httpClient, refreshDurationSecs, checkIntervalMs, tokenWriter);
105
+ }
47
106
  async callInto(method, bizParams) {
48
107
  // Inject secretKey into every request when available (institution accounts require it).
49
108
  // Individual methods that already set secretKey will not be overwritten (|| not &&).
@@ -86,14 +145,54 @@ class TradeClient {
86
145
  });
87
146
  }
88
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
+ }
89
188
  async placeOrder(order) {
90
- return this.callInto('place_order', { ...order, account: this.account });
189
+ return this.callInto('place_order', this.marshalOrderForWire(order));
91
190
  }
92
191
  async previewOrder(order) {
93
- return this.callInto('preview_order', { ...order, account: this.account });
192
+ return this.callInto('preview_order', this.marshalOrderForWire(order));
94
193
  }
95
194
  async modifyOrder(id, order) {
96
- return this.callInto('modify_order', { ...order, account: this.account, id });
195
+ return this.callInto('modify_order', { ...this.marshalOrderForWire(order), id });
97
196
  }
98
197
  async cancelOrder(id, secretKey) {
99
198
  const params = { account: this.account, id };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Call the `user_token_refresh` API and return the new token string.
3
+ * Read-only: does NOT update the stored token.
4
+ * Use `refreshToken()` to also update the in-memory config and optionally persist.
5
+ */
6
+ export async function queryToken(httpClient) {
7
+ return httpClient.queryToken();
8
+ }
9
+ /**
10
+ * Refresh the token: call the API, update the in-memory config token, and
11
+ * optionally persist via the provided TokenManager (file + writer callback).
12
+ *
13
+ * @param httpClient - The underlying HTTP client
14
+ * @param tokenManager - optional TokenManager for file persistence and writer callback
15
+ */
16
+ export async function refreshToken(httpClient, tokenManager) {
17
+ return httpClient.refreshToken(tokenManager);
18
+ }
19
+ /**
20
+ * Start background token auto-refresh. Returns a TokenManager that can be
21
+ * used to stop the background task via `tm.stopAutoRefresh()`.
22
+ *
23
+ * @param httpClient - The underlying HTTP client
24
+ * @param refreshDurationSecs - Refresh threshold in seconds (minimum 30).
25
+ * When the token age exceeds this value the refresh is triggered.
26
+ * @param checkIntervalMs - How often to check whether a refresh is needed,
27
+ * in milliseconds. Defaults to 300 000 ms (5 minutes).
28
+ * @param tokenWriter - Optional callback invoked with the new token string
29
+ * after each successful refresh.
30
+ */
31
+ export function startTokenAutoRefresh(httpClient, refreshDurationSecs, checkIntervalMs, tokenWriter) {
32
+ return httpClient.startTokenAutoRefresh(null, {
33
+ refreshDuration: refreshDurationSecs,
34
+ refreshInterval: checkIntervalMs,
35
+ tokenWriter,
36
+ });
37
+ }
@@ -9,7 +9,7 @@
9
9
  * Priority: environment variables > code options (incl. config file) > defaults
10
10
  */
11
11
  import { readFileSync, existsSync, statSync } from 'fs';
12
- import { join } from 'path';
12
+ import { join, dirname } from 'path';
13
13
  import { homedir, networkInterfaces } from 'os';
14
14
  import { parsePropertiesString } from './config-parser.js';
15
15
  import { queryDomains, resolveDynamicServerUrl, resolveDynamicQuoteServerUrl } from './domain.js';
@@ -108,23 +108,32 @@ export function createClientConfig(options) {
108
108
  const opts = options ?? {};
109
109
  // Load properties: explicit path > auto-discovery
110
110
  let fileProps = {};
111
+ let configFileDir;
111
112
  if (opts.propertiesFilePath) {
112
113
  // Support both directory path and full file path
113
114
  let filePath = opts.propertiesFilePath;
114
115
  if (existsSync(filePath) && statSync(filePath).isDirectory()) {
116
+ configFileDir = filePath;
115
117
  filePath = join(filePath, CONFIG_FILE_NAME);
116
118
  }
117
119
  else if (!existsSync(filePath) && !filePath.endsWith('.properties')) {
118
120
  // Path doesn't exist as-is; try treating it as a directory
119
121
  const asDir = join(filePath, CONFIG_FILE_NAME);
120
- if (existsSync(asDir))
122
+ if (existsSync(asDir)) {
123
+ configFileDir = filePath;
121
124
  filePath = asDir;
125
+ }
126
+ }
127
+ else {
128
+ // It's a file path: derive directory from it
129
+ configFileDir = dirname(filePath);
122
130
  }
123
131
  fileProps = loadPropertiesFile(filePath);
124
132
  }
125
133
  else {
126
134
  const discovered = discoverConfigFile();
127
135
  if (discovered) {
136
+ configFileDir = dirname(discovered);
128
137
  fileProps = loadPropertiesFile(discovered);
129
138
  }
130
139
  }
@@ -184,8 +193,9 @@ export function createClientConfig(options) {
184
193
  }
185
194
  }
186
195
  else {
187
- // Load from token file: TIGEROPEN_TOKEN_FILE env var > default file
188
- const tokenFilePath = process.env[ENV_TOKEN_FILE] || 'tiger_openapi_token.properties';
196
+ // Load from token file: TIGEROPEN_TOKEN_FILE env var > config dir > default
197
+ const tokenFilePath = process.env[ENV_TOKEN_FILE]
198
+ || (configFileDir ? join(configFileDir, 'tiger_openapi_token.properties') : 'tiger_openapi_token.properties');
189
199
  const tm = new TokenManager({ filePath: tokenFilePath });
190
200
  try {
191
201
  token = tm.loadTokenSync();
@@ -88,26 +88,11 @@ export function algoOrder(account, symbol, secType, action, quantity, limitPrice
88
88
  };
89
89
  }
90
90
  /**
91
- * Iceberg order (basic) — only displaySize required.
92
- */
93
- export function icebergOrder(account, symbol, secType, action, quantity, limitPrice, displaySize) {
94
- return {
95
- account, symbol, secType, action,
96
- orderType: OrderType.ICEBERG,
97
- totalQuantity: quantity,
98
- limitPrice,
99
- timeInForce: TimeInForce.DAY,
100
- outsideRth: false,
101
- displaySize,
102
- priceType: PriceType.LIMIT_PRICE,
103
- };
104
- }
105
- /**
106
- * Iceberg order (full params).
107
- * Pass `priceType` as empty string to use server default (LIMIT_PRICE).
91
+ * Iceberg order. `displaySize` is required; all other parameters are optional.
92
+ * Pass `priceType` as empty string to use the server default (LIMIT_PRICE).
108
93
  * Pass `startTime` / `endTime` as 0 to omit the time window.
109
94
  */
110
- export function icebergOrderFull(account, symbol, secType, action, quantity, limitPrice, displaySize, minDisplaySize, checkIntervals, priceType, startTime, endTime) {
95
+ export function icebergOrder(account, symbol, secType, action, quantity, limitPrice, displaySize, minDisplaySize, checkIntervals, priceType, startTime, endTime) {
111
96
  const req = {
112
97
  account, symbol, secType, action,
113
98
  orderType: OrderType.ICEBERG,
@@ -116,13 +101,12 @@ export function icebergOrderFull(account, symbol, secType, action, quantity, lim
116
101
  timeInForce: TimeInForce.DAY,
117
102
  outsideRth: false,
118
103
  displaySize,
104
+ priceType: priceType || PriceType.LIMIT_PRICE,
119
105
  };
120
106
  if (minDisplaySize !== undefined && minDisplaySize > 0)
121
107
  req.minDisplaySize = minDisplaySize;
122
108
  if (checkIntervals !== undefined && checkIntervals > 0)
123
109
  req.checkIntervals = checkIntervals;
124
- if (priceType)
125
- req.priceType = priceType;
126
110
  if (startTime && startTime > 0)
127
111
  req.startTime = startTime;
128
112
  if (endTime && endTime > 0)
@@ -133,3 +117,118 @@ export function icebergOrderFull(account, symbol, secType, action, quantity, lim
133
117
  export function orderLeg(legType, price, timeInForce) {
134
118
  return { legType, price, timeInForce };
135
119
  }
120
+ /**
121
+ * Market order by cash amount (instead of quantity).
122
+ * Server uses `isQuantityByAmount=true` + `amount` field.
123
+ */
124
+ export function marketOrderByAmount(account, symbol, secType, action, amount) {
125
+ return {
126
+ account, symbol, secType, action,
127
+ orderType: OrderType.MKT,
128
+ totalQuantity: 0,
129
+ cashAmount: amount,
130
+ timeInForce: TimeInForce.DAY,
131
+ outsideRth: false,
132
+ };
133
+ }
134
+ /**
135
+ * Limit order by cash amount (instead of quantity).
136
+ */
137
+ export function limitOrderByAmount(account, symbol, secType, action, amount, limitPrice) {
138
+ return {
139
+ account, symbol, secType, action,
140
+ orderType: OrderType.LMT,
141
+ totalQuantity: 0,
142
+ cashAmount: amount,
143
+ limitPrice,
144
+ timeInForce: TimeInForce.DAY,
145
+ outsideRth: false,
146
+ };
147
+ }
148
+ /**
149
+ * Trailing stop order using a fixed price offset (auxPrice) instead of a percentage.
150
+ * Use `trailOrder()` for percentage-based trailing stops.
151
+ */
152
+ export function trailOrderByPrice(account, symbol, secType, action, quantity, auxPrice) {
153
+ return {
154
+ account, symbol, secType, action,
155
+ orderType: OrderType.TRAIL,
156
+ totalQuantity: quantity,
157
+ auxPrice,
158
+ timeInForce: TimeInForce.DAY,
159
+ outsideRth: false,
160
+ };
161
+ }
162
+ /**
163
+ * Limit order with attached profit/loss legs (bracket order).
164
+ * At most 2 legs are supported.
165
+ *
166
+ * @param orderLegs - Array of `OrderLegRequest` (use `orderLeg()` helper)
167
+ */
168
+ export function limitOrderWithLegs(account, symbol, secType, action, quantity, limitPrice, orderLegs) {
169
+ if (orderLegs.length === 0) {
170
+ throw new Error('At least 1 order leg is required for a bracket order');
171
+ }
172
+ if (orderLegs.length > 2) {
173
+ throw new Error('At most 2 order legs are supported');
174
+ }
175
+ return {
176
+ account, symbol, secType, action,
177
+ orderType: OrderType.LMT,
178
+ totalQuantity: quantity,
179
+ limitPrice,
180
+ orderLegs,
181
+ timeInForce: TimeInForce.DAY,
182
+ outsideRth: false,
183
+ };
184
+ }
185
+ /**
186
+ * Multi-leg (combo/MLEG) order for options combos.
187
+ *
188
+ * @param contractLegs - Array of `ContractLegRequest` (use `contractLeg()` helper)
189
+ * @param comboType - e.g. `'MLEG'`
190
+ */
191
+ export function comboOrder(account, action, quantity, orderType, contractLegs, comboType = 'MLEG', limitPrice, auxPrice, trailingPercent) {
192
+ return {
193
+ account,
194
+ symbol: '', // not used for combo orders
195
+ secType: 'MLEG',
196
+ action,
197
+ orderType,
198
+ totalQuantity: quantity,
199
+ limitPrice,
200
+ auxPrice,
201
+ trailingPercent,
202
+ contractLegs,
203
+ comboType,
204
+ timeInForce: TimeInForce.DAY,
205
+ outsideRth: false,
206
+ };
207
+ }
208
+ /**
209
+ * OCA (One-Cancels-All) order.
210
+ * Server expects `orderType: 'OCA'` and an `ocaOrders` list.
211
+ *
212
+ * @param ocaOrders - The contained orders (use any order helper to build each)
213
+ */
214
+ export function ocaOrder(account, symbol, secType, action, quantity, ocaOrders) {
215
+ return {
216
+ account, symbol, secType, action,
217
+ orderType: OrderType.OCA,
218
+ totalQuantity: quantity,
219
+ ocaOrders,
220
+ timeInForce: TimeInForce.DAY,
221
+ outsideRth: false,
222
+ };
223
+ }
224
+ /**
225
+ * Build a single leg for a multi-leg (MLEG / combo) order.
226
+ *
227
+ * @param symbol - Underlying symbol
228
+ * @param secType - Security type (e.g. `'OPT'`)
229
+ * @param action - `'BUY'` or `'SELL'`
230
+ * @param ratio - Number of contracts for this leg
231
+ */
232
+ export function contractLeg(symbol, secType, action, ratio, expiry, strike, right) {
233
+ return { symbol, secType, action, ratio, expiry, strike, right };
234
+ }
@@ -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;