@tigeropenapi/tigeropen 0.1.0 → 0.4.0

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 (141) hide show
  1. package/README.md +475 -104
  2. package/dist/cjs/client/api-request.js +16 -10
  3. package/dist/cjs/client/api-response.js +23 -3
  4. package/dist/cjs/client/case-convert.js +35 -0
  5. package/dist/cjs/client/http-client.js +47 -21
  6. package/dist/cjs/client/index.js +6 -2
  7. package/dist/cjs/config/client-config.js +130 -49
  8. package/dist/cjs/config/domain.js +21 -2
  9. package/dist/cjs/config/index.js +3 -1
  10. package/dist/cjs/index.js +1 -1
  11. package/dist/cjs/model/contract.js +1 -2
  12. package/dist/cjs/model/enums.js +89 -7
  13. package/dist/cjs/model/order-helpers.js +9 -9
  14. package/dist/cjs/model/order-status.js +46 -0
  15. package/dist/cjs/model/order.js +5 -2
  16. package/dist/cjs/model/position.js +1 -2
  17. package/dist/cjs/model/quote-requests.js +11 -0
  18. package/dist/cjs/model/quote.js +7 -0
  19. package/dist/cjs/model/trade-requests.js +13 -0
  20. package/dist/cjs/model/trade.js +2 -0
  21. package/dist/cjs/push/index.js +6 -3
  22. package/dist/cjs/push/pb/AssetData.js +208 -0
  23. package/dist/cjs/push/pb/KlineData.js +184 -0
  24. package/dist/cjs/push/pb/OptionTopData.js +427 -0
  25. package/dist/cjs/push/pb/OrderStatusData.js +592 -0
  26. package/dist/cjs/push/pb/OrderTransactionData.js +256 -0
  27. package/dist/cjs/push/pb/PositionData.js +316 -0
  28. package/dist/cjs/push/pb/PushData.js +219 -0
  29. package/dist/cjs/push/pb/QuoteBBOData.js +160 -0
  30. package/dist/cjs/push/pb/QuoteBasicData.js +329 -0
  31. package/dist/cjs/push/pb/QuoteData.js +507 -0
  32. package/dist/cjs/push/pb/QuoteDepthData.js +220 -0
  33. package/dist/cjs/push/pb/Request.js +278 -0
  34. package/dist/cjs/push/pb/Response.js +97 -0
  35. package/dist/cjs/push/pb/SocketCommon.js +86 -0
  36. package/dist/cjs/push/pb/StockTopData.js +199 -0
  37. package/dist/cjs/push/pb/TickData.js +202 -0
  38. package/dist/cjs/push/pb/TradeTickData.js +308 -0
  39. package/dist/cjs/push/pb/index.js +49 -0
  40. package/dist/cjs/push/proto-message.js +135 -0
  41. package/dist/cjs/push/push-client.js +361 -163
  42. package/dist/cjs/push/push-message.js +8 -60
  43. package/dist/cjs/push/varint.js +76 -0
  44. package/dist/cjs/quote/index.js +3 -2
  45. package/dist/cjs/quote/quote-client.js +440 -94
  46. package/dist/cjs/signer/signer.js +10 -10
  47. package/dist/cjs/trade/trade-client.js +197 -62
  48. package/dist/esm/client/api-request.js +14 -8
  49. package/dist/esm/client/api-response.js +22 -3
  50. package/dist/esm/client/case-convert.js +31 -0
  51. package/dist/esm/client/http-client.js +48 -22
  52. package/dist/esm/client/index.js +3 -2
  53. package/dist/esm/config/client-config.js +131 -51
  54. package/dist/esm/config/domain.js +20 -2
  55. package/dist/esm/config/index.js +2 -2
  56. package/dist/esm/index.js +1 -1
  57. package/dist/esm/model/contract.js +1 -2
  58. package/dist/esm/model/enums.js +87 -6
  59. package/dist/esm/model/order-helpers.js +9 -9
  60. package/dist/esm/model/order-status.js +43 -0
  61. package/dist/esm/model/order.js +5 -2
  62. package/dist/esm/model/position.js +1 -2
  63. package/dist/esm/model/quote-requests.js +10 -0
  64. package/dist/esm/model/quote.js +6 -0
  65. package/dist/esm/model/trade-requests.js +12 -0
  66. package/dist/esm/model/trade.js +1 -0
  67. package/dist/esm/push/index.js +4 -2
  68. package/dist/esm/push/pb/AssetData.js +205 -0
  69. package/dist/esm/push/pb/KlineData.js +181 -0
  70. package/dist/esm/push/pb/OptionTopData.js +424 -0
  71. package/dist/esm/push/pb/OrderStatusData.js +589 -0
  72. package/dist/esm/push/pb/OrderTransactionData.js +253 -0
  73. package/dist/esm/push/pb/PositionData.js +313 -0
  74. package/dist/esm/push/pb/PushData.js +216 -0
  75. package/dist/esm/push/pb/QuoteBBOData.js +157 -0
  76. package/dist/esm/push/pb/QuoteBasicData.js +326 -0
  77. package/dist/esm/push/pb/QuoteData.js +504 -0
  78. package/dist/esm/push/pb/QuoteDepthData.js +217 -0
  79. package/dist/esm/push/pb/Request.js +275 -0
  80. package/dist/esm/push/pb/Response.js +94 -0
  81. package/dist/esm/push/pb/SocketCommon.js +83 -0
  82. package/dist/esm/push/pb/StockTopData.js +196 -0
  83. package/dist/esm/push/pb/TickData.js +199 -0
  84. package/dist/esm/push/pb/TradeTickData.js +305 -0
  85. package/dist/esm/push/pb/index.js +25 -0
  86. package/dist/esm/push/proto-message.js +127 -0
  87. package/dist/esm/push/push-client.js +329 -164
  88. package/dist/esm/push/push-message.js +7 -56
  89. package/dist/esm/push/varint.js +72 -0
  90. package/dist/esm/quote/index.js +2 -2
  91. package/dist/esm/quote/quote-client.js +439 -94
  92. package/dist/esm/signer/signer.js +10 -10
  93. package/dist/esm/trade/trade-client.js +197 -62
  94. package/dist/types/client/api-request.d.ts +12 -11
  95. package/dist/types/client/api-response.d.ts +15 -7
  96. package/dist/types/client/case-convert.d.ts +16 -0
  97. package/dist/types/client/http-client.d.ts +26 -14
  98. package/dist/types/client/index.d.ts +3 -2
  99. package/dist/types/config/client-config.d.ts +29 -13
  100. package/dist/types/config/domain.d.ts +9 -0
  101. package/dist/types/config/index.d.ts +2 -2
  102. package/dist/types/index.d.ts +1 -1
  103. package/dist/types/model/contract.d.ts +13 -29
  104. package/dist/types/model/enums.d.ts +60 -6
  105. package/dist/types/model/order-helpers.d.ts +21 -20
  106. package/dist/types/model/order-status.d.ts +14 -0
  107. package/dist/types/model/order.d.ts +101 -55
  108. package/dist/types/model/position.d.ts +26 -24
  109. package/dist/types/model/quote-requests.d.ts +344 -0
  110. package/dist/types/model/quote.d.ts +572 -0
  111. package/dist/types/model/trade-requests.d.ts +195 -0
  112. package/dist/types/model/trade.d.ts +235 -0
  113. package/dist/types/push/callbacks.d.ts +32 -51
  114. package/dist/types/push/index.d.ts +5 -3
  115. package/dist/types/push/pb/AssetData.d.ts +47 -0
  116. package/dist/types/push/pb/KlineData.d.ts +43 -0
  117. package/dist/types/push/pb/OptionTopData.d.ts +76 -0
  118. package/dist/types/push/pb/OrderStatusData.d.ts +100 -0
  119. package/dist/types/push/pb/OrderTransactionData.d.ts +51 -0
  120. package/dist/types/push/pb/PositionData.d.ts +63 -0
  121. package/dist/types/push/pb/PushData.d.ts +46 -0
  122. package/dist/types/push/pb/QuoteBBOData.d.ts +35 -0
  123. package/dist/types/push/pb/QuoteBasicData.d.ts +66 -0
  124. package/dist/types/push/pb/QuoteData.d.ts +94 -0
  125. package/dist/types/push/pb/QuoteDepthData.d.ts +37 -0
  126. package/dist/types/push/pb/Request.d.ts +44 -0
  127. package/dist/types/push/pb/Response.d.ts +30 -0
  128. package/dist/types/push/pb/SocketCommon.d.ts +64 -0
  129. package/dist/types/push/pb/StockTopData.d.ts +37 -0
  130. package/dist/types/push/pb/TickData.d.ts +44 -0
  131. package/dist/types/push/pb/TradeTickData.d.ts +53 -0
  132. package/dist/types/push/pb/index.d.ts +39 -0
  133. package/dist/types/push/proto-message.d.ts +33 -0
  134. package/dist/types/push/push-client.d.ts +100 -68
  135. package/dist/types/push/push-message.d.ts +7 -147
  136. package/dist/types/push/varint.d.ts +23 -0
  137. package/dist/types/quote/index.d.ts +2 -2
  138. package/dist/types/quote/quote-client.d.ts +154 -47
  139. package/dist/types/signer/signer.d.ts +6 -6
  140. package/dist/types/trade/trade-client.d.ts +106 -38
  141. package/package.json +11 -6
