@tigeropenapi/tigeropen 0.4.9 → 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.
- package/dist/cjs/client/api-response.js +17 -15
- package/dist/cjs/config/client-config.js +46 -8
- package/dist/cjs/config/config-parser.js +19 -9
- package/dist/cjs/config/index.js +2 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/model/enums.js +4 -0
- package/dist/cjs/quote/quote-client.js +59 -21
- package/dist/cjs/trade/trade-client.js +15 -16
- package/dist/esm/client/api-response.js +17 -15
- package/dist/esm/config/client-config.js +45 -8
- package/dist/esm/config/config-parser.js +19 -9
- package/dist/esm/config/index.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/model/enums.js +4 -0
- package/dist/esm/quote/quote-client.js +59 -21
- package/dist/esm/trade/trade-client.js +15 -16
- package/dist/types/client/api-response.d.ts +6 -5
- package/dist/types/config/client-config.d.ts +2 -0
- package/dist/types/config/index.d.ts +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/model/enums.d.ts +5 -1
- package/dist/types/model/order.d.ts +45 -1
- package/dist/types/model/quote.d.ts +79 -10
- package/dist/types/model/trade-requests.d.ts +41 -0
- package/dist/types/model/trade.d.ts +2 -1
- package/dist/types/quote/quote-client.d.ts +19 -9
- package/dist/types/trade/trade-client.d.ts +0 -3
- package/package.json +1 -1
|
@@ -7,29 +7,33 @@ exports.unmarshalData = unmarshalData;
|
|
|
7
7
|
*/
|
|
8
8
|
const errors_1 = require("./errors");
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
//
|
|
22
|
-
|
|
23
|
-
//
|
|
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
|
-
*
|
|
29
|
-
*
|
|
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
|
|
41
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
218
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
80
|
-
|
|
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
|
* 值中可以包含 = 或 :,只按第一个分隔符拆分。
|
package/dist/cjs/config/index.js
CHANGED
|
@@ -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
|
package/dist/cjs/model/enums.js
CHANGED
|
@@ -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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
99
|
-
params.secretKey =
|
|
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('
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
//
|
|
18
|
-
|
|
19
|
-
//
|
|
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
|
-
*
|
|
25
|
-
*
|
|
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
|
|
37
|
-
*
|
|
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
|
}
|