@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.
- package/README.md +475 -104
- package/dist/cjs/client/api-request.js +16 -10
- package/dist/cjs/client/api-response.js +23 -3
- package/dist/cjs/client/case-convert.js +35 -0
- package/dist/cjs/client/http-client.js +47 -21
- package/dist/cjs/client/index.js +6 -2
- package/dist/cjs/config/client-config.js +130 -49
- package/dist/cjs/config/domain.js +21 -2
- package/dist/cjs/config/index.js +3 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/model/contract.js +1 -2
- package/dist/cjs/model/enums.js +89 -7
- package/dist/cjs/model/order-helpers.js +9 -9
- package/dist/cjs/model/order-status.js +46 -0
- package/dist/cjs/model/order.js +5 -2
- package/dist/cjs/model/position.js +1 -2
- package/dist/cjs/model/quote-requests.js +11 -0
- package/dist/cjs/model/quote.js +7 -0
- package/dist/cjs/model/trade-requests.js +13 -0
- package/dist/cjs/model/trade.js +2 -0
- package/dist/cjs/push/index.js +6 -3
- package/dist/cjs/push/pb/AssetData.js +208 -0
- package/dist/cjs/push/pb/KlineData.js +184 -0
- package/dist/cjs/push/pb/OptionTopData.js +427 -0
- package/dist/cjs/push/pb/OrderStatusData.js +592 -0
- package/dist/cjs/push/pb/OrderTransactionData.js +256 -0
- package/dist/cjs/push/pb/PositionData.js +316 -0
- package/dist/cjs/push/pb/PushData.js +219 -0
- package/dist/cjs/push/pb/QuoteBBOData.js +160 -0
- package/dist/cjs/push/pb/QuoteBasicData.js +329 -0
- package/dist/cjs/push/pb/QuoteData.js +507 -0
- package/dist/cjs/push/pb/QuoteDepthData.js +220 -0
- package/dist/cjs/push/pb/Request.js +278 -0
- package/dist/cjs/push/pb/Response.js +97 -0
- package/dist/cjs/push/pb/SocketCommon.js +86 -0
- package/dist/cjs/push/pb/StockTopData.js +199 -0
- package/dist/cjs/push/pb/TickData.js +202 -0
- package/dist/cjs/push/pb/TradeTickData.js +308 -0
- package/dist/cjs/push/pb/index.js +49 -0
- package/dist/cjs/push/proto-message.js +135 -0
- package/dist/cjs/push/push-client.js +361 -163
- package/dist/cjs/push/push-message.js +8 -60
- package/dist/cjs/push/varint.js +76 -0
- package/dist/cjs/quote/index.js +3 -2
- package/dist/cjs/quote/quote-client.js +440 -94
- package/dist/cjs/signer/signer.js +10 -10
- package/dist/cjs/trade/trade-client.js +197 -62
- package/dist/esm/client/api-request.js +14 -8
- package/dist/esm/client/api-response.js +22 -3
- package/dist/esm/client/case-convert.js +31 -0
- package/dist/esm/client/http-client.js +48 -22
- package/dist/esm/client/index.js +3 -2
- package/dist/esm/config/client-config.js +131 -51
- package/dist/esm/config/domain.js +20 -2
- package/dist/esm/config/index.js +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/model/contract.js +1 -2
- package/dist/esm/model/enums.js +87 -6
- package/dist/esm/model/order-helpers.js +9 -9
- package/dist/esm/model/order-status.js +43 -0
- package/dist/esm/model/order.js +5 -2
- package/dist/esm/model/position.js +1 -2
- package/dist/esm/model/quote-requests.js +10 -0
- package/dist/esm/model/quote.js +6 -0
- package/dist/esm/model/trade-requests.js +12 -0
- package/dist/esm/model/trade.js +1 -0
- package/dist/esm/push/index.js +4 -2
- package/dist/esm/push/pb/AssetData.js +205 -0
- package/dist/esm/push/pb/KlineData.js +181 -0
- package/dist/esm/push/pb/OptionTopData.js +424 -0
- package/dist/esm/push/pb/OrderStatusData.js +589 -0
- package/dist/esm/push/pb/OrderTransactionData.js +253 -0
- package/dist/esm/push/pb/PositionData.js +313 -0
- package/dist/esm/push/pb/PushData.js +216 -0
- package/dist/esm/push/pb/QuoteBBOData.js +157 -0
- package/dist/esm/push/pb/QuoteBasicData.js +326 -0
- package/dist/esm/push/pb/QuoteData.js +504 -0
- package/dist/esm/push/pb/QuoteDepthData.js +217 -0
- package/dist/esm/push/pb/Request.js +275 -0
- package/dist/esm/push/pb/Response.js +94 -0
- package/dist/esm/push/pb/SocketCommon.js +83 -0
- package/dist/esm/push/pb/StockTopData.js +196 -0
- package/dist/esm/push/pb/TickData.js +199 -0
- package/dist/esm/push/pb/TradeTickData.js +305 -0
- package/dist/esm/push/pb/index.js +25 -0
- package/dist/esm/push/proto-message.js +127 -0
- package/dist/esm/push/push-client.js +329 -164
- package/dist/esm/push/push-message.js +7 -56
- package/dist/esm/push/varint.js +72 -0
- package/dist/esm/quote/index.js +2 -2
- package/dist/esm/quote/quote-client.js +439 -94
- package/dist/esm/signer/signer.js +10 -10
- package/dist/esm/trade/trade-client.js +197 -62
- package/dist/types/client/api-request.d.ts +12 -11
- package/dist/types/client/api-response.d.ts +15 -7
- package/dist/types/client/case-convert.d.ts +16 -0
- package/dist/types/client/http-client.d.ts +26 -14
- package/dist/types/client/index.d.ts +3 -2
- package/dist/types/config/client-config.d.ts +29 -13
- package/dist/types/config/domain.d.ts +9 -0
- package/dist/types/config/index.d.ts +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model/contract.d.ts +13 -29
- package/dist/types/model/enums.d.ts +60 -6
- package/dist/types/model/order-helpers.d.ts +21 -20
- package/dist/types/model/order-status.d.ts +14 -0
- package/dist/types/model/order.d.ts +101 -55
- package/dist/types/model/position.d.ts +26 -24
- package/dist/types/model/quote-requests.d.ts +344 -0
- package/dist/types/model/quote.d.ts +572 -0
- package/dist/types/model/trade-requests.d.ts +195 -0
- package/dist/types/model/trade.d.ts +235 -0
- package/dist/types/push/callbacks.d.ts +32 -51
- package/dist/types/push/index.d.ts +5 -3
- package/dist/types/push/pb/AssetData.d.ts +47 -0
- package/dist/types/push/pb/KlineData.d.ts +43 -0
- package/dist/types/push/pb/OptionTopData.d.ts +76 -0
- package/dist/types/push/pb/OrderStatusData.d.ts +100 -0
- package/dist/types/push/pb/OrderTransactionData.d.ts +51 -0
- package/dist/types/push/pb/PositionData.d.ts +63 -0
- package/dist/types/push/pb/PushData.d.ts +46 -0
- package/dist/types/push/pb/QuoteBBOData.d.ts +35 -0
- package/dist/types/push/pb/QuoteBasicData.d.ts +66 -0
- package/dist/types/push/pb/QuoteData.d.ts +94 -0
- package/dist/types/push/pb/QuoteDepthData.d.ts +37 -0
- package/dist/types/push/pb/Request.d.ts +44 -0
- package/dist/types/push/pb/Response.d.ts +30 -0
- package/dist/types/push/pb/SocketCommon.d.ts +64 -0
- package/dist/types/push/pb/StockTopData.d.ts +37 -0
- package/dist/types/push/pb/TickData.d.ts +44 -0
- package/dist/types/push/pb/TradeTickData.d.ts +53 -0
- package/dist/types/push/pb/index.d.ts +39 -0
- package/dist/types/push/proto-message.d.ts +33 -0
- package/dist/types/push/push-client.d.ts +100 -68
- package/dist/types/push/push-message.d.ts +7 -147
- package/dist/types/push/varint.d.ts +23 -0
- package/dist/types/quote/index.d.ts +2 -2
- package/dist/types/quote/quote-client.d.ts +154 -47
- package/dist/types/signer/signer.d.ts +6 -6
- package/dist/types/trade/trade-client.d.ts +106 -38
- 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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
59
|
-
account: this.account, id,
|
|
60
|
-
});
|
|
75
|
+
return this.callInto('cancel_order', { account: this.account, id });
|
|
61
76
|
}
|
|
62
|
-
// ===
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
69
|
-
return this.
|
|
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
|
|
73
|
-
return this.
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
|
94
|
-
return this.
|
|
95
|
-
|
|
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
|
-
*
|
|
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 -
|
|
9
|
-
* @
|
|
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
|
-
*
|
|
7
|
-
*
|
|
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.
|
|
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
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
71
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
106
|
+
* General-purpose API call method.
|
|
101
107
|
*
|
|
102
|
-
* @param apiMethod - API
|
|
103
|
-
* @param requestJson -
|
|
104
|
-
* @returns
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/dist/esm/client/index.js
CHANGED
|
@@ -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';
|