@@ -2,98 +2,233 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TradeClient = void 0;
4
4
  const api_request_1 = require("../client/api-request");
5
- /**
6
- * 交易客户端,封装所有交易相关 API。
7
- */
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
+ }
26
+ /** Trading client wrapping all trade-related APIs. */
8
27
  class TradeClient {
9
28
  constructor(httpClient, account) {
10
29
  this.httpClient = httpClient;
11
30
  this.account = account;
12
31
  }
13
- /**
14
- * 内部通用方法:构造请求、发送、返回 data 字段
15
- */
16
- async execute(method, bizParams) {
32
+ async callInto(method, bizParams) {
17
33
  const request = (0, api_request_1.createApiRequest)(method, bizParams);
18
34
  const response = await this.httpClient.executeRequest(request);
19
- return response.data;
20
- }
21
- // === 合约查询方法 ===
22
- /** 查询单个合约 */
23
- async contract(symbol, secType) {
24
- return this.execute('contract', {
35
+ return (0, api_response_1.unmarshalData)(response.data);
36
+ }
37
+ /** Strip `{items: [...]}` envelope used by most trade read endpoints. */
38
+ async callIntoItems(method, bizParams) {
39
+ const wrap = await this.callInto(method, bizParams);
40
+ if (Array.isArray(wrap))
41
+ return wrap;
42
+ return wrap?.items ?? [];
43
+ }
44
+ // === Contracts ===
45
+ async getContract(symbol, secType) {
46
+ return this.callIntoItems('contract', {
25
47
  account: this.account, symbol, secType,
26
48
  });
27
49
  }
28
- /** 批量查询合约 */
29
- async contracts(symbols, secType) {
30
- return this.execute('contracts', {
50
+ async getContracts(symbols, secType) {
51
+ return this.callIntoItems('contracts', {
31
52
  account: this.account, symbols, secType,
32
53
  });
33
54
  }
34
- /** 查询衍生品合约 */
35
- async quoteContract(symbol, secType) {
36
- return this.execute('quote_contract', {
37
- account: this.account, symbol, secType,
55
+ /**
56
+ * Derivative contracts (OPT / WAR / IOPT only).
57
+ * `symbol` is the underlying (e.g. "AAPL"); `expiry` is "YYYYMMDD".
58
+ */
59
+ async getQuoteContract(symbol, secType, expiry) {
60
+ return this.callIntoItems('quote_contract', {
61
+ account: this.account, symbols: [symbol], secType, expiry,
38
62
  });
39
63
  }
40
- // === 订单操作方法 ===
41
- /** 下单 */
64
+ // === Order operations ===
42
65
  async placeOrder(order) {
43
- const params = { ...order, account: this.account };
44
- return this.execute('place_order', params);
66
+ return this.callInto('place_order', { ...order, account: this.account });
45
67
  }
46
- /** 预览订单 */
47
68
  async previewOrder(order) {
48
- const params = { ...order, account: this.account };
49
- return this.execute('preview_order', params);
69
+ return this.callInto('preview_order', { ...order, account: this.account });
50
70
  }
51
- /** 修改订单 */
52
71
  async modifyOrder(id, order) {
53
- const params = { ...order, account: this.account, id };
54
- return this.execute('modify_order', params);
72
+ return this.callInto('modify_order', { ...order, account: this.account, id });
55
73
  }
56
- /** 取消订单 */
57
74
  async cancelOrder(id) {
58
- return this.execute('cancel_order', {
59
- account: this.account, id,
60
- });
75
+ return this.callInto('cancel_order', { account: this.account, id });
61
76
  }
62
- // === 订单查询方法 ===
63
- /** 查询全部订单 */
64
- async orders() {
65
- return this.execute('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)));
66
89
  }
67
- /** 查询待成交订单 */
68
- async activeOrders() {
69
- return this.execute('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)));
70
93
  }
71
- /** 查询已撤销订单 */
72
- async inactiveOrders() {
73
- return this.execute('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)));
74
97
  }
75
- /** 查询已成交订单 */
76
- async filledOrders() {
77
- return this.execute('filled_orders', { account: this.account });
98
+ /**
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 });
106
+ */
107
+ async getFilledOrders(req) {
108
+ return normalizeOrders(await this.callIntoItems('filled_orders', withAccount(req, this.account)));
78
109
  }
79
- // === 持仓和资产查询方法 ===
80
- /** 查询持仓 */
81
- async positions() {
82
- return this.execute('positions', { account: this.account });
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;
83
124
  }
84
- /** 查询资产 */
85
- async assets() {
86
- return this.execute('assets', { account: this.account });
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));
87
131
  }
88
- /** 查询综合账户资产 */
89
- async primeAssets() {
90
- return this.execute('prime_assets', { account: this.account });
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));
91
136
  }
92
- /** 查询订单成交明细 */
93
- async orderTransactions(id) {
94
- return this.execute('order_transactions', {
95
- account: this.account, id,
96
- });
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
+ async getFundingHistory(req) {
199
+ return this.callIntoItems('transfer_fund', withAccount(req, this.account));
200
+ }
201
+ // ===== v0.4.0 NEW: Internal / external position transfer =====
202
+ /**
203
+ * Internal position transfer (cross sub-account).
204
+ * @note Uses `fromAccount` / `toAccount` (not `account`).
205
+ */
206
+ async transferPosition(req) {
207
+ const merged = { ...req };
208
+ if (!merged.fromAccount)
209
+ merged.fromAccount = this.account;
210
+ return this.callInto('position_transfer', merged);
211
+ }
212
+ /** Query internal position transfer records. Uses `accountId` (wire: `account_id`). */
213
+ async getPositionTransferRecords(req) {
214
+ const merged = { ...req };
215
+ if (!merged.accountId)
216
+ merged.accountId = this.account;
217
+ return this.callIntoItems('position_transfer_records', merged);
218
+ }
219
+ /** Query single internal position transfer detail by ID. */
220
+ async getPositionTransferDetail(req) {
221
+ const merged = { ...req };
222
+ if (!merged.accountId)
223
+ merged.accountId = this.account;
224
+ return this.callInto('position_transfer_detail', merged);
225
+ }
226
+ /** Query external position transfer records (DWAC / FOP / etc.). */
227
+ async getPositionTransferExternalRecords(req) {
228
+ const merged = { ...req };
229
+ if (!merged.accountId)
230
+ merged.accountId = this.account;
231
+ return this.callIntoItems('position_transfer_external_records', merged);
97
232
  }
98
233
  }
99
234
  exports.TradeClient = TradeClient;
@@ -1,14 +1,20 @@
1
1
  /**
2
- * API 请求构造模块
2
+ * API request builder.
3
+ *
4
+ * Requests use camelCase parameters in TypeScript (idiomatic), but the
5
+ * server wire format is snake_case, so we convert keys before serializing.
3
6
  */
7
+ import { keysToSnakeCase } from './case-convert';
4
8
  /**
5
- * 创建 API 请求,将业务参数序列化为 JSON 字符串作为 bizContent。
9
+ * Create an API request, serializing business parameters to JSON as bizContent.
10
+ * camelCase keys are automatically converted to snake_case to match the server.
6
11
  *
7
- * @param method - API 方法名
8
- * @param bizParams - 业务参数(对象、字符串或 null)
9
- * @returns ApiRequest 对象
12
+ * @param method - API method name
13
+ * @param bizParams - Business parameters (object, string, or null); objects are converted recursively
14
+ * @param version - Optional API version override
15
+ * @returns ApiRequest object
10
16
  */
11
- export function createApiRequest(method, bizParams) {
17
+ export function createApiRequest(method, bizParams, version) {
12
18
  let bizContent;
13
19
  if (typeof bizParams === 'string') {
14
20
  bizContent = bizParams;
@@ -17,7 +23,7 @@ export function createApiRequest(method, bizParams) {
17
23
  bizContent = '{}';
18
24
  }
19
25
  else {
20
- bizContent = JSON.stringify(bizParams);
26
+ bizContent = JSON.stringify(keysToSnakeCase(bizParams));
21
27
  }
22
- return { method, bizContent };
28
+ return { method, bizContent, version };
23
29
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
- * API 响应解析模块
2
+ * API response parsing.
3
3
  */
4
4
  import { TigerError } from './errors';
5
5
  /**
6
- * 解析 API 响应 JSON 字符串。
7
- * code 为 0 时返回 ApiResponse;code 不为 0 时抛出 TigerError。
6
+ * Parse the raw HTTP response body.
7
+ * Throws TigerError when code != 0.
8
8
  */
9
9
  export function parseApiResponse(body) {
10
10
  const resp = JSON.parse(body);
@@ -13,3 +13,22 @@ export function parseApiResponse(body) {
13
13
  }
14
14
  return resp;
15
15
  }
16
+ /**
17
+ * Decode an ApiResponse.data payload into a typed value.
18
+ * Handles the server's occasional double-encoded JSON (where `data` is a
19
+ * JSON string that itself wraps JSON), seen on some trade endpoints.
20
+ */
21
+ export function unmarshalData(data) {
22
+ if (data == null)
23
+ return undefined;
24
+ if (typeof data === 'string') {
25
+ // Attempt to parse JSON string (double-encoded case)
26
+ try {
27
+ return JSON.parse(data);
28
+ }
29
+ catch {
30
+ return data;
31
+ }
32
+ }
33
+ return data;
34
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Case conversion utilities.
3
+ *
4
+ * TypeScript SDK uses camelCase idiomatically. The server expects
5
+ * snake_case for request field names and returns camelCase for responses.
6
+ * These helpers convert camelCase objects to snake_case before
7
+ * serialization; response side needs no conversion.
8
+ */
9
+ /** Convert a single camelCase key to snake_case. */
10
+ export function camelToSnake(key) {
11
+ return key.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase());
12
+ }
13
+ /**
14
+ * Recursively convert all object keys from camelCase to snake_case.
15
+ * Preserves arrays, primitives, and Date/null/undefined.
16
+ * Keys already containing an underscore are kept as-is (already snake_case).
17
+ */
18
+ export function keysToSnakeCase(value) {
19
+ if (Array.isArray(value)) {
20
+ return value.map((v) => keysToSnakeCase(v));
21
+ }
22
+ if (value && typeof value === 'object' && value.constructor === Object) {
23
+ const out = {};
24
+ for (const [k, v] of Object.entries(value)) {
25
+ const newKey = k.includes('_') ? k : camelToSnake(k);
26
+ out[newKey] = keysToSnakeCase(v);
27
+ }
28
+ return out;
29
+ }
30
+ return value;
31
+ }
@@ -1,10 +1,10 @@
1
1
  import { getSignContent } from '../signer/sign-content';
2
- import { signWithRSA } from '../signer/signer';
2
+ import { signWithRSA, verifyWithRSA } from '../signer/signer';
3
3
  import { TigerError } from './errors';
4
4
  import { defaultRetryPolicy } from './retry';
5
5
  import { parseApiResponse } from './api-response';
6
6
  /** SDK 版本号 */
7
- const SDK_VERSION = '0.1.0';
7
+ const SDK_VERSION = '0.4.0';
8
8
  /** User-Agent 字符串 */
9
9
  const USER_AGENT = `openapi-typescript-sdk-${SDK_VERSION}`;
10
10
  /** 默认字符集 */
@@ -14,29 +14,33 @@ const DEFAULT_SIGN_TYPE = 'RSA';
14
14
  /** 默认 API 版本 */
15
15
  const DEFAULT_VERSION = '2.0';
16
16
  /**
17
- * HttpClient 封装 HTTP 请求、签名、重试、超时
17
+ * HttpClient handles HTTP requests, signing, retry, and timeout
18
18
  */
19
19
  export class HttpClient {
20
- constructor(config, retryPolicy) {
20
+ constructor(config, retryPolicy, options) {
21
21
  this.config = config;
22
22
  this.retryPolicy = retryPolicy ?? defaultRetryPolicy();
23
+ this.targetUrl = options?.useQuoteServerUrl ? config.quoteServerUrl : config.serverUrl;
23
24
  }
24
25
  /**
25
- * 构造公共请求参数
26
+ * Build common request parameters
26
27
  */
27
- buildCommonParams(apiMethod, bizContent) {
28
+ buildCommonParams(apiMethod, bizContent, version) {
28
29
  const params = {
29
30
  tiger_id: this.config.tigerId,
30
31
  method: apiMethod,
31
32
  charset: DEFAULT_CHARSET,
32
33
  sign_type: DEFAULT_SIGN_TYPE,
33
34
  timestamp: new Date().toISOString().replace('T', ' ').substring(0, 19),
34
- version: DEFAULT_VERSION,
35
+ version: version || DEFAULT_VERSION,
35
36
  biz_content: bizContent,
36
37
  };
37
38
  if (this.config.language) {
38
39
  params['language'] = this.config.language;
39
40
  }
41
+ if (this.config.deviceId) {
42
+ params['device_id'] = this.config.deviceId;
43
+ }
40
44
  return params;
41
45
  }
42
46
  /**
@@ -47,7 +51,7 @@ export class HttpClient {
47
51
  return signWithRSA(this.config.privateKey, content);
48
52
  }
49
53
  /**
50
- * 发送 HTTP POST 请求
54
+ * Send HTTP POST request
51
55
  */
52
56
  async doHttpPost(params) {
53
57
  const body = JSON.stringify(params);
@@ -58,7 +62,7 @@ export class HttpClient {
58
62
  if (this.config.token) {
59
63
  headers['Authorization'] = this.config.token;
60
64
  }
61
- const response = await fetch(this.config.serverUrl, {
65
+ const response = await fetch(this.targetUrl, {
62
66
  method: 'POST',
63
67
  headers,
64
68
  body,
@@ -67,11 +71,11 @@ export class HttpClient {
67
71
  return response.text();
68
72
  }
69
73
  /**
70
- * 执行结构化 API 请求,返回解析后的 ApiResponse。
71
- * 供 QuoteClient/TradeClient 内部使用。
74
+ * Execute a structured API request, returning a parsed ApiResponse.
75
+ * Used internally by QuoteClient/TradeClient.
72
76
  */
73
77
  async executeRequest(request) {
74
- const params = this.buildCommonParams(request.method, request.bizContent);
78
+ const params = this.buildCommonParams(request.method, request.bizContent, request.version);
75
79
  const sign = this.signParams(params);
76
80
  params['sign'] = sign;
77
81
  let lastErr;
@@ -85,7 +89,9 @@ export class HttpClient {
85
89
  }
86
90
  try {
87
91
  const body = await this.doHttpPost(params);
88
- return parseApiResponse(body);
92
+ const response = parseApiResponse(body);
93
+ this.verifyResponseSign(params['timestamp'], response.sign);
94
+ return response;
89
95
  }
90
96
  catch (err) {
91
97
  lastErr = err;
@@ -97,22 +103,22 @@ export class HttpClient {
97
103
  throw lastErr;
98
104
  }
99
105
  /**
100
- * 通用 API 调用方法。
106
+ * General-purpose API call method.
101
107
  *
102
- * @param apiMethod - API 方法名(如 "market_state"、"place_order")
103
- * @param requestJson - 原始 biz_content JSON 字符串
104
- * @returns 原始 response JSON 字符串,不做任何解析
108
+ * @param apiMethod - API method name (e.g. "market_state", "place_order")
109
+ * @param requestJson - Raw biz_content JSON string
110
+ * @returns Raw response JSON string, unparsed
105
111
  */
106
112
  async execute(apiMethod, requestJson) {
107
- // 参数校验
113
+ // Parameter validation
108
114
  if (!apiMethod) {
109
- throw new TigerError(-1, 'api_method 不能为空');
115
+ throw new TigerError(-1, 'api_method must not be empty');
110
116
  }
111
117
  try {
112
118
  JSON.parse(requestJson);
113
119
  }
114
120
  catch {
115
- throw new TigerError(-1, 'request_json 不是有效的 JSON');
121
+ throw new TigerError(-1, 'request_json is not valid JSON');
116
122
  }
117
123
  const params = this.buildCommonParams(apiMethod, requestJson);
118
124
  const sign = this.signParams(params);
@@ -127,7 +133,11 @@ export class HttpClient {
127
133
  await this.sleep(backoff);
128
134
  }
129
135
  try {
130
- return await this.doHttpPost(params);
136
+ const body = await this.doHttpPost(params);
137
+ // Verify response signature even for raw responses
138
+ const parsed = JSON.parse(body);
139
+ this.verifyResponseSign(params['timestamp'], parsed.sign);
140
+ return body;
131
141
  }
132
142
  catch (err) {
133
143
  lastErr = err;
@@ -138,8 +148,24 @@ export class HttpClient {
138
148
  }
139
149
  throw lastErr;
140
150
  }
141
- /** 延迟指定毫秒 */
151
+ /** Delay for the specified milliseconds */
142
152
  sleep(ms) {
143
153
  return new Promise((resolve) => setTimeout(resolve, ms));
144
154
  }
155
+ /**
156
+ * Verify the response signature using the tiger public key.
157
+ *
158
+ * @param timestamp - The timestamp sent in the request params
159
+ * @param signBase64 - The `sign` field from the response body
160
+ * @throws TigerError if the signature is missing or invalid
161
+ */
162
+ verifyResponseSign(timestamp, signBase64) {
163
+ if (!signBase64) {
164
+ throw new TigerError(-1, 'Response signature is missing');
165
+ }
166
+ const valid = verifyWithRSA(this.config.tigerPublicKey, timestamp, signBase64);
167
+ if (!valid) {
168
+ throw new TigerError(-1, 'Response signature verification failed');
169
+ }
170
+ }
145
171
  }
@@ -1,8 +1,9 @@
1
1
  /**
2
- * 传输层模块导出
2
+ * Transport layer exports.
3
3
  */
4
4
  export { HttpClient } from './http-client';
5
5
  export { TigerError, classifyErrorCode } from './errors';
6
6
  export { RetryPolicy, defaultRetryPolicy, isTradeOperation } from './retry';
7
7
  export { createApiRequest } from './api-request';
8
- export { parseApiResponse } from './api-response';
8
+ export { parseApiResponse, unmarshalData } from './api-response';
9
+ export { camelToSnake, keysToSnakeCase } from './case-convert';