@tigeropenapi/tigeropen 0.1.0 → 0.3.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 (129) 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 +46 -20
  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/order-helpers.js +9 -9
  13. package/dist/cjs/model/order.js +5 -2
  14. package/dist/cjs/model/position.js +1 -2
  15. package/dist/cjs/model/quote.js +7 -0
  16. package/dist/cjs/model/trade.js +2 -0
  17. package/dist/cjs/push/index.js +6 -3
  18. package/dist/cjs/push/pb/AssetData.js +208 -0
  19. package/dist/cjs/push/pb/KlineData.js +184 -0
  20. package/dist/cjs/push/pb/OptionTopData.js +427 -0
  21. package/dist/cjs/push/pb/OrderStatusData.js +568 -0
  22. package/dist/cjs/push/pb/OrderTransactionData.js +256 -0
  23. package/dist/cjs/push/pb/PositionData.js +316 -0
  24. package/dist/cjs/push/pb/PushData.js +219 -0
  25. package/dist/cjs/push/pb/QuoteBBOData.js +160 -0
  26. package/dist/cjs/push/pb/QuoteBasicData.js +329 -0
  27. package/dist/cjs/push/pb/QuoteData.js +507 -0
  28. package/dist/cjs/push/pb/QuoteDepthData.js +220 -0
  29. package/dist/cjs/push/pb/Request.js +278 -0
  30. package/dist/cjs/push/pb/Response.js +97 -0
  31. package/dist/cjs/push/pb/SocketCommon.js +86 -0
  32. package/dist/cjs/push/pb/StockTopData.js +199 -0
  33. package/dist/cjs/push/pb/TickData.js +202 -0
  34. package/dist/cjs/push/pb/TradeTickData.js +308 -0
  35. package/dist/cjs/push/pb/index.js +49 -0
  36. package/dist/cjs/push/proto-message.js +129 -0
  37. package/dist/cjs/push/push-client.js +292 -164
  38. package/dist/cjs/push/push-message.js +4 -60
  39. package/dist/cjs/push/varint.js +76 -0
  40. package/dist/cjs/quote/index.js +3 -2
  41. package/dist/cjs/quote/quote-client.js +128 -94
  42. package/dist/cjs/signer/signer.js +10 -10
  43. package/dist/cjs/trade/trade-client.js +56 -62
  44. package/dist/esm/client/api-request.js +14 -8
  45. package/dist/esm/client/api-response.js +22 -3
  46. package/dist/esm/client/case-convert.js +31 -0
  47. package/dist/esm/client/http-client.js +47 -21
  48. package/dist/esm/client/index.js +3 -2
  49. package/dist/esm/config/client-config.js +131 -51
  50. package/dist/esm/config/domain.js +20 -2
  51. package/dist/esm/config/index.js +2 -2
  52. package/dist/esm/index.js +1 -1
  53. package/dist/esm/model/contract.js +1 -2
  54. package/dist/esm/model/order-helpers.js +9 -9
  55. package/dist/esm/model/order.js +5 -2
  56. package/dist/esm/model/position.js +1 -2
  57. package/dist/esm/model/quote.js +6 -0
  58. package/dist/esm/model/trade.js +1 -0
  59. package/dist/esm/push/index.js +4 -2
  60. package/dist/esm/push/pb/AssetData.js +205 -0
  61. package/dist/esm/push/pb/KlineData.js +181 -0
  62. package/dist/esm/push/pb/OptionTopData.js +424 -0
  63. package/dist/esm/push/pb/OrderStatusData.js +565 -0
  64. package/dist/esm/push/pb/OrderTransactionData.js +253 -0
  65. package/dist/esm/push/pb/PositionData.js +313 -0
  66. package/dist/esm/push/pb/PushData.js +216 -0
  67. package/dist/esm/push/pb/QuoteBBOData.js +157 -0
  68. package/dist/esm/push/pb/QuoteBasicData.js +326 -0
  69. package/dist/esm/push/pb/QuoteData.js +504 -0
  70. package/dist/esm/push/pb/QuoteDepthData.js +217 -0
  71. package/dist/esm/push/pb/Request.js +275 -0
  72. package/dist/esm/push/pb/Response.js +94 -0
  73. package/dist/esm/push/pb/SocketCommon.js +83 -0
  74. package/dist/esm/push/pb/StockTopData.js +196 -0
  75. package/dist/esm/push/pb/TickData.js +199 -0
  76. package/dist/esm/push/pb/TradeTickData.js +305 -0
  77. package/dist/esm/push/pb/index.js +25 -0
  78. package/dist/esm/push/proto-message.js +121 -0
  79. package/dist/esm/push/push-client.js +260 -165
  80. package/dist/esm/push/push-message.js +3 -56
  81. package/dist/esm/push/varint.js +72 -0
  82. package/dist/esm/quote/index.js +2 -2
  83. package/dist/esm/quote/quote-client.js +127 -94
  84. package/dist/esm/signer/signer.js +10 -10
  85. package/dist/esm/trade/trade-client.js +56 -62
  86. package/dist/types/client/api-request.d.ts +12 -11
  87. package/dist/types/client/api-response.d.ts +15 -7
  88. package/dist/types/client/case-convert.d.ts +16 -0
  89. package/dist/types/client/http-client.d.ts +26 -14
  90. package/dist/types/client/index.d.ts +3 -2
  91. package/dist/types/config/client-config.d.ts +29 -13
  92. package/dist/types/config/domain.d.ts +9 -0
  93. package/dist/types/config/index.d.ts +2 -2
  94. package/dist/types/index.d.ts +1 -1
  95. package/dist/types/model/contract.d.ts +13 -29
  96. package/dist/types/model/order-helpers.d.ts +21 -20
  97. package/dist/types/model/order.d.ts +101 -55
  98. package/dist/types/model/position.d.ts +26 -24
  99. package/dist/types/model/quote.d.ts +311 -0
  100. package/dist/types/model/trade.d.ts +113 -0
  101. package/dist/types/push/callbacks.d.ts +32 -51
  102. package/dist/types/push/index.d.ts +5 -3
  103. package/dist/types/push/pb/AssetData.d.ts +47 -0
  104. package/dist/types/push/pb/KlineData.d.ts +43 -0
  105. package/dist/types/push/pb/OptionTopData.d.ts +76 -0
  106. package/dist/types/push/pb/OrderStatusData.d.ts +96 -0
  107. package/dist/types/push/pb/OrderTransactionData.d.ts +51 -0
  108. package/dist/types/push/pb/PositionData.d.ts +63 -0
  109. package/dist/types/push/pb/PushData.d.ts +46 -0
  110. package/dist/types/push/pb/QuoteBBOData.d.ts +35 -0
  111. package/dist/types/push/pb/QuoteBasicData.d.ts +66 -0
  112. package/dist/types/push/pb/QuoteData.d.ts +94 -0
  113. package/dist/types/push/pb/QuoteDepthData.d.ts +37 -0
  114. package/dist/types/push/pb/Request.d.ts +44 -0
  115. package/dist/types/push/pb/Response.d.ts +30 -0
  116. package/dist/types/push/pb/SocketCommon.d.ts +64 -0
  117. package/dist/types/push/pb/StockTopData.d.ts +37 -0
  118. package/dist/types/push/pb/TickData.d.ts +44 -0
  119. package/dist/types/push/pb/TradeTickData.d.ts +53 -0
  120. package/dist/types/push/pb/index.d.ts +39 -0
  121. package/dist/types/push/proto-message.d.ts +33 -0
  122. package/dist/types/push/push-client.d.ts +69 -68
  123. package/dist/types/push/push-message.d.ts +3 -147
  124. package/dist/types/push/varint.d.ts +23 -0
  125. package/dist/types/quote/index.d.ts +2 -2
  126. package/dist/types/quote/quote-client.d.ts +41 -47
  127. package/dist/types/signer/signer.d.ts +6 -6
  128. package/dist/types/trade/trade-client.d.ts +34 -38
  129. package/package.json +11 -6
