openclaw-glance-plugin 0.1.22 → 0.1.27

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.
@@ -1,9 +1,19 @@
1
+ import {
2
+ normalizeFundBasicTableQuery,
3
+ normalizeKeywordTableQuery,
4
+ normalizeTickerQuery,
5
+ normalizeTradeCalendarQuery
6
+ } from '../agentQueryNormalize.js';
1
7
  import { resolveRuntimeConfig } from '../config/runtime-config.js';
2
8
  import { extractOpenclawRoutingFromRecord, deriveOpenclawRouting } from '../openclawRouting.js';
3
9
  import { BridgeRuntime } from '../runtime/BridgeRuntime.js';
4
10
  import { PluginDispatcher } from '../runtime/dispatchers/PluginDispatcher.js';
5
11
  import { ProcessLock } from '../runtime/lock/ProcessLock.js';
6
12
 
13
+ /** 与 BridgeRuntime FINANCE_TABLE_REQUEST_TIMEOUT_MS 一致 */
14
+ const GATEWAY_TABLE_REQUEST_TIMEOUT_MS = 90_000;
15
+ const FUND_CODE_PATTERN = /^\d{6}\.OF$/i;
16
+
7
17
  let activeRuntime = null;
8
18
 
9
19
  function installProcessShutdown(runtime) {
@@ -141,25 +151,105 @@ function mergeOpenclawChannelConfig(payload = {}, context = {}) {
141
151
  return merged;
142
152
  }
143
153
 
154
+ function assertWatchCreateSupported(payload = {}) {
155
+ const code = String(payload.product_code ?? payload.productCode ?? '')
156
+ .trim()
157
+ .toUpperCase();
158
+ const productType = String(payload.product_type ?? payload.productType ?? '')
159
+ .trim()
160
+ .toLowerCase();
161
+ if (productType === 'fund' || FUND_CODE_PATTERN.test(code)) {
162
+ throw new Error(
163
+ 'watch_create does not support fund strategies. Supported product_type: stock, hk_stock, index, crypto. For funds use watch_query_fund_estimates or watch_search_fund_basic.'
164
+ );
165
+ }
166
+ }
167
+
144
168
  function buildControlApi(startupPromise) {
145
169
  return {
146
170
  async queryTickerData(query = {}) {
147
171
  const runtime = await getReadyRuntime(startupPromise);
148
- const stockCode = query.stockCode || query.productCode || query.stock_code || '';
149
- const productType = query.productType || query.product_type || '';
150
- let market = query.market;
151
- if (market == null && String(productType).toLowerCase() === 'crypto') {
152
- market = '';
172
+ const payload = normalizeTickerQuery(query || {});
173
+ if (!payload.market || !payload.symbol) {
174
+ throw new Error(
175
+ 'queryTickerData requires market and symbol. If user gave a company/index name only, call watch_search_*_basic first to get ts_code, then map to market+symbol.'
176
+ );
153
177
  }
154
- return runtime.request('ticker.query', {
155
- stock_code: stockCode,
156
- market: market == null ? '' : String(market),
157
- product_type: productType
158
- });
178
+ return runtime.request('ticker.query', payload);
179
+ },
180
+ async queryFundEstimates(query = {}) {
181
+ const runtime = await getReadyRuntime(startupPromise);
182
+ let fundCodes = query.fund_codes ?? query.fundCodes;
183
+ if (fundCodes == null) {
184
+ throw new Error('queryFundEstimates requires fund_codes (or fundCodes): string or string[]');
185
+ }
186
+ if (typeof fundCodes === 'string') {
187
+ fundCodes = fundCodes.trim();
188
+ } else if (Array.isArray(fundCodes)) {
189
+ fundCodes = fundCodes.map((x) => String(x).trim()).filter(Boolean);
190
+ } else {
191
+ throw new Error('fund_codes must be a string or string[]');
192
+ }
193
+ return runtime.request('fund.estimates', { fund_codes: fundCodes });
194
+ },
195
+ async searchAStockBasic(query = {}) {
196
+ const runtime = await getReadyRuntime(startupPromise);
197
+ const q = normalizeKeywordTableQuery(query, 'searchAStockBasic');
198
+ return runtime.request(
199
+ 'finance.table',
200
+ { path: '/v1/a-stock/basic/search', query: q },
201
+ { requestTimeoutMs: GATEWAY_TABLE_REQUEST_TIMEOUT_MS }
202
+ );
203
+ },
204
+ async searchHkStockBasic(query = {}) {
205
+ const runtime = await getReadyRuntime(startupPromise);
206
+ const q = normalizeKeywordTableQuery(query, 'searchHkStockBasic');
207
+ return runtime.request(
208
+ 'finance.table',
209
+ { path: '/v1/hk-stock/basic/search', query: q },
210
+ { requestTimeoutMs: GATEWAY_TABLE_REQUEST_TIMEOUT_MS }
211
+ );
212
+ },
213
+ async searchIndexBasic(query = {}) {
214
+ const runtime = await getReadyRuntime(startupPromise);
215
+ const q = normalizeKeywordTableQuery(query, 'searchIndexBasic');
216
+ return runtime.request(
217
+ 'finance.table',
218
+ { path: '/v1/index/basic/search', query: q },
219
+ { requestTimeoutMs: GATEWAY_TABLE_REQUEST_TIMEOUT_MS }
220
+ );
221
+ },
222
+ async searchFundBasic(query = {}) {
223
+ const runtime = await getReadyRuntime(startupPromise);
224
+ const q = normalizeFundBasicTableQuery(query);
225
+ return runtime.request(
226
+ 'finance.table',
227
+ { path: '/v1/fund/basic', query: q },
228
+ { requestTimeoutMs: GATEWAY_TABLE_REQUEST_TIMEOUT_MS }
229
+ );
230
+ },
231
+ async queryFinNews(query = {}) {
232
+ const runtime = await getReadyRuntime(startupPromise);
233
+ const q = normalizeKeywordTableQuery(query, 'queryFinNews');
234
+ return runtime.request(
235
+ 'finance.table',
236
+ { path: '/v1/news', query: q },
237
+ { requestTimeoutMs: GATEWAY_TABLE_REQUEST_TIMEOUT_MS }
238
+ );
239
+ },
240
+ async queryTradeCalendar(query = {}) {
241
+ const runtime = await getReadyRuntime(startupPromise);
242
+ const q = normalizeTradeCalendarQuery(query);
243
+ return runtime.request(
244
+ 'finance.table',
245
+ { path: '/v1/trade-calendar', query: q },
246
+ { requestTimeoutMs: GATEWAY_TABLE_REQUEST_TIMEOUT_MS }
247
+ );
159
248
  },
160
249
  async createWatch(payload = {}, context = {}) {
161
250
  const runtime = await getReadyRuntime(startupPromise);
162
251
  const normalized = mergeOpenclawChannelConfig(payload, context);
252
+ assertWatchCreateSupported(normalized);
163
253
  return runtime.request('watch.create', normalized);
164
254
  },
165
255
  async sendNotification(input = {}) {
@@ -195,6 +285,7 @@ function buildControlApi(startupPromise) {
195
285
  const runtime = await getReadyRuntime(startupPromise);
196
286
  const payload = mapDemandToCreatePayload(demand);
197
287
  const normalized = mergeOpenclawChannelConfig(payload, context);
288
+ assertWatchCreateSupported(normalized);
198
289
  return runtime.request('watch.create', normalized);
199
290
  },
200
291
  async pauseWatch(strategyId) {
@@ -264,23 +355,185 @@ function tryRegisterTool(registerTool, name, description, parameters, handler) {
264
355
  function registerControlTools(api, controlApi) {
265
356
  const registerTool = api?.registerTool || api?.runtime?.registerTool;
266
357
 
358
+ const DESC_TICKER =
359
+ '【实时行情】当前价、涨跌幅等撮合侧快照。' +
360
+ 'When: 用户已明确标的代码/简称对应的代码时用;仅说公司名时先用 watch_search_*_basic 再调本工具。' +
361
+ 'Returns: type=ticker.query.result;success=true 时读 quote(英文键 last、name、pct_change 等)。' +
362
+ 'Do NOT: 场外基金估值勿用本工具(用 watch_query_fund_estimates)。market 可用 a|hk|crypto 或「A股」「港股」「加密」等别名。';
363
+
267
364
  tryRegisterTool(
268
365
  registerTool,
269
366
  'watch_query_ticker',
270
- 'Query ticker data',
367
+ DESC_TICKER,
271
368
  {
272
369
  type: 'object',
370
+ description:
371
+ '实时行情。必填 market + symbol;segment 仅 A/港股指数场景建议 index 或省略。插件会将「A股」等映射为 a。',
273
372
  additionalProperties: true,
274
373
  properties: {
275
- stock_code: { type: 'string' },
276
- product_code: { type: 'string' },
277
- product_type: { type: 'string' },
278
- market: { type: 'string' }
279
- }
374
+ market: {
375
+ type: 'string',
376
+ description:
377
+ '市场:a | hk | crypto;或中文别名 A股/港股/加密/数字货币(插件会归一化)。'
378
+ },
379
+ symbol: {
380
+ type: 'string',
381
+ description:
382
+ '标的代码:A 股如 600000.SH 或 600000;港股 00700;加密 BTCUSDT。名称请先用 search 工具解析。'
383
+ },
384
+ segment: {
385
+ type: 'string',
386
+ description: '可选。A/港股时 auto|stock|index;加密不传。'
387
+ }
388
+ },
389
+ required: ['market', 'symbol']
280
390
  },
281
391
  (args) => controlApi.queryTickerData(args || {})
282
392
  );
283
393
 
394
+ const DESC_FUND_EST =
395
+ '【基金当日估值】场外开放式基金估算净值/涨跌,非股票盘口。' +
396
+ 'When: 用户问「基金今天估值、估算涨跌」且代码形如 xxxxxx.OF。' +
397
+ 'Returns: fund.estimates.result;可能较慢(~90s)。' +
398
+ 'Do NOT: 股票/指数/加密行情用 watch_query_ticker。';
399
+
400
+ tryRegisterTool(
401
+ registerTool,
402
+ 'watch_query_fund_estimates',
403
+ DESC_FUND_EST,
404
+ {
405
+ type: 'object',
406
+ description: '单只或多只基金代码;fund_codes 与 fundCodes 等价。',
407
+ additionalProperties: true,
408
+ properties: {
409
+ fund_codes: {
410
+ description: '单只 "000006.OF" 或字符串数组;与 fundCodes 二选一即可',
411
+ oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }]
412
+ },
413
+ fundCodes: {
414
+ description: 'camelCase 别名,含义同 fund_codes',
415
+ oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }]
416
+ }
417
+ },
418
+ anyOf: [{ required: ['fund_codes'] }, { required: ['fundCodes'] }]
419
+ },
420
+ (args) => controlApi.queryFundEstimates(args || {})
421
+ );
422
+
423
+ const keywordSearchSchema = {
424
+ type: 'object',
425
+ description:
426
+ 'keyword 与 q 等价,至少填一个(插件会合并为网关 keyword)。可选 limit(条数)。返回 finance.table.result,行在 data[]。',
427
+ additionalProperties: true,
428
+ properties: {
429
+ keyword: { type: 'string', description: '搜索词:名称或代码片段,优先使用' },
430
+ q: { type: 'string', description: '与 keyword 二选一' },
431
+ limit: { type: 'number', description: '最大行数,默认由网关决定' }
432
+ },
433
+ anyOf: [{ required: ['keyword'] }, { required: ['q'] }]
434
+ };
435
+
436
+ tryRegisterTool(
437
+ registerTool,
438
+ 'watch_search_a_stock_basic',
439
+ '【A 股基础信息】按名称/代码找 ts_code、简称、行业等(日终/静态库表)。' +
440
+ 'When: 用户只说中文名或模糊代码、要映射到 600000.SH 再查 watch_query_ticker。' +
441
+ 'Returns: finance.table.result,data[] 每行含 ts_code 等字段。',
442
+ keywordSearchSchema,
443
+ (args) => controlApi.searchAStockBasic(args || {})
444
+ );
445
+
446
+ tryRegisterTool(
447
+ registerTool,
448
+ 'watch_search_hk_stock_basic',
449
+ '【港股基础信息】名称/拼音/代码检索港股代码。' +
450
+ 'When: 港股名称→代码后再 watch_query_ticker(market=hk)。' +
451
+ 'Returns: finance.table.result。',
452
+ keywordSearchSchema,
453
+ (args) => controlApi.searchHkStockBasic(args || {})
454
+ );
455
+
456
+ tryRegisterTool(
457
+ registerTool,
458
+ 'watch_search_index_basic',
459
+ '【指数基础信息】按简称/ts_code 找指数。' +
460
+ 'When: 用户说「沪深300」「恒生指数」等需解析为指数代码再 watch_query_ticker。' +
461
+ 'Returns: finance.table.result。',
462
+ keywordSearchSchema,
463
+ (args) => controlApi.searchIndexBasic(args || {})
464
+ );
465
+
466
+ tryRegisterTool(
467
+ registerTool,
468
+ 'watch_search_fund_basic',
469
+ '【基金档案】按 ts_code 或名称查基金元数据(非估值)。' +
470
+ 'When: 确认基金代码、或名称反查 000xxx.OF;查当日估算涨跌用 watch_query_fund_estimates。' +
471
+ 'Returns: finance.table.result。',
472
+ {
473
+ type: 'object',
474
+ description: 'ts_code 精确查优先;否则 keyword 或 q 模糊查。可选 limit。',
475
+ additionalProperties: true,
476
+ properties: {
477
+ ts_code: { type: 'string', description: '基金代码如 000006.OF' },
478
+ tsCode: { type: 'string', description: 'camelCase 别名' },
479
+ keyword: { type: 'string', description: '基金名称关键词' },
480
+ q: { type: 'string', description: '同 keyword' },
481
+ limit: { type: 'number' }
482
+ },
483
+ anyOf: [
484
+ { required: ['ts_code'] },
485
+ { required: ['tsCode'] },
486
+ { required: ['keyword'] },
487
+ { required: ['q'] }
488
+ ]
489
+ },
490
+ (args) => controlApi.searchFundBasic(args || {})
491
+ );
492
+
493
+ tryRegisterTool(
494
+ registerTool,
495
+ 'watch_fin_news',
496
+ '【财经快讯】标题/摘要类新闻,非实时逐笔。' +
497
+ 'When: 用户问「有什么新闻、快讯」并给出主题词。' +
498
+ 'Returns: finance.table.result;可选 pub_time_start/pub_time_end 收窄时间。' +
499
+ 'Do NOT: 与行情混淆;无关键词时先追问。',
500
+ {
501
+ ...keywordSearchSchema,
502
+ properties: {
503
+ ...keywordSearchSchema.properties,
504
+ pub_time_start: { type: 'string', description: '可选,发布时间下界,如 YYYY-MM-DD HH:MM:SS' },
505
+ pub_time_end: { type: 'string', description: '可选,发布时间上界' }
506
+ }
507
+ },
508
+ (args) => controlApi.queryFinNews(args || {})
509
+ );
510
+
511
+ tryRegisterTool(
512
+ registerTool,
513
+ 'watch_trade_calendar',
514
+ '【交易日历】某日是否开市、上一交易日等,无价量。' +
515
+ 'When: 「今天A股开不开盘」「五一休市吗」等;A 股常用 exchange=SSE(沪)或 SZSE(深),可各查一次或问用户。' +
516
+ 'Returns: finance.table.result,行内 is_open 等字段依网关。' +
517
+ 'Required: exchange + start_date + end_date(YYYY-MM-DD);可用 startDate/endDate。',
518
+ {
519
+ type: 'object',
520
+ description: '区间宜覆盖所问日期;单日则 start=end。',
521
+ additionalProperties: true,
522
+ properties: {
523
+ exchange: {
524
+ type: 'string',
525
+ description: '交易所代码,如 SSE(上交所)、SZSE(深交所),与网关文档一致'
526
+ },
527
+ start_date: { type: 'string', description: '区间起点 YYYY-MM-DD' },
528
+ end_date: { type: 'string', description: '区间终点 YYYY-MM-DD' },
529
+ startDate: { type: 'string', description: 'camelCase,同 start_date' },
530
+ endDate: { type: 'string', description: 'camelCase,同 end_date' }
531
+ },
532
+ required: ['exchange']
533
+ },
534
+ (args) => controlApi.queryTradeCalendar(args || {})
535
+ );
536
+
284
537
  tryRegisterTool(
285
538
  registerTool,
286
539
  'notify_sms',
@@ -332,7 +585,7 @@ function registerControlTools(api, controlApi) {
332
585
  tryRegisterTool(
333
586
  registerTool,
334
587
  'watch_create',
335
- 'Create watch strategy',
588
+ 'Create watch strategy for stock/hk_stock/index/crypto. Do NOT use for funds (.OF).',
336
589
  {
337
590
  type: 'object',
338
591
  additionalProperties: true,
@@ -343,7 +596,8 @@ function registerControlTools(api, controlApi) {
343
596
  operator_parameters: { type: 'object' },
344
597
  channels: { type: 'array', items: { type: 'string' } },
345
598
  channel_configs: { type: 'object' }
346
- }
599
+ },
600
+ required: ['product_code', 'product_type', 'operator_parameters']
347
601
  },
348
602
  (args, meta = {}) => controlApi.createWatch(args || {}, meta?.context || {})
349
603
  );
@@ -9,6 +9,12 @@ function makeRequestId() {
9
9
  return `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
10
10
  }
11
11
 
12
+ /** 与 OpenClawBridgeClient 一致:基金估值回源较慢 */
13
+ const FUND_ESTIMATES_REQUEST_TIMEOUT_MS = 120_000;
14
+
15
+ /** 网关表查询(新闻等)可能较慢 */
16
+ const FINANCE_TABLE_REQUEST_TIMEOUT_MS = 90_000;
17
+
12
18
  function stableStringify(value) {
13
19
  if (Array.isArray(value)) {
14
20
  return `[${value.map((item) => stableStringify(item)).join(',')}]`;
@@ -118,7 +124,7 @@ export class BridgeRuntime extends EventEmitter {
118
124
  await this.lock?.release().catch(() => {});
119
125
  }
120
126
 
121
- async request(type, payload = {}) {
127
+ async request(type, payload = {}, options = {}) {
122
128
  let requestId = makeRequestId();
123
129
  let normalizedPayload = payload || {};
124
130
  if (IDEMPOTENT_REQUEST_TYPES.has(type)) {
@@ -126,6 +132,13 @@ export class BridgeRuntime extends EventEmitter {
126
132
  requestId = resolved.requestId;
127
133
  normalizedPayload = resolved.payload;
128
134
  }
135
+ const timeoutMs =
136
+ options.requestTimeoutMs ??
137
+ (type === 'fund.estimates'
138
+ ? FUND_ESTIMATES_REQUEST_TIMEOUT_MS
139
+ : type === 'finance.table'
140
+ ? FINANCE_TABLE_REQUEST_TIMEOUT_MS
141
+ : this.requestTimeoutMs);
129
142
  const msg = { type, request_id: requestId, payload: normalizedPayload };
130
143
  const { promise, resolve, reject } = this._buildWaiter();
131
144
 
@@ -138,10 +151,10 @@ export class BridgeRuntime extends EventEmitter {
138
151
  reject(new Error(`request queue overflow (max=${this.maxQueueSize})`));
139
152
  return promise;
140
153
  }
141
- this.requestQueue.push({ msg, requestId, resolve, reject });
154
+ this.requestQueue.push({ msg, requestId, resolve, reject, timeoutMs });
142
155
  return promise;
143
156
  }
144
- this._sendWithTimeout({ msg, requestId, resolve, reject });
157
+ this._sendWithTimeout({ msg, requestId, resolve, reject, timeoutMs });
145
158
  return promise;
146
159
  }
147
160
 
@@ -155,11 +168,12 @@ export class BridgeRuntime extends EventEmitter {
155
168
  return { promise, resolve, reject };
156
169
  }
157
170
 
158
- _sendWithTimeout({ msg, requestId, resolve, reject }) {
171
+ _sendWithTimeout({ msg, requestId, resolve, reject, timeoutMs }) {
172
+ const ms = timeoutMs ?? this.requestTimeoutMs;
159
173
  const timer = setTimeout(() => {
160
174
  this.pending.delete(requestId);
161
175
  reject(new Error(`request timeout: ${msg.type} (${requestId})`));
162
- }, this.requestTimeoutMs);
176
+ }, ms);
163
177
 
164
178
  this.pending.set(requestId, { resolve, reject, timer });
165
179
  try {
@@ -1,56 +0,0 @@
1
- # 盯盘插件参考文档
2
-
3
- ## 环境配置
4
-
5
- ```bash
6
- # 基础配置(桥接地址固定为 wss://glanceup-pre.100credit.cn)
7
- export OPENCLAW_WS_TOKEN="<token>"
8
-
9
- # Token 申请:在网页上申请 OPENCLAW_WS_TOKEN
10
- ```
11
-
12
- ## 市场类型与产品代码
13
-
14
- | 市场 | productType | productCode 示例 | 行情频率 |
15
- |------|-------------|-----------------|----------|
16
- | A股个股 | stock | 000001 | 约每 3 秒 |
17
- | A股指数 | index | 000300 | 约每 3 秒 |
18
- | 港股 | hk_stock | 00700 | 延迟约 15 分钟 |
19
- | 比特币 | crypto | BTCUSDT | 约每 10 秒 |
20
-
21
- ## 条件表达式
22
-
23
- ### 可用变量
24
-
25
- - 通用: `price`, `volume`, `change_percent`
26
- - A股/港股额外: `turnover_rate`
27
- - 比特币: 不支持 `turnover_rate`
28
-
29
- ### 示例条件
30
-
31
- ```javascript
32
- // 单一条件
33
- 'price >= threshold'
34
-
35
- // 多条件
36
- 'price >= threshold and change_percent >= cp_threshold'
37
-
38
- // A股专用
39
- 'price >= threshold and turnover_rate >= tr_threshold'
40
- ```
41
-
42
- ## 产品代码速查
43
-
44
- ### A股主要指数
45
- - 沪深300: 000300
46
- - 上证指数: 000001
47
- - 创业板指: 399006
48
-
49
- ### 热门港股
50
- - 腾讯控股: 00700
51
- - 阿里巴巴: 09988
52
- - 美团: 03690
53
- - 比亚迪股份: 01211
54
-
55
- ### 主流加密货币
56
- - 比特币: BTCUSDT
@@ -1,69 +0,0 @@
1
- # 标的检索与行情查询
2
-
3
- ## 标的检索规则(必须遵循)
4
-
5
- 当不能直接确定 `product_code` / `product_type` 时,先查本地 CSV,再让用户确认。
6
-
7
- 数据文件(字段:`类型,代码,名称,完整代码,市场`):
8
- - `data/stock_a.csv` -> A 股个股(`productType=stock`)
9
- - `data/stock_hk.csv` -> 港股个股(`productType=hk_stock`)
10
- - `data/index_a.csv` -> A 股指数(`productType=index`)
11
- - `data/index_hk.csv` -> 港股指数(`productType=index`,查询常配 `market=HK`)
12
-
13
- ## 场景 1:用户只说名称
14
-
15
- - 在 CSV 里按名称模糊搜索。
16
- - 命中多条时,必须给出候选(代码 + 名称 + 市场)让用户确认。
17
- - 不可自行猜测后直接创建策略。
18
-
19
- ## 场景 2:用户不知道代码或市场
20
-
21
- - 用 `rg` 在 4 个 CSV 搜索名称或代码。
22
- - 映射规则:
23
- - A 股个股 -> `stock`
24
- - 港股个股 -> `hk_stock`
25
- - A 股指数 -> `index`
26
- - 港股指数 -> `index`(`market=HK`)
27
- - 结果不唯一时先追问。
28
-
29
- ## 推荐检索命令
30
-
31
- ```bash
32
- # 按名称模糊查找
33
- rg -n "平安银行|腾讯|沪深300|BTC" data/stock_a.csv data/stock_hk.csv data/index_a.csv
34
- rg -n "恒生科技指数|恒生指数|HSTECH|HSI" data/index_hk.csv
35
-
36
- # 按代码查找
37
- rg -n "000001|00700|399001" data/stock_a.csv data/stock_hk.csv data/index_a.csv
38
- rg -n "HSTECH|HSI|VHSI" data/index_hk.csv
39
-
40
- # 无 rg 时兜底
41
- grep -nE "平安银行|腾讯|沪深300|000001|00700|恒生科技指数|HSTECH" \
42
- data/stock_a.csv data/stock_hk.csv data/index_a.csv data/index_hk.csv
43
- ```
44
-
45
- ## 行情查询流程(`watch.query_ticker`)
46
-
47
- 说明:
48
- - 对外统一动作名是 `watch.query_ticker`。
49
- - 文档中的 `runtime.queryTickerData(...)` 仅用于说明宿主运行时内部调用形态。
50
-
51
- 1. 先确定标的代码、市场、`productType`。
52
- 2. 调用查询动作。
53
- 3. 成功后反馈价格/涨跌幅,失败则返回错误并让用户确认代码或市场。
54
-
55
- 示例:
56
-
57
- ```javascript
58
- await runtime.queryTickerData({
59
- stockCode: '00700',
60
- market: 'HK',
61
- productType: 'hk_stock'
62
- })
63
- ```
64
-
65
- 成功判定:
66
- - `code = "000000"` 或 `success = true`
67
-
68
- 失败处理:
69
- - 直接返回失败原因,不静默重试