@tigeropenapi/tigeropen 0.3.0 → 0.4.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.
Files changed (64) hide show
  1. package/dist/cjs/client/case-convert.js +4 -3
  2. package/dist/cjs/client/http-client.js +123 -6
  3. package/dist/cjs/config/client-config.js +56 -12
  4. package/dist/cjs/config/index.js +3 -1
  5. package/dist/cjs/config/token-manager.js +54 -4
  6. package/dist/cjs/index.js +51 -2
  7. package/dist/cjs/model/enums.js +89 -7
  8. package/dist/cjs/model/order-status.js +46 -0
  9. package/dist/cjs/model/quote-requests.js +11 -0
  10. package/dist/cjs/model/trade-requests.js +13 -0
  11. package/dist/cjs/push/pb/OrderStatusData.js +24 -0
  12. package/dist/cjs/push/proto-message.js +6 -0
  13. package/dist/cjs/push/push-client.js +111 -16
  14. package/dist/cjs/push/push-message.js +4 -0
  15. package/dist/cjs/quote/quote-client.js +333 -9
  16. package/dist/cjs/trade/trade-client.js +204 -25
  17. package/dist/esm/client/api-request.js +1 -1
  18. package/dist/esm/client/api-response.js +1 -1
  19. package/dist/esm/client/case-convert.js +4 -3
  20. package/dist/esm/client/http-client.js +128 -11
  21. package/dist/esm/client/index.js +6 -6
  22. package/dist/esm/config/client-config.js +59 -15
  23. package/dist/esm/config/index.js +4 -3
  24. package/dist/esm/config/token-manager.js +55 -5
  25. package/dist/esm/index.js +15 -1
  26. package/dist/esm/logger/index.js +1 -1
  27. package/dist/esm/model/contract-helpers.js +1 -1
  28. package/dist/esm/model/enums.js +87 -6
  29. package/dist/esm/model/order-helpers.js +1 -1
  30. package/dist/esm/model/order-status.js +43 -0
  31. package/dist/esm/model/quote-requests.js +10 -0
  32. package/dist/esm/model/trade-requests.js +12 -0
  33. package/dist/esm/push/index.js +5 -5
  34. package/dist/esm/push/pb/OrderStatusData.js +24 -0
  35. package/dist/esm/push/pb/PushData.js +11 -11
  36. package/dist/esm/push/pb/QuoteBasicData.js +1 -1
  37. package/dist/esm/push/pb/Response.js +1 -1
  38. package/dist/esm/push/pb/index.js +17 -17
  39. package/dist/esm/push/proto-message.js +9 -3
  40. package/dist/esm/push/push-client.js +118 -23
  41. package/dist/esm/push/push-message.js +4 -0
  42. package/dist/esm/quote/index.js +1 -1
  43. package/dist/esm/quote/quote-client.js +335 -11
  44. package/dist/esm/signer/index.js +2 -2
  45. package/dist/esm/trade/index.js +1 -1
  46. package/dist/esm/trade/trade-client.js +206 -27
  47. package/dist/types/client/case-convert.d.ts +3 -1
  48. package/dist/types/client/http-client.d.ts +32 -1
  49. package/dist/types/config/client-config.d.ts +16 -1
  50. package/dist/types/config/index.d.ts +2 -0
  51. package/dist/types/config/token-manager.d.ts +27 -3
  52. package/dist/types/index.d.ts +22 -1
  53. package/dist/types/model/enums.d.ts +60 -6
  54. package/dist/types/model/order-status.d.ts +14 -0
  55. package/dist/types/model/quote-requests.d.ts +344 -0
  56. package/dist/types/model/quote.d.ts +261 -0
  57. package/dist/types/model/trade-requests.d.ts +260 -0
  58. package/dist/types/model/trade.d.ts +214 -1
  59. package/dist/types/push/pb/OrderStatusData.d.ts +4 -0
  60. package/dist/types/push/push-client.d.ts +36 -3
  61. package/dist/types/push/push-message.d.ts +4 -0
  62. package/dist/types/quote/quote-client.d.ts +121 -6
  63. package/dist/types/trade/trade-client.d.ts +95 -12
  64. package/package.json +2 -2
@@ -45,12 +45,31 @@ class QuoteClient {
45
45
  const response = await this.httpClient.executeRequest(request);
46
46
  return (0, api_response_1.unmarshalData)(response.data);
47
47
  }