@@ -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,5 +1,5 @@
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';
@@ -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';
@@ -1,58 +1,125 @@
1
1
  /**
2
- * ClientConfig - 客户端配置管理
2
+ * ClientConfig - Client configuration management
3
3
  *
4
- * 聚合所有配置参数,支持:
5
- * - 代码直接设置
6
- * - 从 properties 配置文件加载
7
- * - 环境变量覆盖
4
+ * Aggregates all configuration parameters with support for:
5
+ * - Direct code configuration
6
+ * - Properties file loading (explicit path or auto-discovery)
7
+ * - Environment variable overrides
8
8
  *
9
- * 优先级:环境变量 > 代码设置(含配置文件) > 默认值
9
+ * Priority: environment variables > code options (incl. config file) > defaults
10
10
  */
11
- import { readFileSync } from 'fs';
11
+ import { readFileSync, existsSync } from 'fs';
12
+ import { join } from 'path';
13
+ import { homedir, networkInterfaces } from 'os';
12
14
  import { parsePropertiesString } from './config-parser';
13
- import { queryDomains, resolveDynamicServerUrl } from './domain';
14
- /** 默认值 */
15
+ import { queryDomains, resolveDynamicServerUrl, resolveDynamicQuoteServerUrl } from './domain';
16
+ /** Default config file name */
17
+ const CONFIG_FILE_NAME = 'tiger_openapi_config.properties';
18
+ /** Defaults */
15
19
  const DEFAULT_LANGUAGE = 'zh_CN';
