openclaw-glance-plugin 0.1.22 → 0.1.28
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 +7 -3
- package/openclaw.plugin.json +1 -2
- package/package.json +2 -1
- package/skills/glance-watch/SKILL.md +117 -49
- package/skills/glance-watch/references/channels.md +61 -66
- package/skills/glance-watch/references/examples.md +145 -25
- package/skills/glance-watch/references/news-briefing.md +41 -0
- package/skills/glance-watch/references/quote-realtime.md +62 -0
- package/skills/glance-watch/references/symbol-search-and-calendar.md +74 -0
- package/skills/glance-watch/references/troubleshooting.md +5 -2
- package/skills/glance-watch/references/watch-contract.md +93 -57
- package/src/OpenClawBridgeClient.js +24 -4
- package/src/OpenClawPluginAdapter.js +76 -10
- package/src/agentQueryNormalize.js +84 -0
- package/src/plugin/index.js +272 -18
- package/src/runtime/BridgeRuntime.js +19 -5
- package/skills/glance-watch/references/markets.md +0 -56
- package/skills/glance-watch/references/query-and-symbol.md +0 -69
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 实时行情与基金估值
|
|
2
|
+
|
|
3
|
+
## 适用范围
|
|
4
|
+
|
|
5
|
+
- 股票/指数/加密实时行情:`watch_query_ticker`
|
|
6
|
+
- 基金当日估值:`watch_query_fund_estimates`
|
|
7
|
+
- 注意:基金不支持 `watch_create` 创建盯盘策略
|
|
8
|
+
|
|
9
|
+
## 1) 股票/指数/加密实时行情:`watch_query_ticker`
|
|
10
|
+
|
|
11
|
+
必填参数:
|
|
12
|
+
- `market`:`a` / `hk` / `crypto`(支持中文别名)
|
|
13
|
+
- `symbol`
|
|
14
|
+
|
|
15
|
+
可选参数:
|
|
16
|
+
- `segment`:`auto` / `stock` / `index`
|
|
17
|
+
|
|
18
|
+
成功判定:
|
|
19
|
+
- `success === true`
|
|
20
|
+
- `http_status === 200`
|
|
21
|
+
- 行情在 `quote`(英文键,如 `last`、`name`、`pct_change`)
|
|
22
|
+
|
|
23
|
+
失败处理:
|
|
24
|
+
- 读取 `error` 与 `http_status` 直接反馈,不静默重试
|
|
25
|
+
|
|
26
|
+
示例:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
await watch_query_ticker({ market: 'hk', symbol: '00700', segment: 'stock' })
|
|
30
|
+
await watch_query_ticker({ market: 'A股', symbol: '600000.SH' })
|
|
31
|
+
await watch_query_ticker({ market: 'crypto', symbol: 'BTCUSDT' })
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 2) 基金估值:`watch_query_fund_estimates`
|
|
35
|
+
|
|
36
|
+
参数(二选一):
|
|
37
|
+
- `fund_codes`
|
|
38
|
+
- `fundCodes`
|
|
39
|
+
|
|
40
|
+
值格式:
|
|
41
|
+
- 单只:`"000006.OF"`
|
|
42
|
+
- 多只:`["000006.OF", "110011.OF"]`
|
|
43
|
+
|
|
44
|
+
成功判定:
|
|
45
|
+
- `success === true`
|
|
46
|
+
- `http_status === 200`
|
|
47
|
+
- `data` 为基金代码映射
|
|
48
|
+
|
|
49
|
+
失败处理:
|
|
50
|
+
- 读取 `error` 与 `http_status` 反馈用户
|
|
51
|
+
|
|
52
|
+
示例:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
await watch_query_fund_estimates({ fund_codes: '000006.OF' })
|
|
56
|
+
await watch_query_fund_estimates({ fund_codes: ['000006.OF', '110011.OF'] })
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 3) 硬边界
|
|
60
|
+
|
|
61
|
+
- 不要用 `watch_query_ticker` 查基金当日估值
|
|
62
|
+
- 不要对基金调用 `watch_create`
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 标的检索与交易日查询
|
|
2
|
+
|
|
3
|
+
## 适用范围
|
|
4
|
+
|
|
5
|
+
- 名称/模糊输入 -> 本地 CSV 匹配或代码:`watch_search_*_basic`
|
|
6
|
+
- 是否开市/交易日:`watch_trade_calendar`
|
|
7
|
+
|
|
8
|
+
## 1) 标的检索顺序(必须遵循)
|
|
9
|
+
|
|
10
|
+
1. 先查本地 CSV(`skills/glance-watch/data/*.csv`)
|
|
11
|
+
2. 本地未命中,再调用 `watch_search_*_basic`
|
|
12
|
+
3. 多候选时必须让用户确认后再创建策略
|
|
13
|
+
|
|
14
|
+
CSV 映射:
|
|
15
|
+
- `stock_a.csv` -> `product_type=stock`
|
|
16
|
+
- `stock_hk.csv` -> `product_type=hk_stock`
|
|
17
|
+
- `index_a.csv` -> `product_type=index`
|
|
18
|
+
- `index_hk.csv` -> `product_type=index`
|
|
19
|
+
- 基金 `xxxxxx.OF` -> 仅估值/基础信息,不创建策略
|
|
20
|
+
|
|
21
|
+
### 本地 CSV 匹配算法(执行约束)
|
|
22
|
+
|
|
23
|
+
- 可使用 `rg`/`grep` 检索,不要求固定命令模板。
|
|
24
|
+
- 匹配优先级:
|
|
25
|
+
1. `完整代码` 精确匹配(如 `600000.SH`、`00700.HK`)
|
|
26
|
+
2. `代码` 精确匹配(如 `600000`、`00700`)
|
|
27
|
+
3. `名称` 精确匹配
|
|
28
|
+
4. `名称` 模糊匹配
|
|
29
|
+
- 命中后优先使用 `完整代码` + `市场` 来确定后续 `market/symbol`,避免仅用短代码。
|
|
30
|
+
- 若出现重名/重码或返回多条候选:必须先向用户确认具体标的,再执行 `watch_query_ticker` 或 `watch_create`。
|
|
31
|
+
|
|
32
|
+
## 2) 网关基础信息检索工具
|
|
33
|
+
|
|
34
|
+
- `watch_search_a_stock_basic`(A股)
|
|
35
|
+
- `watch_search_hk_stock_basic`(港股)
|
|
36
|
+
- `watch_search_index_basic`(指数)
|
|
37
|
+
- `watch_search_fund_basic`(基金基础信息)
|
|
38
|
+
|
|
39
|
+
统一规则:
|
|
40
|
+
- 名称检索至少给 `keyword` 或 `q`
|
|
41
|
+
- 成功返回 `finance.table.result`,结果在 `data[]`
|
|
42
|
+
|
|
43
|
+
示例:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
await watch_search_a_stock_basic({ keyword: '平安银行', limit: 5 })
|
|
47
|
+
await watch_search_hk_stock_basic({ q: '腾讯' })
|
|
48
|
+
await watch_search_index_basic({ keyword: '沪深300' })
|
|
49
|
+
await watch_search_fund_basic({ ts_code: '000006.OF' })
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 3) 交易日查询:`watch_trade_calendar`
|
|
53
|
+
|
|
54
|
+
参数:
|
|
55
|
+
- `exchange`(A股常用 `SSE` 或 `SZSE`)
|
|
56
|
+
- `start_date`
|
|
57
|
+
- `end_date`
|
|
58
|
+
|
|
59
|
+
可兼容:
|
|
60
|
+
- `startDate` / `endDate`(会归一化)
|
|
61
|
+
|
|
62
|
+
成功判定:
|
|
63
|
+
- `success === true`
|
|
64
|
+
- `data[]` 可读 `is_open` 等字段
|
|
65
|
+
|
|
66
|
+
示例:
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
await watch_trade_calendar({
|
|
70
|
+
exchange: 'SSE',
|
|
71
|
+
start_date: '2026-04-10',
|
|
72
|
+
end_date: '2026-04-10'
|
|
73
|
+
})
|
|
74
|
+
```
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
- 重试必须使用同一组 payload(字段和值不变)。
|
|
7
7
|
- `request_id` 由插件运行时自动生成并在同 payload 上复用。
|
|
8
8
|
|
|
9
|
-
## 创建策略失败(`
|
|
9
|
+
## 创建策略失败(`watch_create`)
|
|
10
10
|
|
|
11
11
|
处理规则:
|
|
12
12
|
- 明确返回失败原因,不静默重试。
|
|
13
13
|
- 若报“未注册的算子类型”,将 `operator_type` 修正为 `rule` 后重试。
|
|
14
|
+
- 若报 `UNSUPPORTED_PRODUCT_TYPE`,说明命中了基金边界(如 `fund` 或 `000006.OF`):
|
|
15
|
+
- 不再重试 `watch_create`
|
|
16
|
+
- 改用 `watch_query_fund_estimates` 或 `watch_search_fund_basic`
|
|
14
17
|
|
|
15
|
-
## 通知失败(`
|
|
18
|
+
## 通知失败(`notify_*`)
|
|
16
19
|
|
|
17
20
|
处理规则:
|
|
18
21
|
- 优先返回 `code/error/hint`。
|
|
@@ -1,30 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 盯盘策略(watch_*)
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 适用工具
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- `
|
|
7
|
-
- `
|
|
8
|
-
- `
|
|
5
|
+
- `watch_create`
|
|
6
|
+
- `watch_list`
|
|
7
|
+
- `watch_pause`
|
|
8
|
+
- `watch_activate`
|
|
9
|
+
- `watch_remove`
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
- `code = "000000"` 或 `success = true`
|
|
11
|
+
## 0. 产品边界
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
- 支持盯盘:`stock` / `hk_stock` / `index` / `crypto`
|
|
14
|
+
- 不支持盯盘:基金(`product_type=fund` 或代码形如 `xxxxxx.OF`)
|
|
15
|
+
- 基金相关请改用:`watch_query_fund_estimates` / `watch_search_fund_basic`
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## 1. `watch_create`
|
|
18
18
|
|
|
19
19
|
最小参数:
|
|
20
20
|
- `product_code`
|
|
21
21
|
- `product_type`
|
|
22
22
|
- `operator_type`(固定 `rule`)
|
|
23
|
-
- `operator_parameters
|
|
23
|
+
- `operator_parameters.condition`
|
|
24
|
+
- `operator_parameters.variables`
|
|
24
25
|
|
|
25
26
|
建议参数:
|
|
26
|
-
- `channels
|
|
27
|
-
- `channel_configs
|
|
27
|
+
- `channels`(至少 1 个渠道;是否包含 `openclaw` 见下方渠道规则)
|
|
28
|
+
- `channel_configs`
|
|
29
|
+
|
|
30
|
+
渠道规则:
|
|
31
|
+
- 用户明确“仅/只用某几个渠道”时:严格按用户指定,可不含 `openclaw`。
|
|
32
|
+
- 用户只说“用某个渠道/某几个渠道”但未强调“仅限”时:默认补 `openclaw`。
|
|
33
|
+
- 不得擅自附加除 `openclaw` 之外的其他渠道。
|
|
34
|
+
|
|
35
|
+
OpenClaw 路由约束(当 `channels` 包含 `openclaw`):
|
|
36
|
+
- `channel_configs.openclaw` 必须能定位当前会话。
|
|
37
|
+
- 常用字段:`channel`(或 `source_channel`)、`account_id`、`session_key`、`conversation_id`(或 `chat_id`)。
|
|
38
|
+
- 禁止传空对象 `openclaw: {}` 充当已配置路由。
|
|
39
|
+
- 若宿主提供 `context` 路由信息,优先使用宿主信息。
|
|
40
|
+
|
|
41
|
+
若 `channels` 包含 `call/sms/email/dingtalk`:
|
|
42
|
+
- 调用前必须读取联系人 CSV 并补齐参数
|
|
43
|
+
- 不可省略对应渠道必填配置
|
|
28
44
|
|
|
29
45
|
固定结构(字段名不可改):
|
|
30
46
|
|
|
@@ -63,70 +79,90 @@
|
|
|
63
79
|
}
|
|
64
80
|
```
|
|
65
81
|
|
|
82
|
+
成功判定:
|
|
83
|
+
- `success === true`
|
|
84
|
+
|
|
85
|
+
失败处理:
|
|
86
|
+
- 返回失败原因,不静默重试
|
|
87
|
+
- 网络波动可重试,但 payload 字段和值保持一致
|
|
88
|
+
- 不手动传 `request_id`
|
|
89
|
+
|
|
66
90
|
禁止项:
|
|
67
91
|
- `operator_type` 非 `rule`
|
|
68
|
-
-
|
|
69
|
-
- `channel_configs
|
|
70
|
-
-
|
|
92
|
+
- 顶层放 `condition`
|
|
93
|
+
- `channel_configs.*` 传 JSON 字符串
|
|
94
|
+
- 用户明确“仅/只用某几个渠道”时仍强行附加渠道
|
|
95
|
+
- 基金标的调用 `watch_create`
|
|
71
96
|
|
|
72
|
-
|
|
73
|
-
- `success = true`
|
|
97
|
+
## 联系人 CSV(创建策略场景必遵守)
|
|
74
98
|
|
|
75
|
-
|
|
76
|
-
- 明确返回失败原因,不静默重试
|
|
77
|
-
- 超时/网络波动重试时,使用同一 payload(字段和值不变)
|
|
78
|
-
- `request_id` 由插件运行时自动生成并复用,大模型不手动传
|
|
99
|
+
联系人真源:`~/.openclaw/workspace/memory/watch-notify-contacts.csv`
|
|
79
100
|
|
|
80
|
-
|
|
101
|
+
规则:
|
|
102
|
+
1. `watch_create` 涉及 `call/sms/email/dingtalk` 时,调用前必须先查 CSV。
|
|
103
|
+
2. 补值优先级:用户本轮输入 > CSV 历史值 > 追问。
|
|
104
|
+
3. 调用成功后,如联系方式有新增或变化,必须回写 CSV。
|
|
105
|
+
4. 只认该 CSV,不读取其他联系人文件。
|
|
81
106
|
|
|
82
|
-
|
|
83
|
-
- `strategyId`(或 `strategy_id`)
|
|
107
|
+
建议表头:
|
|
84
108
|
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
```text
|
|
110
|
+
channel,sender_id,sender_name,phone,email,dingtalk_cas_id,customer_name,updated_at,notes
|
|
111
|
+
```
|
|
87
112
|
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
查询示例:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
rg -n '^sms,jinguo\.xie,' ~/.openclaw/workspace/memory/watch-notify-contacts.csv
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
回写要求:
|
|
120
|
+
|
|
121
|
+
- 已有同一 `(channel, sender_id)`:更新该行,不重复追加
|
|
122
|
+
- 不存在:按表头顺序追加
|
|
123
|
+
- `updated_at`:写 ISO-8601 时间
|
|
90
124
|
|
|
91
|
-
##
|
|
125
|
+
## 2. `watch_list`
|
|
92
126
|
|
|
93
127
|
可选参数:
|
|
94
|
-
- `status
|
|
128
|
+
- `status`:`active/paused/completed/failed/expired`
|
|
95
129
|
- `product_code`(或 `productCode`)
|
|
96
130
|
|
|
97
131
|
成功判定:
|
|
98
|
-
- `success
|
|
99
|
-
- `data.total`
|
|
100
|
-
- `data.strategies` 为策略列表
|
|
132
|
+
- `success === true`
|
|
133
|
+
- `data.total` 与 `data.strategies` 可读
|
|
101
134
|
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
- 空结果明确告知“当前条件下没有策略”
|
|
135
|
+
空结果文案:
|
|
136
|
+
- 当 `data.total === 0` 时,明确告知“当前条件下没有策略”。
|
|
105
137
|
|
|
106
138
|
安全约束:
|
|
107
|
-
-
|
|
108
|
-
-
|
|
139
|
+
- 仅查询当前连接用户策略
|
|
140
|
+
- 禁止通过 `user_id/use_id` 越权查询
|
|
141
|
+
|
|
142
|
+
## 3. `watch_pause` / `watch_activate` / `watch_remove`
|
|
143
|
+
|
|
144
|
+
参数:
|
|
145
|
+
- `strategy_id`(或 `strategyId`)
|
|
146
|
+
|
|
147
|
+
成功判定:
|
|
148
|
+
- `success === true`
|
|
109
149
|
|
|
110
|
-
##
|
|
150
|
+
## 调用前最终检查(`watch_create`)
|
|
111
151
|
|
|
112
|
-
1.
|
|
113
|
-
2. `operator_type`
|
|
114
|
-
3. `operator_parameters.condition` 与 `variables` 同时存在
|
|
115
|
-
4. `channels`
|
|
116
|
-
5.
|
|
117
|
-
6.
|
|
152
|
+
1. 字段名使用 snake_case:`product_code/product_type/operator_type/operator_parameters/channels/channel_configs`
|
|
153
|
+
2. `operator_type` 固定为 `rule`
|
|
154
|
+
3. `operator_parameters.condition` 与 `operator_parameters.variables` 同时存在
|
|
155
|
+
4. `channels` 至少一个,且与 `channel_configs` 对应
|
|
156
|
+
5. 渠道配置必须是对象,不能是 JSON 字符串
|
|
157
|
+
6. 不手动传 `request_id`
|
|
118
158
|
|
|
119
|
-
## 买卖意图与条件方向
|
|
159
|
+
## 4. 买卖意图与条件方向
|
|
120
160
|
|
|
121
161
|
| 用户意图 | 条件方向 |
|
|
122
|
-
|
|
162
|
+
|---|---|
|
|
123
163
|
| 买入(逢低) | `price <= threshold` |
|
|
124
164
|
| 卖出(止盈/止损) | `price >= threshold` |
|
|
125
165
|
|
|
126
|
-
|
|
127
|
-
1.
|
|
128
|
-
2. 用户只说“到了
|
|
129
|
-
3. 常见映射:
|
|
130
|
-
- `涨到/涨过/突破/冲到` -> `price >= threshold`
|
|
131
|
-
- `跌到/跌破/回调到/回到` -> `price <= threshold`
|
|
132
|
-
- `到了/到达/价格到` -> 方向不明确,需追问
|
|
166
|
+
规则:
|
|
167
|
+
1. 用户明确“涨到/跌到”时按方向生成。
|
|
168
|
+
2. 用户只说“到了XX提醒我”时先追问买卖方向。
|
|
@@ -3,6 +3,12 @@ import WebSocket from 'ws';
|
|
|
3
3
|
|
|
4
4
|
const DEFAULT_BASE_WS_URL = 'wss://glanceup-pre.100credit.cn';
|
|
5
5
|
|
|
6
|
+
/** 基金估值网关可能较慢,应大于 bridge 侧 OPENCLAW_FUND_ESTIMATES_TIMEOUT_SECONDS(默认 90s) */
|
|
7
|
+
const FUND_ESTIMATES_REQUEST_TIMEOUT_MS = 120_000;
|
|
8
|
+
|
|
9
|
+
/** 白名单表接口(含 fin_news)可能较慢 */
|
|
10
|
+
const FINANCE_TABLE_REQUEST_TIMEOUT_MS = 90_000;
|
|
11
|
+
|
|
6
12
|
function sleep(ms) {
|
|
7
13
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
14
|
}
|
|
@@ -180,6 +186,18 @@ export class OpenClawBridgeClient extends EventEmitter {
|
|
|
180
186
|
return this._request('ticker.query', payload || {});
|
|
181
187
|
}
|
|
182
188
|
|
|
189
|
+
async queryFundEstimates(payload) {
|
|
190
|
+
return this._request('fund.estimates', payload || {}, {
|
|
191
|
+
requestTimeoutMs: FUND_ESTIMATES_REQUEST_TIMEOUT_MS
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async queryFinanceTable(payload) {
|
|
196
|
+
return this._request('finance.table', payload || {}, {
|
|
197
|
+
requestTimeoutMs: FINANCE_TABLE_REQUEST_TIMEOUT_MS
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
183
201
|
async waitUntilConnected(timeoutMs = this.waitConnectTimeoutMs) {
|
|
184
202
|
if (this.connected && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
185
203
|
return true;
|
|
@@ -216,6 +234,7 @@ export class OpenClawBridgeClient extends EventEmitter {
|
|
|
216
234
|
if (!requestId) {
|
|
217
235
|
requestId = makeRequestId();
|
|
218
236
|
}
|
|
237
|
+
const timeoutMs = options.requestTimeoutMs ?? this.requestTimeoutMs;
|
|
219
238
|
const msg = { type, request_id: requestId, payload: normalizedPayload };
|
|
220
239
|
const { promise, resolve, reject } = this._buildWaiter(type, requestId);
|
|
221
240
|
|
|
@@ -228,12 +247,12 @@ export class OpenClawBridgeClient extends EventEmitter {
|
|
|
228
247
|
reject(new Error(`request queue overflow (max=${this.maxQueueSize})`));
|
|
229
248
|
return promise;
|
|
230
249
|
}
|
|
231
|
-
this.requestQueue.push({ msg, requestId, type, resolve, reject });
|
|
250
|
+
this.requestQueue.push({ msg, requestId, type, resolve, reject, timeoutMs });
|
|
232
251
|
this.emit('queued', { type, requestId, queueSize: this.requestQueue.length });
|
|
233
252
|
return promise;
|
|
234
253
|
}
|
|
235
254
|
|
|
236
|
-
this._sendWithTimeout({ msg, requestId, type, resolve, reject });
|
|
255
|
+
this._sendWithTimeout({ msg, requestId, type, resolve, reject, timeoutMs });
|
|
237
256
|
return promise;
|
|
238
257
|
}
|
|
239
258
|
|
|
@@ -247,11 +266,12 @@ export class OpenClawBridgeClient extends EventEmitter {
|
|
|
247
266
|
return { promise, resolve, reject, type, requestId };
|
|
248
267
|
}
|
|
249
268
|
|
|
250
|
-
_sendWithTimeout({ msg, requestId, type, resolve, reject }) {
|
|
269
|
+
_sendWithTimeout({ msg, requestId, type, resolve, reject, timeoutMs }) {
|
|
270
|
+
const ms = timeoutMs ?? this.requestTimeoutMs;
|
|
251
271
|
const timer = setTimeout(() => {
|
|
252
272
|
this.pending.delete(requestId);
|
|
253
273
|
reject(new Error(`request timeout: ${type} (${requestId})`));
|
|
254
|
-
},
|
|
274
|
+
}, ms);
|
|
255
275
|
|
|
256
276
|
this.pending.set(requestId, { resolve, reject, timer, type });
|
|
257
277
|
this.ws.send(JSON.stringify(msg));
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeFundBasicTableQuery,
|
|
3
|
+
normalizeKeywordTableQuery,
|
|
4
|
+
normalizeTickerQuery,
|
|
5
|
+
normalizeTradeCalendarQuery
|
|
6
|
+
} from './agentQueryNormalize.js';
|
|
1
7
|
import { OpenClawBridgeClient } from './OpenClawBridgeClient.js';
|
|
2
8
|
import { extractOpenclawRoutingFromRecord } from './openclawRouting.js';
|
|
3
9
|
|
|
@@ -134,21 +140,81 @@ export class OpenClawPluginAdapter {
|
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
/**
|
|
137
|
-
*
|
|
143
|
+
* 查询标的实时行情;参数为 market / symbol / segment,与网关 quote 接口一致;应答为 ticker.query.result。
|
|
138
144
|
*/
|
|
139
145
|
async queryTickerData(query) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
const payload = normalizeTickerQuery(query || {});
|
|
147
|
+
if (!payload.market || !payload.symbol) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
'queryTickerData requires market and symbol. If user gave a name only, use search*Basic first.'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return this.client.queryTickerData(payload);
|
|
153
|
+
}
|
|
143
154
|
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
/**
|
|
156
|
+
* 基金实时估值;payload.fund_codes 与网关 POST /v1/realtime/fund/estimates 一致;应答为 fund.estimates.result。
|
|
157
|
+
*/
|
|
158
|
+
async queryFundEstimates(query) {
|
|
159
|
+
let fundCodes = query?.fund_codes ?? query?.fundCodes;
|
|
160
|
+
if (fundCodes == null) {
|
|
161
|
+
throw new Error('queryFundEstimates requires fund_codes (or fundCodes)');
|
|
162
|
+
}
|
|
163
|
+
if (typeof fundCodes === 'string') {
|
|
164
|
+
fundCodes = fundCodes.trim();
|
|
165
|
+
} else if (Array.isArray(fundCodes)) {
|
|
166
|
+
fundCodes = fundCodes.map((x) => String(x).trim()).filter(Boolean);
|
|
167
|
+
} else {
|
|
168
|
+
throw new Error('fund_codes must be a string or string[]');
|
|
146
169
|
}
|
|
170
|
+
return this.client.queryFundEstimates({ fund_codes: fundCodes });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async searchAStockBasic(query) {
|
|
174
|
+
const q = normalizeKeywordTableQuery(query, 'searchAStockBasic');
|
|
175
|
+
return this.client.queryFinanceTable({
|
|
176
|
+
path: '/v1/a-stock/basic/search',
|
|
177
|
+
query: q
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async searchHkStockBasic(query) {
|
|
182
|
+
const q = normalizeKeywordTableQuery(query, 'searchHkStockBasic');
|
|
183
|
+
return this.client.queryFinanceTable({
|
|
184
|
+
path: '/v1/hk-stock/basic/search',
|
|
185
|
+
query: q
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async searchIndexBasic(query) {
|
|
190
|
+
const q = normalizeKeywordTableQuery(query, 'searchIndexBasic');
|
|
191
|
+
return this.client.queryFinanceTable({
|
|
192
|
+
path: '/v1/index/basic/search',
|
|
193
|
+
query: q
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async searchFundBasic(query) {
|
|
198
|
+
const q = normalizeFundBasicTableQuery(query);
|
|
199
|
+
return this.client.queryFinanceTable({
|
|
200
|
+
path: '/v1/fund/basic',
|
|
201
|
+
query: q
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async queryFinNews(query) {
|
|
206
|
+
const q = normalizeKeywordTableQuery(query, 'queryFinNews');
|
|
207
|
+
return this.client.queryFinanceTable({
|
|
208
|
+
path: '/v1/news',
|
|
209
|
+
query: q
|
|
210
|
+
});
|
|
211
|
+
}
|
|
147
212
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
213
|
+
async queryTradeCalendar(query) {
|
|
214
|
+
const q = normalizeTradeCalendarQuery(query);
|
|
215
|
+
return this.client.queryFinanceTable({
|
|
216
|
+
path: '/v1/trade-calendar',
|
|
217
|
+
query: q
|
|
152
218
|
});
|
|
153
219
|
}
|
|
154
220
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw / Agent 侧查询参数归一化,与 buildControlApi 行为一致。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function trimQuery(v) {
|
|
6
|
+
if (v == null) return '';
|
|
7
|
+
return String(v).trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** 中文/别名 → 网关 market;symbol 去空格 */
|
|
11
|
+
export function normalizeTickerQuery(query = {}) {
|
|
12
|
+
const raw = trimQuery(query.market);
|
|
13
|
+
const lower = raw.toLowerCase();
|
|
14
|
+
const cn = {
|
|
15
|
+
A股: 'a',
|
|
16
|
+
a股: 'a',
|
|
17
|
+
沪深: 'a',
|
|
18
|
+
上证: 'a',
|
|
19
|
+
深市: 'a',
|
|
20
|
+
港股: 'hk',
|
|
21
|
+
港交所: 'hk',
|
|
22
|
+
加密: 'crypto',
|
|
23
|
+
数字货币: 'crypto',
|
|
24
|
+
虚拟货币: 'crypto',
|
|
25
|
+
比特币: 'crypto'
|
|
26
|
+
};
|
|
27
|
+
let market = cn[raw] || lower;
|
|
28
|
+
if (market === 'hongkong' || market === 'hong kong') market = 'hk';
|
|
29
|
+
if (market === 'btc' || market === 'crypto-currency') market = 'crypto';
|
|
30
|
+
const symbol = trimQuery(query.symbol);
|
|
31
|
+
const out = { market, symbol };
|
|
32
|
+
const seg = query.segment != null ? trimQuery(query.segment) : '';
|
|
33
|
+
if (seg) out.segment = seg;
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizeKeywordTableQuery(query = {}, label) {
|
|
38
|
+
const q = { ...(query || {}) };
|
|
39
|
+
const k = trimQuery(q.keyword);
|
|
40
|
+
const qq = trimQuery(q.q);
|
|
41
|
+
const text = k || qq;
|
|
42
|
+
if (!text) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`${label} requires keyword or q (search text). If user only gave a name, put it in keyword.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
q.keyword = text;
|
|
48
|
+
delete q.q;
|
|
49
|
+
return q;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function normalizeFundBasicTableQuery(query = {}) {
|
|
53
|
+
const q = { ...(query || {}) };
|
|
54
|
+
const tc = trimQuery(q.ts_code ?? q.tsCode);
|
|
55
|
+
if (tc) {
|
|
56
|
+
const out = { ...q, ts_code: tc };
|
|
57
|
+
delete out.tsCode;
|
|
58
|
+
delete out.keyword;
|
|
59
|
+
delete out.q;
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
return normalizeKeywordTableQuery(q, 'searchFundBasic');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function normalizeTradeCalendarQuery(query = {}) {
|
|
66
|
+
const q = { ...(query || {}) };
|
|
67
|
+
const start = trimQuery(q.start_date ?? q.startDate);
|
|
68
|
+
const end = trimQuery(q.end_date ?? q.endDate);
|
|
69
|
+
const ex = trimQuery(q.exchange);
|
|
70
|
+
if (!ex) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'queryTradeCalendar requires exchange (SSE=上交所, SZSE=深交所, 等,与网关一致)'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (!start || !end) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
'queryTradeCalendar requires start_date and end_date as YYYY-MM-DD (or startDate/endDate)'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const out = { ...q, exchange: ex, start_date: start, end_date: end };
|
|
81
|
+
delete out.startDate;
|
|
82
|
+
delete out.endDate;
|
|
83
|
+
return out;
|
|
84
|
+
}
|