48
+ /**
49
+ * Some endpoints return either a list or a single object depending on the
50
+ * server response shape (future_contract_by_contract_code /
51
+ * future_continuous_contracts). Unify into an array.
52
+ */
53
+ async callIntoListOrObject(method, bizParams, version) {
54
+ const raw = await this.callInto(method, bizParams, version);
55
+ if (raw == null)
56
+ return [];
57
+ if (Array.isArray(raw))
58
+ return raw;
59
+ return [raw];
60
+ }
61
+ /** Unwrap server {"items":[...]} envelope. */
62
+ async callIntoItems(method, bizParams, version) {
63
+ const wrap = await this.callInto(method, bizParams, version);
64
+ return wrap?.items ?? [];
65
+ }
48
66
  // === Basic market data ===
49
67
  async getMarketState(market) {
50
68
  return this.callInto('market_state', { market });
51
69
  }
52
- async getBrief(symbols) {
53
- return this.callInto('quote_real_time', { symbols });
70
+ /** Real-time stock briefs. wire: quote_real_time */
71
+ async getBrief(req) {
72
+ return this.callInto('quote_real_time', req);
54
73
  }
55
74
  async getKline(symbol, period) {
56
75
  return this.callInto('kline', { symbols: [symbol], period });
@@ -58,12 +77,13 @@ class QuoteClient {
58
77
  async getTimeline(symbols) {
59
78
  return this.callInto('timeline', { symbols });
60
79
  }
61
- async getTradeTick(symbols) {
62
- return this.callInto('trade_tick', { symbols });
80
+ /** Tick-by-tick trades. wire: trade_tick */
81
+ async getTradeTick(req) {
82
+ return this.callInto('trade_tick', req);
63
83
  }
64
- /** Depth snapshot. market is required (US / HK supported). */
65
- async getQuoteDepth(symbol, market) {
66
- return this.callInto('quote_depth', { symbols: [symbol], market });
84
+ /** Depth snapshot. wire: quote_depth */
85
+ async getQuoteDepth(req) {
86
+ return this.callInto('quote_depth', req);
67
87
  }
68
88
  // === Options ===
69
89
  async getOptionExpiration(symbol) {
@@ -98,8 +118,9 @@ class QuoteClient {
98
118
  async getFutureContracts(exchange) {
99
119
  return this.callInto('future_contract_by_exchange_code', { exchangeCode: exchange });
100
120
  }
101
- async getFutureRealTimeQuote(contractCodes) {
102
- return this.callInto('future_real_time_quote', { contractCodes });
121
+ /** Real-time futures quotes. wire: future_real_time_quote */
122
+ async getFutureRealTimeQuote(req) {
123
+ return this.callInto('future_real_time_quote', req);
103
124
  }
104
125
  /** Futures K-line; use -1 for unbounded beginTime / endTime. */
105
126
  async getFutureKline(req) {
@@ -140,5 +161,308 @@ class QuoteClient {
140
161
  async grabQuotePermission() {
141
162
  return this.callInto('grab_quote_permission');
142
163
  }
164
+ // ==========================================================================
165
+ // Batch 3: stock basics + time series
166
+ // ==========================================================================
167
+ /** All symbol codes (string list). wire: all_symbols */
168
+ async getSymbols(req) {
169
+ return this.callInto('all_symbols', req);
170
+ }
171
+ /** All symbols with names. wire: all_symbol_names */
172
+ async getSymbolNames(req) {
173
+ return this.callInto('all_symbol_names', req);
174
+ }
175
+ /** Trade meta (lot size / tick size). wire: quote_stock_trade */
176
+ async getTradeMetas(req) {
177
+ return this.callInto('quote_stock_trade', req);
178
+ }
179
+ /** Stock details. wire: stock_detail. Server returns {items:[...]}. */
180
+ async getStockDetails(req) {
181
+ return this.callIntoItems('stock_detail', req);
182
+ }
183
+ /** Delayed stock briefs. wire: quote_delay */
184
+ async getStockDelayBriefs(req) {
185
+ return this.callInto('quote_delay', req);
186
+ }
187
+ /** K-line bars (full request). wire: kline */
188
+ async getBars(req) {
189
+ return this.callInto('kline', req);
190
+ }
191
+ /**
192
+ * Client-side paginated K-line fetch — loops over getBars until TotalSize
193
+ * bars are collected. Items are returned oldest-first, matching
194
+ * Python/Go get_bars_by_page behaviour.
195
+ */
196
+ async getBarsByPage(req) {
197
+ const pageSize = req.pageSize && req.pageSize > 0 ? req.pageSize : 200;
198
+ const totalSize = req.totalSize && req.totalSize > 0 ? req.totalSize : 1000;
199
+ let beginTime = req.beginTime ?? -1;
200
+ let endTime = req.endTime ?? -1;
201
+ if (beginTime === 0)
202
+ beginTime = -1;
203
+ if (endTime === 0)
204
+ endTime = -1;
205
+ const acc = [];
206
+ while (acc.length < totalSize) {
207
+ const sub = {
208
+ symbols: req.symbol ? [req.symbol] : undefined,
209
+ period: req.period,
210
+ right: req.right,
211
+ beginTime,
212
+ endTime,
213
+ limit: pageSize,
214
+ tradeSession: req.tradeSession,
215
+ lang: req.lang,
216
+ };
217
+ const pageOut = await this.callInto('kline', sub);
218
+ if (!pageOut || pageOut.length === 0 || !pageOut[0].items || pageOut[0].items.length === 0) {
219
+ break;
220
+ }
221
+ const items = pageOut[0].items;
222
+ acc.push(...items);
223
+ if (items.length < pageSize)
224
+ break;
225
+ // next page endTime = oldest bar time - 1
226
+ let oldest = items[0].time;
227
+ for (const it of items) {
228
+ if (it.time < oldest)
229
+ oldest = it.time;
230
+ }
231
+ endTime = oldest - 1;
232
+ }
233
+ return acc;
234
+ }
235
+ /** Historical intraday timeline. wire: history_timeline */
236
+ async getTimelineHistory(req) {
237
+ return this.callInto('history_timeline', req);
238
+ }
239
+ /** Trade rank (top movers / volume). wire: trade_rank */
240
+ async getTradeRank(req) {
241
+ return this.callInto('trade_rank', req);
242
+ }
243
+ /** Short interest / shortable. wire: quote_shortable_stocks */
244
+ async getShortInterest(req) {
245
+ return this.callInto('quote_shortable_stocks', req);
246
+ }
247
+ /** Broker queue. wire: stock_broker */
248
+ async getStockBroker(req) {
249
+ return this.callInto('stock_broker', req);
250
+ }
251
+ /** Stock fundamentals (server returns grouped map). wire: stock_fundamental */
252
+ async getStockFundamental(req) {
253
+ const out = await this.callInto('stock_fundamental', req);
254
+ return out ?? {};
255
+ }
256
+ /** Stock industry classification (array). wire: stock_industry */
257
+ async getStockIndustry(req) {
258
+ return this.callInto('stock_industry', req);
259
+ }
260
+ /** Quote permission detail. wire: get_quote_permission */
261
+ async getQuotePermission(req) {
262
+ return this.callInto('get_quote_permission', req);
263
+ }
264
+ /** K-line quota usage. wire: kline_quota */
265
+ async getKlineQuota(req) {
266
+ return this.callInto('kline_quota', req);
267
+ }
268
+ // ==========================================================================
269
+ // Batch 4: option / future extensions
270
+ // ==========================================================================
271
+ /** Option K-line. wire: option_kline (v2.0) */
272
+ async getOptionBars(req) {
273
+ return this.callInto('option_kline', req, '2.0');
274
+ }
275
+ /** Option tick trades. wire: option_trade_tick */
276
+ async getOptionTradeTicks(req) {
277
+ return this.callInto('option_trade_tick', req);
278
+ }
279
+ /** Option intraday timeline. wire: option_timeline */
280
+ async getOptionTimeline(req) {
281
+ return this.callInto('option_timeline', req);
282
+ }
283
+ /** Option depth. wire: option_depth */
284
+ async getOptionDepth(req) {
285
+ return this.callInto('option_depth', req);
286
+ }
287
+ /** Available option symbols. wire: option_symbol */
288
+ async getOptionSymbols(req) {
289
+ return this.callInto('option_symbol', req);
290
+ }
291
+ /** Option analysis (volatility). wire: option_analysis */
292
+ async getOptionAnalysis(req) {
293
+ return this.callInto('option_analysis', req);
294
+ }
295
+ /** Single futures contract by code. wire: future_contract_by_contract_code */
296
+ async getFutureContract(req) {
297
+ return this.callIntoListOrObject('future_contract_by_contract_code', req);
298
+ }
299
+ /** All futures contracts. wire: future_contracts */
300
+ async getAllFutureContracts(req) {
301
+ return this.callInto('future_contracts', req);
302
+ }
303
+ /** Current main contract. wire: future_current_contract */
304
+ async getCurrentFutureContract(req) {
305
+ return this.callInto('future_current_contract', req);
306
+ }
307
+ /** Continuous main contracts. wire: future_continuous_contracts */
308
+ async getFutureContinuousContracts(req) {
309
+ return this.callIntoListOrObject('future_continuous_contracts', req);
310
+ }
311
+ /** Historical main contracts. wire: future_main_contract */
312
+ async getFutureHistoryMainContract(req) {
313
+ return this.callInto('future_main_contract', req);
314
+ }
315
+ /**
316
+ * Futures K-line (full request with index pagination). wire: future_kline
317
+ * begin_time / end_time default to -1 when unset (server requires them present).
318
+ */
319
+ async getFutureBars(req) {
320
+ const body = {
321
+ ...req,
322
+ beginTime: req.beginTime ?? -1,
323
+ endTime: req.endTime ?? -1,
324
+ };
325
+ return this.callInto('future_kline', body);
326
+ }
327
+ /** Client-side paginated futures K-line fetch. */
328
+ async getFutureBarsByPage(req) {
329
+ const pageSize = req.pageSize && req.pageSize > 0 ? req.pageSize : 200;
330
+ const totalSize = req.totalSize && req.totalSize > 0 ? req.totalSize : 1000;
331
+ let beginTime = req.beginTime ?? -1;
332
+ let endTime = req.endTime ?? -1;
333
+ if (beginTime === 0)
334
+ beginTime = -1;
335
+ if (endTime === 0)
336
+ endTime = -1;
337
+ const acc = [];
338
+ while (acc.length < totalSize) {
339
+ const sub = {
340
+ contractCode: req.contractCode,
341
+ period: req.period,
342
+ beginTime,
343
+ endTime,
344
+ limit: pageSize,
345
+ lang: req.lang,
346
+ };
347
+ const pageOut = await this.callInto('future_kline', sub);
348
+ if (!pageOut || pageOut.length === 0 || !pageOut[0].items || pageOut[0].items.length === 0) {
349
+ break;
350
+ }
351
+ const items = pageOut[0].items;
352
+ acc.push(...items);
353
+ if (items.length < pageSize)
354
+ break;
355
+ let oldest = items[0].time;
356
+ for (const it of items) {
357
+ if (it.time < oldest)
358
+ oldest = it.time;
359
+ }
360
+ endTime = oldest - 1;
361
+ }
362
+ return acc;
363
+ }
364
+ /** Futures tick trades. wire: future_tick (API v3.0)
365
+ * Server returns {contractCode, items:[...]}; unwrap items and backfill contractCode.
366
+ * endIndex defaults to 30 when not set (consistent with Python/Go SDK). */
367
+ async getFutureTradeTicks(req) {
368
+ if (!req.endIndex)
369
+ req.endIndex = 30;
370
+ const wrap = await this.callInto('future_tick', req, '3.0');
371
+ const items = wrap?.items ?? [];
372
+ if (wrap?.contractCode) {
373
+ for (const item of items) {
374
+ if (!item.contractCode)
375
+ item.contractCode = wrap.contractCode;
376
+ }
377
+ }
378
+ return items;
379
+ }
380
+ /** Futures depth. wire: future_depth */
381
+ async getFutureDepth(req) {
382
+ return this.callInto('future_depth', req);
383
+ }
384
+ /** Futures trading times (single-object response). wire: future_trading_date */
385
+ async getFutureTradingTimes(req) {
386
+ return this.callInto('future_trading_date', req);
387
+ }
388
+ // ==========================================================================
389
+ // Batch 5: fund / warrant / industry / corporate / financial / calendar
390
+ // ==========================================================================
391
+ /** Fund symbols. wire: fund_all_symbols */
392
+ async getFundSymbols(req) {
393
+ return this.callInto('fund_all_symbols', req);
394
+ }
395
+ /** Fund contracts. wire: fund_contracts */
396
+ async getFundContracts(req) {
397
+ return this.callInto('fund_contracts', req);
398
+ }
399
+ /** Fund real-time NAV. wire: fund_quote */
400
+ async getFundQuote(req) {
401
+ return this.callInto('fund_quote', req);
402
+ }
403
+ /** Fund historical NAV. wire: fund_history_quote */
404
+ async getFundHistoryQuote(req) {
405
+ return this.callInto('fund_history_quote', req);
406
+ }
407
+ /** Warrant briefs. wire: warrant_briefs */
408
+ async getWarrantBriefs(req) {
409
+ return this.callInto('warrant_briefs', req);
410
+ }
411
+ /** Warrant filter (paged). wire: warrant_filter */
412
+ async getWarrantFilter(req) {
413
+ return this.callInto('warrant_filter', req);
414
+ }
415
+ /** Industry list. wire: industry_list */
416
+ async getIndustryList(req) {
417
+ return this.callInto('industry_list', req);
418
+ }
419
+ /** Stocks inside an industry. wire: industry_stock_list */
420
+ async getIndustryStocks(req) {
421
+ return this.callInto('industry_stock_list', req);
422
+ }
423
+ /** Corporate split (flattened). wire: corporate_action (action_type=split) */
424
+ async getCorporateSplit(req) {
425
+ const body = { ...req, actionType: 'split' };
426
+ const grouped = await this.callInto('corporate_action', body);
427
+ if (!grouped || typeof grouped !== 'object')
428
+ return [];
429
+ return Object.values(grouped).flat();
430
+ }
431
+ /** Corporate dividend (flattened). wire: corporate_action (action_type=dividend) */
432
+ async getCorporateDividend(req) {
433
+ const body = { ...req, actionType: 'dividend' };
434
+ const grouped = await this.callInto('corporate_action', body);
435
+ if (!grouped || typeof grouped !== 'object')
436
+ return [];
437
+ return Object.values(grouped).flat();
438
+ }
439
+ /** Corporate earnings calendar (flattened). wire: corporate_action (action_type=earning) */
440
+ async getCorporateEarningsCalendar(req) {
441
+ const body = { ...req, actionType: 'earning' };
442
+ const grouped = await this.callInto('corporate_action', body);
443
+ if (!grouped || typeof grouped !== 'object')
444
+ return [];
445
+ return Object.values(grouped).flat();
446
+ }
447
+ /** Financial currency per symbol. wire: financial_currency */
448
+ async getFinancialCurrency(req) {
449
+ return this.callInto('financial_currency', req);
450
+ }
451
+ /** FX rates. wire: financial_exchange_rate */
452
+ async getFinancialExchangeRate(req) {
453
+ return this.callInto('financial_exchange_rate', req);
454
+ }
455
+ /** Trading calendar. wire: trading_calendar */
456
+ async getTradingCalendar(req) {
457
+ return this.callInto('trading_calendar', req);
458
+ }
459
+ /** Available market-scanner tags. wire: market_scanner_tags */
460
+ async getMarketScannerTags(req) {
461
+ return this.callInto('market_scanner_tags', req);
462
+ }
463
+ /** Overnight quote. wire: quote_overnight */
464
+ async getQuoteOvernight(req) {
465
+ return this.callInto('quote_overnight', req);
466
+ }
143
467
  }
144
468
  exports.QuoteClient = QuoteClient;
@@ -3,6 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TradeClient = void 0;
4
4
  const api_request_1 = require("../client/api-request");
5
5
  const api_response_1 = require("../client/api-response");
6
+ const order_status_1 = require("../model/order-status");
7
+ /** Normalize `Order.status` field in-place (handles int → string). */
8
+ function normalizeOrderInPlace(o) {
9
+ if (o && o.status !== undefined && o.status !== null) {
10
+ o.status = (0, order_status_1.normalizeOrderStatus)(o.status);
11
+ }
12
+ }
13
+ /** Normalize every Order in a list in-place. */
14
+ function normalizeOrders(orders) {
15
+ for (const o of orders)
16
+ normalizeOrderInPlace(o);
17
+ return orders;
18
+ }
19
+ /** Shallow-copy a request object and fill account if empty. */
20
+ function withAccount(req, account) {
21
+ const r = { ...(req ?? {}) };
22
+ if (!r.account)
23
+ r.account = account;
24
+ return r;
25
+ }
6
26
  /** Trading client wrapping all trade-related APIs. */
7
27
  class TradeClient {
8
28
  constructor(httpClient, account) {
@@ -54,40 +74,199 @@ class TradeClient {
54
74
  async cancelOrder(id) {
55
75
  return this.callInto('cancel_order', { account: this.account, id });
56
76
  }
57
- // === Order queries ===
58
- async getOrders() {
59
- return this.callIntoItems('orders', { account: this.account });
77
+ // === Order queries (BREAKING v0.4.0) ===
78
+ /**
79
+ * Query all historical orders. v0.4.0: optional OrdersRequest for filtering.
80
+ *
81
+ * @example
82
+ * // Default (unfiltered)
83
+ * await tc.getOrders();
84
+ * // Filter by status
85
+ * await tc.getOrders({ states: ['Filled'], limit: 100 });
86
+ */
87
+ async getOrders(req) {
88
+ return normalizeOrders(await this.callIntoItems('orders', withAccount(req, this.account)));
60
89
  }
61
- async getActiveOrders() {
62
- return this.callIntoItems('active_orders', { account: this.account });
90
+ /** Query active (open) orders. v0.4.0: supports ParentId to filter attached legs. */
91
+ async getActiveOrders(req) {
92
+ return normalizeOrders(await this.callIntoItems('active_orders', withAccount(req, this.account)));
63
93
  }
64
- async getInactiveOrders() {
65
- return this.callIntoItems('inactive_orders', { account: this.account });
94
+ /** Query inactive/cancelled orders. */
95
+ async getInactiveOrders(req) {
96
+ return normalizeOrders(await this.callIntoItems('inactive_orders', withAccount(req, this.account)));
66
97
  }
67
98
  /**
68
- * Filled orders in a time range (ms timestamps required by the server).
69
- * Use `Date.now() - 30*24*3600*1000` for "last 30 days".
99
+ * Query filled orders. v0.4.0 BREAKING: signature changed from positional
100
+ * (startDateMs, endDateMs) to OrdersRequest object.
101
+ *
102
+ * @example
103
+ * // Last 30 days
104
+ * const now = Date.now();
105
+ * await tc.getFilledOrders({ startDate: now - 30*24*3600*1000, endDate: now });
70
106
  */
71
- async getFilledOrders(startDateMs, endDateMs) {
72
- return this.callIntoItems('filled_orders', {
73
- account: this.account, startDate: startDateMs, endDate: endDateMs,
74
- });
107
+ async getFilledOrders(req) {
108
+ return normalizeOrders(await this.callIntoItems('filled_orders', withAccount(req, this.account)));
75
109
  }
76
- /** Transactions for a specific order; `symbol` and `secType` are required. */
77
- async getOrderTransactions(id, symbol, secType) {
78
- return this.callIntoItems('order_transactions', {
79
- account: this.account, orderId: id, symbol, secType,
80
- });
110
+ /**
111
+ * Get a single order by ID (v0.4.0 new).
112
+ * Wire method: `orders` with id/order_id param; server returns a single Order object
113
+ * (not `{items:[]}` envelope).
114
+ *
115
+ * Returns `undefined` if no match.
116
+ */
117
+ async getOrder(req) {
118
+ const merged = withAccount(req, this.account);
119
+ const out = await this.callInto('orders', merged);
120
+ if (!out || (!out.id && !out.orderId))
121
+ return undefined;
122
+ normalizeOrderInPlace(out);
123
+ return out;
124
+ }
125
+ /**
126
+ * Query order transaction details. v0.4.0 BREAKING: all fields now optional
127
+ * (previously symbol/secType required).
128
+ */
129
+ async getOrderTransactions(req) {
130
+ return this.callIntoItems('order_transactions', withAccount(req, this.account));
131
+ }
132
+ // === Positions & assets (BREAKING v0.4.0) ===
133
+ /** Query positions. v0.4.0: supports Symbol / SecType / Currency / Market filters. */
134
+ async getPositions(req) {
135
+ return this.callIntoItems('positions', withAccount(req, this.account));
136
+ }
137
+ /** Query assets. v0.4.0: supports sub-account list and aggregation options. */
138
+ async getAssets(req) {
139
+ return this.callIntoItems('assets', withAccount(req, this.account));
140
+ }
141
+ /** Query prime assets. v0.4.0: takes AssetsRequest for filters. */
142
+ async getPrimeAssets(req) {
143
+ return this.callInto('prime_assets', withAccount(req, this.account));
144
+ }
145
+ // ===== v0.4.0 NEW: Account management =====
146
+ /** Query managed sub-accounts (institutional). */
147
+ async getManagedAccounts(req) {
148
+ return this.callIntoItems('accounts', withAccount(req, this.account));
149
+ }
150
+ /**
151
+ * Query derivative contracts list. Same wire as getQuoteContract but accepts
152
+ * full request struct. Python: `get_derivative_contracts`.
153
+ */
154
+ async getDerivativeContracts(req) {
155
+ return this.callIntoItems('quote_contract', withAccount(req, this.account));
156
+ }
157
+ // ===== v0.4.0 NEW: Asset analytics =====
158
+ /** Daily P&L / net-value / holding-value analytics. */
159
+ async getAnalyticsAsset(req) {
160
+ return this.callIntoItems('analytics_asset', withAccount(req, this.account));
161
+ }
162
+ /** Aggregate assets (prime) by base currency. */
163
+ async getAggregateAssets(req) {
164
+ return this.callInto('aggregate_assets', withAccount(req, this.account));
165
+ }
166
+ /** Estimate tradable quantity given order parameters. */
167
+ async getEstimateTradableQuantity(req) {
168
+ return this.callInto('estimate_tradable_quantity', withAccount(req, this.account));
169
+ }
170
+ // ===== v0.4.0 NEW: Forex =====
171
+ /** Place forex order (sub-account base-currency swap). */
172
+ async placeForexOrder(req) {
173
+ return this.callInto('place_forex_order', withAccount(req, this.account));
174
+ }
175
+ // ===== v0.4.0 NEW: Segment fund transfer =====
176
+ /** Query amount available for inter-segment transfer. Returns array. */
177
+ async getSegmentFundAvailable(req) {
178
+ return this.callInto('segment_fund_available', withAccount(req, this.account));
179
+ }
180
+ /** Query segment fund transfer history. Server returns plain array (no items wrapper). */
181
+ async getSegmentFundHistory(req) {
182
+ return this.callInto('segment_fund_history', withAccount(req, this.account));
183
+ }
184
+ /** Execute an inter-segment fund transfer. */
185
+ async transferSegmentFund(req) {
186
+ return this.callInto('transfer_segment_fund', withAccount(req, this.account));
187
+ }
188
+ /** Cancel a pending inter-segment transfer. */
189
+ async cancelSegmentFund(req) {
190
+ return this.callInto('cancel_segment_fund', withAccount(req, this.account));
191
+ }
192
+ // ===== v0.4.0 NEW: Fund details / history =====
193
+ /** Fund-movement details (by segment / currency / fund-type). */
194
+ async getFundDetails(req) {
195
+ return this.callIntoItems('fund_details', withAccount(req, this.account));
196
+ }
197
+ /** Funding history (wire method: `transfer_fund`).
198
+ * Server returns bare list (no items wrapper). */
199
+ async getFundingHistory(req) {
200
+ return this.callInto('transfer_fund', withAccount(req, this.account));
201
+ }
202
+ // ===== v0.4.0 NEW: Internal / external position transfer =====
203
+ /**
204
+ * Internal position transfer (cross sub-account).
205
+ * @note Uses `fromAccount` / `toAccount` (not `account`).
206
+ */
207
+ async transferPosition(req) {
208
+ const merged = { ...req };
209
+ if (!merged.fromAccount)
210
+ merged.fromAccount = this.account;
211
+ return this.callInto('position_transfer', merged);
212
+ }
213
+ /** Query internal position transfer records. Uses `accountId` (wire: `account_id`). */
214
+ async getPositionTransferRecords(req) {
215
+ const merged = { ...req };
216
+ if (!merged.accountId)
217
+ merged.accountId = this.account;
218
+ return this.callIntoItems('position_transfer_records', merged);
219
+ }
220
+ /** Query single internal position transfer detail by ID. */
221
+ async getPositionTransferDetail(req) {
222
+ const merged = { ...req };
223
+ if (!merged.accountId)
224
+ merged.accountId = this.account;
225
+ return this.callInto('position_transfer_detail', merged);
226
+ }
227
+ /** Query external position transfer records (DWAC / FOP / etc.). */
228
+ async getPositionTransferExternalRecords(req) {
229
+ const merged = { ...req };
230
+ if (!merged.accountId)
231
+ merged.accountId = this.account;
232
+ return this.callIntoItems('position_transfer_external_records', merged);
233
+ }
234
+ /** 行权检验:预估行权/作废后正股持仓变化 (wire: option_exercise_check) */
235
+ async checkOptionExercise(req) {
236
+ const merged = { ...req };
237
+ if (!merged.account)
238
+ merged.account = this.account;
239
+ return this.callInto('option_exercise_check', merged);
240
+ }
241
+ /** 查询可行权持仓列表 (wire: option_exercise_position) */
242
+ async getOptionExercisePositions(req) {
243
+ const merged = { ...req };
244
+ if (!merged.account)
245
+ merged.account = this.account;
246
+ return this.callInto('option_exercise_position', merged);
81
247
  }
82
- // === Positions & assets ===
83
- async getPositions() {
84
- return this.callIntoItems('positions', { account: this.account });
248
+ /** 提交行权/作废申请 (wire: option_exercise_submit) */
249
+ async submitOptionExercise(req) {
250
+ const merged = { ...req };
251
+ if (!merged.account)
252
+ merged.account = this.account;
253
+ const result = await this.callInto('option_exercise_submit', merged);
254
+ return result ?? false;
85
255
  }
86
- async getAssets() {
87
- return this.callIntoItems('assets', { account: this.account });
256
+ /** 分页查询行权记录 (wire: option_exercise_record) */
257
+ async getOptionExerciseRecords(req) {
258
+ const merged = { ...req };
259
+ if (!merged.account)
260
+ merged.account = this.account;
261
+ return this.callInto('option_exercise_record', merged);
88
262
  }
89
- async getPrimeAssets() {
90
- return this.callInto('prime_assets', { account: this.account });
263
+ /** 撤销行权申请 (wire: option_exercise_cancel) */
264
+ async cancelOptionExercise(req) {
265
+ const merged = { ...req };
266
+ if (!merged.account)
267
+ merged.account = this.account;
268
+ const result = await this.callInto('option_exercise_cancel', merged);
269
+ return result ?? false;
91
270
  }
92
271
  }
93
272
  exports.TradeClient = TradeClient;
@@ -4,7 +4,7 @@
4
4
  * Requests use camelCase parameters in TypeScript (idiomatic), but the
5
5
  * server wire format is snake_case, so we convert keys before serializing.
6
6
  */
7
- import { keysToSnakeCase } from './case-convert';
7
+ import { keysToSnakeCase } from './case-convert.js';
8
8
  /**
9
9
  * Create an API request, serializing business parameters to JSON as bizContent.
10
10
  * camelCase keys are automatically converted to snake_case to match the server.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * API response parsing.
3
3
  */
4
- import { TigerError } from './errors';
4
+ import { TigerError } from './errors.js';
5
5
  /**
6
6
  * Parse the raw HTTP response body.
7
7
  * Throws TigerError when code != 0.
@@ -13,7 +13,9 @@ export function camelToSnake(key) {
13
13
  /**
14
14
  * Recursively convert all object keys from camelCase to snake_case.
15
15
  * Preserves arrays, primitives, and Date/null/undefined.
16
- * Keys already containing an underscore are kept as-is (already snake_case).
16
+ *
17
+ * Note: camelToSnake is idempotent for pure snake_case keys (no uppercase
18
+ * letters → no replacements), so no pre-check is needed.
17
19
  */
18
20
  export function keysToSnakeCase(value) {
19
21
  if (Array.isArray(value)) {
@@ -22,8 +24,7 @@ export function keysToSnakeCase(value) {
22
24
  if (value && typeof value === 'object' && value.constructor === Object) {
23
25
  const out = {};
24
26
  for (const [k, v] of Object.entries(value)) {
25
- const newKey = k.includes('_') ? k : camelToSnake(k);
26
- out[newKey] = keysToSnakeCase(v);
27
+ out[camelToSnake(k)] = keysToSnakeCase(v);
27
28
  }
28
29
  return out;
29
30
  }