16
20
  const DEFAULT_TIMEOUT = 15;
17
21
  const DEFAULT_SERVER_URL = 'https://openapi.tigerfintech.com/gateway';
18
- const SANDBOX_SERVER_URL = 'https://openapi-sandbox.tigerfintech.com/gateway';
19
- /** 环境变量名 */
22
+ /** Tiger public key for response signature verification (Base64-encoded) */
23
+ const TIGER_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNF3G8SoEcCZh2rshUbayDgLLrj6rKgzNMxDL2HSnKcB0+GPOsndqSv+a4IBu9+I3fyBp5hkyMMG2+AXugd9pMpy6VxJxlNjhX1MYbNTZJUT4nudki4uh+LMOkIBHOceGNXjgB+cXqmlUnjlqha/HgboeHSnSgpM3dKSJQlIOsDwIDAQAB';
24
+ /** Environment variable names */
20
25
  const ENV_TIGER_ID = 'TIGEROPEN_TIGER_ID';
21
26
  const ENV_PRIVATE_KEY = 'TIGEROPEN_PRIVATE_KEY';
22
27
  const ENV_ACCOUNT = 'TIGEROPEN_ACCOUNT';
28
+ const ENV_TOKEN = 'TIGEROPEN_TOKEN';
23
29
  /**
24
- * 创建客户端配置。
30
+ * Load and parse a Java-style properties file.
25
31
  *
26
- * 优先级:环境变量 > 代码设置(含配置文件) > 默认值。
27
- * 必填字段 tigerId 和 privateKey 为空时抛出错误。
32
+ * @param filePath - Absolute or relative path to the properties file
33
+ * @returns Parsed key-value pairs, or empty object if the file cannot be read
34
+ */
35
+ export function loadPropertiesFile(filePath) {
36
+ try {
37
+ const content = readFileSync(filePath, 'utf-8');
38
+ return parsePropertiesString(content);
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ /**
45
+ * Discover the config file by searching well-known locations.
46
+ *
47
+ * Search order:
48
+ * 1. `./tiger_openapi_config.properties` (current working directory)
49
+ * 2. `~/.tigeropen/tiger_openapi_config.properties` (home directory)
50
+ *
51
+ * @returns The resolved file path, or `undefined` if not found
52
+ */
53
+ function discoverConfigFile() {
54
+ const candidates = [
55
+ join(process.cwd(), CONFIG_FILE_NAME),
56
+ join(homedir(), '.tigeropen', CONFIG_FILE_NAME),
57
+ ];
58
+ for (const candidate of candidates) {
59
+ if (existsSync(candidate)) {
60
+ return candidate;
61
+ }
62
+ }
63
+ return undefined;
64
+ }
65
+ /**
66
+ * Auto-detect device identifier from the first non-internal MAC address.
28
67
  *
29
- * @param options - 配置选项
30
- * @returns 完整的客户端配置对象
31
- * @throws tigerId 或 privateKey 为空时抛出错误
68
+ * @returns MAC address string, or empty string if none found
69
+ */
70
+ function detectDeviceId() {
71
+ const interfaces = networkInterfaces();
72
+ for (const [, addrs] of Object.entries(interfaces)) {
73
+ if (!addrs)
74
+ continue;
75
+ for (const addr of addrs) {
76
+ if (!addr.internal && addr.mac && addr.mac !== '00:00:00:00:00:00') {
77
+ return addr.mac;
78
+ }
79
+ }
80
+ }
81
+ return '';
82
+ }
83
+ /**
84
+ * Create a client configuration.
85
+ *
86
+ * When called with no arguments (or without `propertiesFilePath`), the function
87
+ * automatically searches for `tiger_openapi_config.properties` in the current
88
+ * directory and `~/.tigeropen/`. Discovered values serve as defaults; explicit
89
+ * options and environment variables take precedence.
90
+ *
91
+ * Priority: environment variables > code options > config file > defaults
92
+ *
93
+ * @param options - Configuration options (optional)
94
+ * @returns Complete client configuration object
95
+ * @throws When tigerId or privateKey is empty after all resolution
32
96
  */
33
97
  export function createClientConfig(options) {
34
- // 从配置文件加载(如果指定了路径)
98
+ const opts = options ?? {};
99
+ // Load properties: explicit path > auto-discovery
35
100
  let fileProps = {};
36
- if (options.propertiesFilePath) {
37
- try {
38
- const content = readFileSync(options.propertiesFilePath, 'utf-8');
39
- fileProps = parsePropertiesString(content);
40
- }
41
- catch {
42
- // 文件加载失败时静默跳过,后续校验会捕获必填字段缺失
101
+ if (opts.propertiesFilePath) {
102
+ fileProps = loadPropertiesFile(opts.propertiesFilePath);
103
+ }
104
+ else {
105
+ const discovered = discoverConfigFile();
106
+ if (discovered) {
107
+ fileProps = loadPropertiesFile(discovered);
43
108
  }
44
109
  }
45
- // 从配置文件提取值(代码设置优先于配置文件)
46
- let tigerId = options.tigerId || fileProps['tiger_id'] || '';
47
- let privateKey = options.privateKey || resolvePrivateKey(fileProps) || '';
48
- let account = options.account || fileProps['account'] || '';
49
- const license = options.license || fileProps['license'];
50
- const language = options.language || fileProps['language'] || DEFAULT_LANGUAGE;
51
- const timezone = options.timezone || fileProps['timezone'];
52
- // 环境变量覆盖(最高优先级)
110
+ // Merge values (code options override config file)
111
+ let tigerId = opts.tigerId || fileProps['tiger_id'] || '';
112
+ let privateKey = opts.privateKey || resolvePrivateKey(fileProps) || '';
113
+ let account = opts.account || fileProps['account'] || '';
114
+ let token = opts.token || fileProps['token'];
115
+ const license = opts.license || fileProps['license'];
116
+ const language = opts.language || fileProps['language'] || DEFAULT_LANGUAGE;
117
+ const timezone = opts.timezone || fileProps['timezone'];
118
+ // Environment variables override everything
53
119
  const envTigerId = process.env[ENV_TIGER_ID];
54
120
  const envPrivateKey = process.env[ENV_PRIVATE_KEY];
55
121
  const envAccount = process.env[ENV_ACCOUNT];
122
+ const envToken = process.env[ENV_TOKEN];
56
123
  if (envTigerId) {
57
124
  tigerId = envTigerId;
58
125
  }
@@ -62,25 +129,24 @@ export function createClientConfig(options) {
62
129
  if (envAccount) {
63
130
  account = envAccount;
64
131
  }
65
- // 校验必填字段
132
+ if (envToken) {
133
+ token = envToken;
134
+ }
135
+ // Validate required fields
66
136
  if (!tigerId) {
67
- throw new Error(`tigerId 不能为空,请通过 options.tigerId 或环境变量 ${ENV_TIGER_ID} 设置`);
137
+ throw new Error(`tigerId is required. Set it via options.tigerId, a config file, or the ${ENV_TIGER_ID} environment variable.`);
68
138
  }
69
139
  if (!privateKey) {
70
- throw new Error(`privateKey 不能为空,请通过 options.privateKey 或环境变量 ${ENV_PRIVATE_KEY} 设置`);
140
+ throw new Error(`privateKey is required. Set it via options.privateKey, a config file, or the ${ENV_PRIVATE_KEY} environment variable.`);
71
141
  }
72
- // 确定服务器 URL:sandbox > 动态域名 > 默认
73
- const sandboxDebug = options.sandboxDebug ?? false;
74
- const enableDynamicDomain = options.enableDynamicDomain ?? true;
142
+ // Resolve server URL: explicit > dynamic domain > default
143
+ const enableDynamicDomain = opts.enableDynamicDomain ?? true;
75
144
  let serverUrl;
76
- if (sandboxDebug) {
77
- serverUrl = SANDBOX_SERVER_URL;
78
- }
79
- else if (options.serverUrl) {
80
- serverUrl = options.serverUrl;
145
+ let quoteServerUrl;
146
+ if (opts.serverUrl) {
147
+ serverUrl = opts.serverUrl;
81
148
  }
82
149
  else {
83
- // 尝试动态域名获取
84
150
  let dynamicUrl = '';
85
151
  if (enableDynamicDomain) {
86
152
  const domainConf = queryDomains(license);
@@ -88,6 +154,18 @@ export function createClientConfig(options) {
88
154
  }
89
155
  serverUrl = dynamicUrl || DEFAULT_SERVER_URL;
90
156
  }
157
+ // Resolve quote server URL: explicit > dynamic domain > serverUrl fallback
158
+ if (opts.quoteServerUrl) {
159
+ quoteServerUrl = opts.quoteServerUrl;
160
+ }
161
+ else {
162
+ let dynamicQuoteUrl = '';
163
+ if (enableDynamicDomain) {
164
+ const domainConf = queryDomains(license);
165
+ dynamicQuoteUrl = resolveDynamicQuoteServerUrl(domainConf, license);
166
+ }
167
+ quoteServerUrl = dynamicQuoteUrl || serverUrl;
168
+ }
91
169
  return {
92
170
  tigerId,
93
171
  privateKey,
@@ -95,16 +173,18 @@ export function createClientConfig(options) {
95
173
  license,
96
174
  language,
97
175
  timezone,
98
- timeout: options.timeout ?? DEFAULT_TIMEOUT,
99
- sandboxDebug,
100
- token: options.token,
101
- tokenRefreshDuration: options.tokenRefreshDuration,
176
+ timeout: opts.timeout ?? DEFAULT_TIMEOUT,
177
+ token,
178
+ tokenRefreshDuration: opts.tokenRefreshDuration,
102
179
  serverUrl,
180
+ quoteServerUrl,
181
+ deviceId: detectDeviceId(),
182
+ tigerPublicKey: TIGER_PUBLIC_KEY,
103
183
  };
104
184
  }
105
185
  /**
106
- * 从 properties 键值对中解析私钥。
107
- * 优先级:private_key > private_key_pk8 > private_key_pk1
186
+ * Resolve private key from properties.
187
+ * Priority: private_key > private_key_pk8 > private_key_pk1
108
188
  */
109
189
  function resolvePrivateKey(props) {
110
190
  return props['private_key'] || props['private_key_pk8'] || props['private_key_pk1'] || '';
@@ -63,8 +63,26 @@ export function resolveDynamicServerUrl(domainConf, license) {
63
63
  return '';
64
64
  }
65
65
  /**
66
- * 同步 HTTP GET 请求(Node.js 环境)。
67
- * 使用 child_process.execSync 实现同步请求,超时快速失败。
66
+ * Resolve the quote server URL from dynamic domain configuration.
67
+ * Uses the `{LICENSE}-QUOTE` key, falling back to empty string.
68
+ *
69
+ * @param domainConf - Domain configuration map from queryDomains
70
+ * @param license - License type (e.g. "TBNZ")
71
+ * @returns Quote server URL with gateway suffix, or empty string
72
+ */
73
+ export function resolveDynamicQuoteServerUrl(domainConf, license) {
74
+ if (!domainConf || Object.keys(domainConf).length === 0) {
75
+ return '';
76
+ }
77
+ const key = license ? `${license}-QUOTE` : `${DOMAIN_KEY_COMMON}-QUOTE`;
78
+ if (typeof domainConf[key] === 'string') {
79
+ return domainConf[key] + GATEWAY_SUFFIX;
80
+ }
81
+ return '';
82
+ }
83
+ /**
84
+ * Synchronous HTTP GET request (Node.js environment).
85
+ * Uses child_process.execSync for synchronous requests with fast timeout.
68
86
  */
69
87
  function syncHttpGet(url, timeoutMs) {
70
88
  try {
@@ -2,5 +2,5 @@
2
2
  * 配置层导出
3
3
  */
4
4
  export { parsePropertiesFile, parsePropertiesString } from './config-parser';
5
- export { createClientConfig } from './client-config';
6
- export { queryDomains, resolveDynamicServerUrl } from './domain';
5
+ export { createClientConfig, loadPropertiesFile } from './client-config';
6
+ export { queryDomains, resolveDynamicServerUrl, resolveDynamicQuoteServerUrl } from './domain';
package/dist/esm/index.js CHANGED
@@ -15,4 +15,4 @@
15
15
  * @packageDocumentation
16
16
  */
17
17
  /** SDK 版本号 */
18
- export const VERSION = '0.1.0';
18
+ export const VERSION = '0.3.0';
@@ -1,5 +1,4 @@
1
1
  /**
2
- * Contract 合约接口
3
- * 字段名 camelCase 与 API JSON 天然一致,不改名、不加封装层
2
+ * Contract / TickSize models.
4
3
  */
5
4
  export {};
@@ -1,5 +1,5 @@
1
1
  import { OrderType, TimeInForce } from './enums';
2
- /** 构造市价单 */
2
+ /** Market order */
3
3
  export function marketOrder(account, symbol, secType, action, quantity) {
4
4
  return {
5
5
  account, symbol, secType, action,
@@ -9,7 +9,7 @@ export function marketOrder(account, symbol, secType, action, quantity) {
9
9
  outsideRth: false,
10
10
  };
11
11
  }
12
- /** 构造限价单 */
12
+ /** Limit order */
13
13
  export function limitOrder(account, symbol, secType, action, quantity, limitPrice) {
14
14
  return {
15
15
  account, symbol, secType, action,
@@ -20,7 +20,7 @@ export function limitOrder(account, symbol, secType, action, quantity, limitPric
20
20
  outsideRth: false,
21
21
  };
22
22
  }
23
- /** 构造止损单 */
23
+ /** Stop order */
24
24
  export function stopOrder(account, symbol, secType, action, quantity, auxPrice) {
25
25
  return {
26
26
  account, symbol, secType, action,
@@ -31,7 +31,7 @@ export function stopOrder(account, symbol, secType, action, quantity, auxPrice)
31
31
  outsideRth: false,
32
32
  };
33
33
  }
34
- /** 构造止损限价单 */
34
+ /** Stop limit order */
35
35
  export function stopLimitOrder(account, symbol, secType, action, quantity, limitPrice, auxPrice) {
36
36
  return {
37
37
  account, symbol, secType, action,
@@ -43,7 +43,7 @@ export function stopLimitOrder(account, symbol, secType, action, quantity, limit
43
43
  outsideRth: false,
44
44
  };
45
45
  }
46
- /** 构造跟踪止损单 */
46
+ /** Trailing stop order */
47
47
  export function trailOrder(account, symbol, secType, action, quantity, trailingPercent) {
48
48
  return {
49
49
  account, symbol, secType, action,
@@ -54,7 +54,7 @@ export function trailOrder(account, symbol, secType, action, quantity, trailingP
54
54
  outsideRth: false,
55
55
  };
56
56
  }
57
- /** 构造竞价限价单 */
57
+ /** Auction limit order */
58
58
  export function auctionLimitOrder(account, symbol, secType, action, quantity, limitPrice) {
59
59
  return {
60
60
  account, symbol, secType, action,
@@ -65,7 +65,7 @@ export function auctionLimitOrder(account, symbol, secType, action, quantity, li
65
65
  outsideRth: false,
66
66
  };
67
67
  }
68
- /** 构造竞价市价单 */
68
+ /** Auction market order */
69
69
  export function auctionMarketOrder(account, symbol, secType, action, quantity) {
70
70
  return {
71
71
  account, symbol, secType, action,
@@ -75,7 +75,7 @@ export function auctionMarketOrder(account, symbol, secType, action, quantity) {
75
75
  outsideRth: false,
76
76
  };
77
77
  }
78
- /** 构造算法订单(TWAP/VWAP) */
78
+ /** Algo order (TWAP / VWAP) */
79
79
  export function algoOrder(account, symbol, secType, action, quantity, limitPrice, algoType, params) {
80
80
  return {
81
81
  account, symbol, secType, action,
@@ -87,7 +87,7 @@ export function algoOrder(account, symbol, secType, action, quantity, limitPrice
87
87
  outsideRth: false,
88
88
  };
89
89
  }
90
- /** 构造附加订单(止盈/止损) */
90
+ /** Attached profit / loss leg */
91
91
  export function orderLeg(legType, price, timeInForce) {
92
92
  return { legType, price, timeInForce };
93
93
  }
@@ -1,5 +1,8 @@
1
1
  /**
2
- * Order 订单接口
3
- * 字段名 camelCase 与 API JSON 天然一致,不改名、不加封装层
2
+ * Order response / request models.
3
+ *
4
+ * TypeScript uses camelCase field names idiomatically. The client layer
5
+ * transparently converts request camelCase to snake_case before sending;
6
+ * server responses are already camelCase so they decode directly.
4
7
  */
5
8
  export {};
@@ -1,5 +1,4 @@
1
1
  /**
2
- * Position 持仓接口
3
- * 字段名 camelCase 与 API JSON 天然一致,不改名、不加封装层
2
+ * Position model.
4
3
  */
5
4
  export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Quote response & request models.
3
+ * All field names are camelCase (TypeScript idiom); request fields are
4
+ * converted to snake_case on the wire by the client layer.
5
+ */
6
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,8 @@
1
1
  /**
2
- * 推送模块导出
2
+ * Push module exports
3
3
  */
4
- export * from './push-message';
4
+ export { SubjectType } from './push-message';
5
5
  export * from './callbacks';
6
+ export * from './varint';
7
+ export * from './proto-message';
6
8
  export { PushClient, ConnectionState } from './push-client';