@tigeropenapi/tigeropen 0.5.0 → 0.5.3

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.
@@ -7,29 +7,33 @@ exports.unmarshalData = unmarshalData;
7
7
  */
8
8
  const errors_1 = require("./errors");
9
9
  /**
10
- * Replace bare integers (not inside JSON string values) that exceed
11
- * Number.MAX_SAFE_INTEGER (> 15 digits) with quoted string equivalents,
12
- * so that int64 order IDs survive JSON.parse without precision loss.
10
+ * Replaces bare int64 values (≥17 digits, not part of a float) with quoted
11
+ * strings so they survive JSON.parse without precision loss.
12
+ * 17+ digits are guaranteed to exceed Number.MAX_SAFE_INTEGER (≈9.007e15),
13
+ * so 16-digit microsecond timestamps remain as numbers.
13
14
  */
14
15
  function patchLargeIntegers(text) {
15
- // Mask all existing JSON string values so their content isn't touched.
16
16
  const strings = [];
17
17
  const masked = text.replace(/"(?:[^"\\]|\\.)*"/g, (match) => {
18
18
  strings.push(match);
19
19
  return '\x01' + (strings.length - 1) + '\x01';
20
20
  });
21
- // Quote any bare integer with >15 digits.
22
- const patched = masked.replace(/-?\d{16,}/g, (m) => '"' + m + '"');
23
- // Restore original string values.
21
+ // Match bare integers preceded by a JSON structural character or line start,
22
+ // not followed by decimal point or exponent (avoids matching float components).
23
+ // The `m` flag makes `^` match line-start for multi-line JSON.
24
+ const patched = masked.replace(/([:,\[{]|^)\s*(-?\d{17,})(?![.\deE])/gm, (full, prefix, num) => {
25
+ return prefix + '"' + num + '"';
26
+ });
24
27
  return patched.replace(/\x01(\d+)\x01/g, (_, i) => strings[parseInt(i, 10)]);
25
28
  }
26
29
  /**
27
- * Parse the raw HTTP response body.
28
- * Throws TigerError when code != 0.
29
- * Large integers in the outer envelope are not an issue (only inner `data` contains int64 IDs).
30
+ * Parse the raw HTTP response body. Throws TigerError when code != 0.
31
+ *
32
+ * patchLargeIntegers is applied before JSON.parse to preserve int64 order IDs
33
+ * (e.g. 28868646234578944) as strings rather than losing precision.
30
34
  */
31
35
  function parseApiResponse(body) {
32
- const resp = JSON.parse(body);
36
+ const resp = JSON.parse(patchLargeIntegers(body));
33
37
  if (resp.code !== 0) {
34
38
  throw new errors_1.TigerError(resp.code, resp.message);
35
39
  }
@@ -37,15 +41,13 @@ function parseApiResponse(body) {
37
41
  }
38
42
  /**
39
43
  * Decode an ApiResponse.data payload into a typed value.
40
- * Handles the server's occasional double-encoded JSON (where `data` is a
41
- * JSON string that itself wraps JSON), seen on some trade endpoints.
44
+ * Handles double-encoded JSON (where `data` is a JSON string wrapping JSON),
45
+ * seen on some trade endpoints.
42
46
  */
43
47
  function unmarshalData(data) {
44
48
  if (data == null)
45
49
  return undefined;
46
50
  if (typeof data === 'string') {
47
- // Attempt to parse JSON string (double-encoded case).
48
- // Patch large integers before parsing so int64 IDs survive as strings.
49
51
  try {
50
52
  return JSON.parse(patchLargeIntegers(data));
51
53
  }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SANDBOX_TIGER_PUBLIC_KEY = void 0;
3
4
  exports.loadPropertiesFile = loadPropertiesFile;
4
5
  exports.createClientConfig = createClientConfig;
5
6
  /**
@@ -26,6 +27,10 @@ const DEFAULT_TIMEOUT = 15;
26
27
  const DEFAULT_SERVER_URL = 'https://openapi.tigerfintech.com/gateway';
27
28
  /** Tiger public key for response signature verification (Base64-encoded) */
28
29
  const TIGER_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNF3G8SoEcCZh2rshUbayDgLLrj6rKgzNMxDL2HSnKcB0+GPOsndqSv+a4IBu9+I3fyBp5hkyMMG2+AXugd9pMpy6VxJxlNjhX1MYbNTZJUT4nudki4uh+LMOkIBHOceGNXjgB+cXqmlUnjlqha/HgboeHSnSgpM3dKSJQlIOsDwIDAQAB';
30
+ /** Sandbox / QA tiger public key for response signature verification */
31
+ exports.SANDBOX_TIGER_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbm21i11hgAENGd3/f280PSe4g9YGkS3TEXBYMidihTvHHf' +
32
+ '+tJ0PYD0o3PruI0hl3qhEjHTAxb75T5YD3SGK4IBhHn/Rk6mhqlGgI+bBrBVYaXixmHfRo75RpUUuWACyeqQk' +
33
+ 'ZckgR0McxuW9xRMIa2cXZOoL1E4SL4lXKGhKoWbwIDAQAB';
29
34
  /** Environment variable names */
30
35
  const ENV_TIGER_ID = 'TIGEROPEN_TIGER_ID';
31
36
  const ENV_PRIVATE_KEY = 'TIGEROPEN_PRIVATE_KEY';
@@ -33,6 +38,9 @@ const ENV_ACCOUNT = 'TIGEROPEN_ACCOUNT';
33
38
  const ENV_SECRET_KEY = 'TIGEROPEN_SECRET_KEY';
34
39
  const ENV_TOKEN = 'TIGEROPEN_TOKEN';
35
40
  const ENV_TOKEN_FILE = 'TIGEROPEN_TOKEN_FILE';
41
+ const ENV_TIGER_PUBLIC_KEY = 'TIGEROPEN_TIGER_PUBLIC_KEY';
42
+ const ENV_SERVER_URL = 'TIGEROPEN_SERVER_URL';
43
+ const ENV_QUOTE_SERVER_URL = 'TIGEROPEN_QUOTE_SERVER_URL';
36
44
  /**
37
45
  * Load and parse a Java-style properties file.
38
46
  *
@@ -134,12 +142,15 @@ function createClientConfig(options) {
134
142
  const license = opts.license || fileProps['license'];
135
143
  const language = opts.language || fileProps['language'] || DEFAULT_LANGUAGE;
136
144
  const timezone = opts.timezone || fileProps['timezone'];
145
+ // tiger_public_key from properties file (e.g. for QA/sandbox environments)
146
+ let tigerPublicKey = opts.tigerPublicKey || fileProps['tiger_public_key'] || TIGER_PUBLIC_KEY;
137
147
  // Environment variables override everything
138
148
  const envTigerId = process.env[ENV_TIGER_ID];
139
149
  const envPrivateKey = process.env[ENV_PRIVATE_KEY];
140
150
  const envAccount = process.env[ENV_ACCOUNT];
141
151
  const envSecretKey = process.env[ENV_SECRET_KEY];
142
152
  const envToken = process.env[ENV_TOKEN];
153
+ const envTigerPublicKey = process.env[ENV_TIGER_PUBLIC_KEY];
143
154
  if (envTigerId) {
144
155
  tigerId = envTigerId;
145
156
  }
@@ -155,6 +166,9 @@ function createClientConfig(options) {
155
166
  if (envToken) {
156
167
  token = envToken;
157
168
  }
169
+ if (envTigerPublicKey) {
170
+ tigerPublicKey = envTigerPublicKey;
171
+ }
158
172
  // Token loading priority: env TIGEROPEN_TOKEN > tokenLoader > token file (TIGEROPEN_TOKEN_FILE or default)
159
173
  if (!token) {
160
174
  if (opts.tokenLoader) {
@@ -193,29 +207,53 @@ function createClientConfig(options) {
193
207
  if (!privateKey) {
194
208
  throw new Error(`privateKey is required. Set it via options.privateKey, a config file, or the ${ENV_PRIVATE_KEY} environment variable.`);
195
209
  }
196
- // Resolve server URL: explicit > dynamic domain > default
210
+ // Resolve server URL: env var > explicit opts > file > dynamic domain > default
197
211
  const enableDynamicDomain = opts.enableDynamicDomain ?? true;
198
212
  let serverUrl;
199
213
  let quoteServerUrl;
200
214
  // Query dynamic domains once (may spawn a child process); reuse for both URLs.
201
215
  let domainConf = {};
202
- if (enableDynamicDomain && (!opts.serverUrl || !opts.quoteServerUrl)) {
216
+ const envServerUrl = process.env[ENV_SERVER_URL];
217
+ const envQuoteServerUrl = process.env[ENV_QUOTE_SERVER_URL];
218
+ const needServerUrl = !envServerUrl && !opts.serverUrl && !fileProps['server_url'];
219
+ const needQuoteServerUrl = !envQuoteServerUrl && !opts.quoteServerUrl && !fileProps['quote_server_url'];
220
+ if (enableDynamicDomain && (needServerUrl || needQuoteServerUrl)) {
203
221
  domainConf = (0, domain_1.queryDomains)(license);
204
222
  }
205
- if (opts.serverUrl) {
223
+ if (envServerUrl) {
224
+ serverUrl = envServerUrl;
225
+ }
226
+ else if (opts.serverUrl) {
206
227
  serverUrl = opts.serverUrl;
207
228
  }
229
+ else if (fileProps['server_url']) {
230
+ serverUrl = fileProps['server_url'];
231
+ }
208
232
  else {
209
233
  const dynamicUrl = (0, domain_1.resolveDynamicServerUrl)(domainConf, license);
210
234
  serverUrl = dynamicUrl || DEFAULT_SERVER_URL;
211
235
  }
212
- // Resolve quote server URL: explicit > dynamic domain > serverUrl fallback
213
- if (opts.quoteServerUrl) {
236
+ // Resolve quote server URL: env var > explicit opts > file > dynamic domain > serverUrl fallback
237
+ if (envQuoteServerUrl) {
238
+ quoteServerUrl = envQuoteServerUrl;
239
+ }
240
+ else if (opts.quoteServerUrl) {
214
241
  quoteServerUrl = opts.quoteServerUrl;
215
242
  }
243
+ else if (fileProps['quote_server_url']) {
244
+ quoteServerUrl = fileProps['quote_server_url'];
245
+ }
216
246
  else {
217
- const dynamicQuoteUrl = (0, domain_1.resolveDynamicQuoteServerUrl)(domainConf, license);
218
- quoteServerUrl = dynamicQuoteUrl || serverUrl;
247
+ // Only use dynamic domain if serverUrl itself came from dynamic resolution (not pinned by env/opts/file).
248
+ // When serverUrl is pinned, prefer it as the quote fallback to avoid mixing environments.
249
+ const serverUrlPinned = !!(envServerUrl || opts.serverUrl || fileProps['server_url']);
250
+ if (!serverUrlPinned) {
251
+ const dynamicQuoteUrl = (0, domain_1.resolveDynamicQuoteServerUrl)(domainConf, license);
252
+ quoteServerUrl = dynamicQuoteUrl || serverUrl;
253
+ }
254
+ else {
255
+ quoteServerUrl = serverUrl;
256
+ }
219
257
  }
220
258
  return {
221
259
  tigerId,
@@ -234,7 +272,7 @@ function createClientConfig(options) {
234
272
  serverUrl,
235
273
  quoteServerUrl,
236
274
  deviceId: detectDeviceId(),
237
- tigerPublicKey: opts.tigerPublicKey ?? TIGER_PUBLIC_KEY,
275
+ tigerPublicKey,
238
276
  };
239
277
  }
240
278
  /**
@@ -70,34 +70,34 @@ function parsePropertiesString(content) {
70
70
  let continuation = false;
71
71
  for (const rawLine of lines) {
72
72
  if (continuation) {
73
- // 续行:去除前导空格后拼接
74
73
  const trimmedLeft = rawLine.replace(/^[ \t]+/, '');
75
- if (trimmedLeft.endsWith('\\')) {
74
+ if (trimmedLeft === '' || trimmedLeft.startsWith('#') || trimmedLeft.startsWith('!')) {
75
+ continuation = false;
76
+ }
77
+ else if (endsWithContinuation(trimmedLeft)) {
76
78
  currentLine += trimmedLeft.slice(0, -1);
77
79
  continue;
78
80
  }
79
- currentLine += trimmedLeft;
80
- continuation = false;
81
+ else {
82
+ currentLine += trimmedLeft;
83
+ continuation = false;
84
+ }
81
85
  }
82
86
  else {
83
87
  const trimmed = rawLine.trim();
84
- // 跳过空行
85
88
  if (trimmed === '') {
86
89
  continue;
87
90
  }
88
- // 跳过注释行
89
91
  if (trimmed.startsWith('#') || trimmed.startsWith('!')) {
90
92
  continue;
91
93
  }
92
- // 检查续行
93
- if (trimmed.endsWith('\\')) {
94
+ if (endsWithContinuation(trimmed)) {
94
95
  currentLine = trimmed.slice(0, -1);
95
96
  continuation = true;
96
97
  continue;
97
98
  }
98
99
  currentLine = trimmed;
99
100
  }
100
- // 解析键值对
101
101
  const result = parseKeyValue(currentLine);
102
102
  if (result) {
103
103
  props[result[0]] = result[1];
@@ -113,6 +113,16 @@ function parsePropertiesString(content) {
113
113
  }
114
114
  return props;
115
115
  }
116
+ /**
117
+ * 判断一行是否以续行标记结尾(奇数个末尾反斜杠)。
118
+ */
119
+ function endsWithContinuation(line) {
120
+ let count = 0;
121
+ for (let i = line.length - 1; i >= 0 && line[i] === '\\'; i--) {
122
+ count++;
123
+ }
124
+ return count % 2 === 1;
125
+ }
116
126
  /**
117
127
  * 解析单行键值对,支持 = 和 : 分隔符。
118
128
  * 值中可以包含 = 或 :,只按第一个分隔符拆分。
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveDynamicQuoteServerUrl = exports.resolveDynamicServerUrl = exports.queryDomains = exports.TokenManager = exports.loadPropertiesFile = exports.createClientConfig = exports.parsePropertiesString = exports.parsePropertiesFile = void 0;
3
+ exports.resolveDynamicQuoteServerUrl = exports.resolveDynamicServerUrl = exports.queryDomains = exports.TokenManager = exports.SANDBOX_TIGER_PUBLIC_KEY = exports.loadPropertiesFile = exports.createClientConfig = exports.parsePropertiesString = exports.parsePropertiesFile = void 0;
4
4
  /**
5
5
  * 配置层导出
6
6
  */
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "parsePropertiesString", { enumerable: true, get:
10
10
  var client_config_1 = require("./client-config");
11
11
  Object.defineProperty(exports, "createClientConfig", { enumerable: true, get: function () { return client_config_1.createClientConfig; } });
12
12
  Object.defineProperty(exports, "loadPropertiesFile", { enumerable: true, get: function () { return client_config_1.loadPropertiesFile; } });
13
+ Object.defineProperty(exports, "SANDBOX_TIGER_PUBLIC_KEY", { enumerable: true, get: function () { return client_config_1.SANDBOX_TIGER_PUBLIC_KEY; } });
13
14
  var token_manager_1 = require("./token-manager");
14
15
  Object.defineProperty(exports, "TokenManager", { enumerable: true, get: function () { return token_manager_1.TokenManager; } });
15
16
  var domain_1 = require("./domain");
package/dist/cjs/index.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * @packageDocumentation
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.orderStatusCode = exports.BarPeriod = exports.License = exports.Language = exports.FinancialReportPeriod = exports.OptionAnalysisPeriod = exports.SortDirection = exports.IndustryLevel = exports.CorporateActionType = exports.OrderSortBy = exports.SegmentType = exports.QuoteRight = exports.Currency = exports.Market = exports.TimeInForce = exports.OrderType = exports.SecurityType = exports.OrderStatus = exports.normalizeOrderStatus = exports.stopLimitOrder = exports.stopOrder = exports.limitOrder = exports.marketOrder = exports.SubjectType = exports.ConnectionState = exports.PushClient = exports.TradeClient = exports.parseOptionIdentifier = exports.QuoteClient = exports.classifyErrorCode = exports.TigerError = exports.HttpClient = exports.TokenManager = exports.loadPropertiesFile = exports.createClientConfig = exports.VERSION = void 0;
19
+ exports.orderStatusCode = exports.BarPeriod = exports.License = exports.Language = exports.FinancialReportPeriod = exports.OptionAnalysisPeriod = exports.SortDirection = exports.IndustryLevel = exports.CorporateActionType = exports.OrderSortBy = exports.SegmentType = exports.QuoteRight = exports.Currency = exports.Market = exports.TimeInForce = exports.OrderType = exports.SecurityType = exports.OrderStatus = exports.normalizeOrderStatus = exports.stopLimitOrder = exports.stopOrder = exports.limitOrder = exports.marketOrder = exports.SubjectType = exports.ConnectionState = exports.PushClient = exports.TradeClient = exports.parseOptionIdentifier = exports.QuoteClient = exports.classifyErrorCode = exports.TigerError = exports.HttpClient = exports.TokenManager = exports.SANDBOX_TIGER_PUBLIC_KEY = exports.loadPropertiesFile = exports.createClientConfig = exports.VERSION = void 0;
20
20
  /** SDK 版本号 */
21
21
  var version_1 = require("./version");
22
22
  Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.SDK_VERSION; } });
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () {
24
24
  var config_1 = require("./config");
25
25
  Object.defineProperty(exports, "createClientConfig", { enumerable: true, get: function () { return config_1.createClientConfig; } });
26
26
  Object.defineProperty(exports, "loadPropertiesFile", { enumerable: true, get: function () { return config_1.loadPropertiesFile; } });
27
+ Object.defineProperty(exports, "SANDBOX_TIGER_PUBLIC_KEY", { enumerable: true, get: function () { return config_1.SANDBOX_TIGER_PUBLIC_KEY; } });
27
28
  var config_2 = require("./config");
28
29
  Object.defineProperty(exports, "TokenManager", { enumerable: true, get: function () { return config_2.TokenManager; } });
29
30
  // Transport
@@ -150,6 +150,7 @@ var TimeInForce;
150
150
  (function (TimeInForce) {
151
151
  TimeInForce["DAY"] = "DAY";
152
152
  TimeInForce["GTC"] = "GTC";
153
+ TimeInForce["GTD"] = "GTD";
153
154
  TimeInForce["OPG"] = "OPG";
154
155
  })(TimeInForce || (exports.TimeInForce = TimeInForce = {}));
155
156
  /** 订单排序字段 */
@@ -172,6 +173,9 @@ var CorporateActionType;
172
173
  CorporateActionType["Split"] = "split";
173
174
  CorporateActionType["Dividend"] = "dividend";
174
175
  CorporateActionType["Earning"] = "earning";
176
+ CorporateActionType["SymbolChange"] = "symbol_change";
177
+ CorporateActionType["Delisting"] = "delisting";
178
+ CorporateActionType["IPO"] = "ipo";
175
179
  })(CorporateActionType || (exports.CorporateActionType = CorporateActionType = {}));
176
180
  /** 行业级别 (1~4 级) */
177
181
  var IndustryLevel;
@@ -155,8 +155,10 @@ class QuoteClient {
155
155
  *
156
156
  * @param timezone - Optional IANA timezone for expiry conversion (e.g. 'America/New_York').
157
157
  * If omitted, defaults to America/New_York for US symbols and Asia/Hong_Kong for .HK symbols.
158
+ * @param returnGreekValue - Whether to return Greeks (delta/gamma/theta/vega/rho).
159
+ * @param optionFilter - Optional filter for in-the-money status, IV range, open interest range, Greeks ranges.
158
160
  */
159
- async getOptionChain(items, timezone) {
161
+ async getOptionChain(items, timezone, returnGreekValue, optionFilter) {
160
162
  const optionBasic = items.map(([symbol, expiry]) => {
161
163
  if (!/^\d{4}-\d{2}-\d{2}$/.test(expiry)) {
162
164
  throw new Error(`invalid expiry date, expected YYYY-MM-DD: ${expiry}`);
@@ -165,37 +167,27 @@ class QuoteClient {
165
167
  const expiryMs = localMidnightMs(expiry, tz);
166
168
  return { symbol, expiry: expiryMs };
167
169
  });
168
- return this.callInto('option_chain', { option_basic: optionBasic }, '3.0');
169
- }
170
- /**
171
- * Real-time option quotes by OCC-style identifier.
172
- *
173
- * @param timezone - Optional IANA timezone for expiry conversion (e.g. 'America/New_York').
174
- * If omitted, defaults to America/New_York for US symbols and Asia/Hong_Kong for .HK symbols.
175
- */
176
- async getOptionQuote(identifiers, timezone) {
177
- const optionBasic = identifiers.map((id) => {
178
- const p = parseOptionIdentifier(id, timezone);
179
- return { symbol: p.symbol, expiry: p.expiryMs, right: p.right, strike: p.strike };
180
- });
181
- return this.callInto('option_brief', { option_basic: optionBasic }, '2.0');
182
- }
183
- /** @deprecated Use getOptionQuote instead. */
184
- async getOptionBrief(identifiers) {
185
- return this.getOptionQuote(identifiers);
170
+ const body = { option_basic: optionBasic };
171
+ if (returnGreekValue !== undefined)
172
+ body['return_greek_value'] = returnGreekValue;
173
+ if (optionFilter !== undefined)
174
+ body['option_filter'] = optionFilter;
175
+ return this.callInto('option_chain', body, '3.0');
186
176
  }
187
177
  /**
188
178
  * Option K-line by OCC-style identifier.
189
179
  *
190
180
  * @param timezone - Optional IANA timezone for expiry conversion (e.g. 'America/New_York').
191
181
  * If omitted, defaults to America/New_York for US symbols and Asia/Hong_Kong for .HK symbols.
182
+ * @param limit - Maximum number of bars to return.
183
+ * @param sortDir - Sort direction ('asc' or 'desc').
192
184
  */
193
- async getOptionKline(identifiers, period, beginTime = -1, endTime = -1, timezone) {
185
+ async getOptionKline(identifiers, period, beginTime = -1, endTime = -1, timezone, limit, sortDir) {
194
186
  const resolvedBegin = beginTime === 0 ? -1 : beginTime;
195
187
  const resolvedEnd = endTime === 0 ? -1 : endTime;
196
188
  const optionQuery = identifiers.map((id) => {
197
189
  const p = parseOptionIdentifier(id, timezone);
198
- return {
190
+ const entry = {
199
191
  symbol: p.symbol,
200
192
  expiry: p.expiryMs,
201
193
  right: p.right,
@@ -204,9 +196,31 @@ class QuoteClient {
204
196
  begin_time: resolvedBegin,
205
197
  end_time: resolvedEnd,
206
198
  };
199
+ if (limit !== undefined && limit > 0)
200
+ entry['limit'] = limit;
201
+ if (sortDir !== undefined && sortDir !== '')
202
+ entry['sort_dir'] = sortDir;
203
+ return entry;
207
204
  });
208
205
  return this.callInto('option_kline', { option_query: optionQuery }, '2.0');
209
206
  }
207
+ /**
208
+ * Real-time option quotes by OCC-style identifier.
209
+ *
210
+ * @param timezone - Optional IANA timezone for expiry conversion (e.g. 'America/New_York').
211
+ * If omitted, defaults to America/New_York for US symbols and Asia/Hong_Kong for .HK symbols.
212
+ */
213
+ async getOptionQuote(identifiers, timezone) {
214
+ const optionBasic = identifiers.map((id) => {
215
+ const p = parseOptionIdentifier(id, timezone);
216
+ return { symbol: p.symbol, expiry: p.expiryMs, right: p.right, strike: p.strike };
217
+ });
218
+ return this.callInto('option_brief', { option_basic: optionBasic }, '2.0');
219
+ }
220
+ /** @deprecated Use getOptionQuote instead. */
221
+ async getOptionBrief(identifiers) {
222
+ return this.getOptionQuote(identifiers);
223
+ }
210
224
  // === Futures ===
211
225
  async getFutureExchange() {
212
226
  return this.callInto('future_exchange', { secType: 'FUT' });
@@ -524,6 +538,30 @@ class QuoteClient {
524
538
  return [];
525
539
  return Object.values(grouped).flat();
526
540
  }
541
+ /** Corporate symbol change (flattened). wire: corporate_action (action_type=symbol_change) */
542
+ async getCorporateSymbolChange(req) {
543
+ const body = { ...req, actionType: 'symbol_change' };
544
+ const grouped = await this.callInto('corporate_action', body);
545
+ if (!grouped || typeof grouped !== 'object')
546
+ return [];
547
+ return Object.values(grouped).flat();
548
+ }
549
+ /** Corporate delisting (flattened). wire: corporate_action (action_type=delisting) */
550
+ async getCorporateDelisting(req) {
551
+ const body = { ...req, actionType: 'delisting' };
552
+ const grouped = await this.callInto('corporate_action', body);
553
+ if (!grouped || typeof grouped !== 'object')
554
+ return [];
555
+ return Object.values(grouped).flat();
556
+ }
557
+ /** Corporate IPO (flattened). wire: corporate_action (action_type=ipo) */
558
+ async getCorporateIPO(req) {
559
+ const body = { ...req, actionType: 'ipo' };
560
+ const grouped = await this.callInto('corporate_action', body);
561
+ if (!grouped || typeof grouped !== 'object')
562
+ return [];
563
+ return Object.values(grouped).flat();
564
+ }
527
565
  /** Financial currency per symbol. wire: financial_currency */
528
566
  async getFinancialCurrency(req) {
529
567
  return this.callInto('financial_currency', req);
@@ -45,7 +45,16 @@ class TradeClient {
45
45
  return new TradeClient(new http_client_1.HttpClient(config), account, secretKey);
46
46
  }
47
47
  async callInto(method, bizParams) {
48
- const request = (0, api_request_1.createApiRequest)(method, bizParams);
48
+ // Inject secretKey into every request when available (institution accounts require it).
49
+ // Individual methods that already set secretKey will not be overwritten (|| not &&).
50
+ let params = bizParams;
51
+ if (this.secretKey && params !== null && typeof params === 'object' && !Array.isArray(params)) {
52
+ const p = params;
53
+ if (p['secretKey'] == null && p['secret_key'] == null) {
54
+ params = { ...p, secretKey: this.secretKey };
55
+ }
56
+ }
57
+ const request = (0, api_request_1.createApiRequest)(method, params);
49
58
  const response = await this.httpClient.executeRequest(request);
50
59
  return (0, api_response_1.unmarshalData)(response.data);
51
60
  }
@@ -78,25 +87,18 @@ class TradeClient {
78
87
  }
79
88
  // === Order operations ===
80
89
  async placeOrder(order) {
81
- const params = { ...order, account: this.account };
82
- if (!params.secretKey && this.secretKey)
83
- params.secretKey = this.secretKey;
84
- return this.callInto('place_order', params);
90
+ return this.callInto('place_order', { ...order, account: this.account });
85
91
  }
86
92
  async previewOrder(order) {
87
93
  return this.callInto('preview_order', { ...order, account: this.account });
88
94
  }
89
95
  async modifyOrder(id, order) {
90
- const params = { ...order, account: this.account, id };
91
- if (!params.secretKey && this.secretKey)
92
- params.secretKey = this.secretKey;
93
- return this.callInto('modify_order', params);
96
+ return this.callInto('modify_order', { ...order, account: this.account, id });
94
97
  }
95
98
  async cancelOrder(id, secretKey) {
96
- const sk = secretKey ?? this.secretKey;
97
99
  const params = { account: this.account, id };
98
- if (sk)
99
- params.secretKey = sk;
100
+ if (secretKey !== undefined)
101
+ params.secretKey = secretKey; // per-call override takes precedence; undefined means use config default
100
102
  return this.callInto('cancel_order', params);
101
103
  }
102
104
  // === Order queries (BREAKING v0.4.0) ===
@@ -134,14 +136,11 @@ class TradeClient {
134
136
  }
135
137
  /**
136
138
  * Get a single order by ID (v0.4.0 new).
137
- * Wire method: `orders` with id/order_id param; server returns a single Order object
138
- * (not `{items:[]}` envelope).
139
- *
140
139
  * Returns `undefined` if no match.
141
140
  */
142
141
  async getOrder(req) {
143
142
  const merged = withAccount(req, this.account);
144
- const out = await this.callInto('orders', merged);
143
+ const out = await this.callInto('order_no', merged);
145
144
  if (!out || (!out.id && !out.orderId))
146
145
  return undefined;
147
146
  normalizeOrderInPlace(out);
@@ -3,29 +3,33 @@
3
3
  */
4
4
  import { TigerError } from './errors.js';
5
5
  /**
6
- * Replace bare integers (not inside JSON string values) that exceed
7
- * Number.MAX_SAFE_INTEGER (> 15 digits) with quoted string equivalents,
8
- * so that int64 order IDs survive JSON.parse without precision loss.
6
+ * Replaces bare int64 values (≥17 digits, not part of a float) with quoted
7
+ * strings so they survive JSON.parse without precision loss.
8
+ * 17+ digits are guaranteed to exceed Number.MAX_SAFE_INTEGER (≈9.007e15),
9
+ * so 16-digit microsecond timestamps remain as numbers.
9
10
  */
10
11
  function patchLargeIntegers(text) {
11
- // Mask all existing JSON string values so their content isn't touched.
12
12
  const strings = [];
13
13
  const masked = text.replace(/"(?:[^"\\]|\\.)*"/g, (match) => {
14
14
  strings.push(match);
15
15
  return '\x01' + (strings.length - 1) + '\x01';
16
16
  });
17
- // Quote any bare integer with >15 digits.
18
- const patched = masked.replace(/-?\d{16,}/g, (m) => '"' + m + '"');
19
- // Restore original string values.
17
+ // Match bare integers preceded by a JSON structural character or line start,
18
+ // not followed by decimal point or exponent (avoids matching float components).
19
+ // The `m` flag makes `^` match line-start for multi-line JSON.
20
+ const patched = masked.replace(/([:,\[{]|^)\s*(-?\d{17,})(?![.\deE])/gm, (full, prefix, num) => {
21
+ return prefix + '"' + num + '"';
22
+ });
20
23
  return patched.replace(/\x01(\d+)\x01/g, (_, i) => strings[parseInt(i, 10)]);
21
24
  }
22
25
  /**
23
- * Parse the raw HTTP response body.
24
- * Throws TigerError when code != 0.
25
- * Large integers in the outer envelope are not an issue (only inner `data` contains int64 IDs).
26
+ * Parse the raw HTTP response body. Throws TigerError when code != 0.
27
+ *
28
+ * patchLargeIntegers is applied before JSON.parse to preserve int64 order IDs
29
+ * (e.g. 28868646234578944) as strings rather than losing precision.
26
30
  */
27
31
  export function parseApiResponse(body) {
28
- const resp = JSON.parse(body);
32
+ const resp = JSON.parse(patchLargeIntegers(body));
29
33
  if (resp.code !== 0) {
30
34
  throw new TigerError(resp.code, resp.message);
31
35
  }
@@ -33,15 +37,13 @@ export function parseApiResponse(body) {
33
37
  }
34
38
  /**
35
39
  * Decode an ApiResponse.data payload into a typed value.
36
- * Handles the server's occasional double-encoded JSON (where `data` is a
37
- * JSON string that itself wraps JSON), seen on some trade endpoints.
40
+ * Handles double-encoded JSON (where `data` is a JSON string wrapping JSON),
41
+ * seen on some trade endpoints.
38
42
  */
39
43
  export function unmarshalData(data) {
40
44
  if (data == null)
41
45
  return undefined;
42
46
  if (typeof data === 'string') {
43
- // Attempt to parse JSON string (double-encoded case).
44
- // Patch large integers before parsing so int64 IDs survive as strings.
45
47
  try {
46
48
  return JSON.parse(patchLargeIntegers(data));
47
49
  }