echopai 2.2.0 → 2.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.
- package/dist/_generated/commands.js +96 -0
- package/dist/_generated/help.js +107 -7
- package/dist/_generated/operations.js +883 -27
- package/dist/bin.js +53 -8
- package/dist/runtime/invoker.js +4 -0
- package/dist/runtime/update_check.js +120 -0
- package/dist/runtime/update_worker.js +63 -0
- package/dist/runtime/verb_cmd.js +2 -0
- package/dist/tools/upgrade.js +103 -0
- package/dist/tools/welcome.js +42 -7
- package/dist/verbs/announcements.js +195 -0
- package/dist/verbs/concepts.js +393 -0
- package/dist/verbs/digest.js +9 -2
- package/dist/verbs/index.js +37 -6
- package/dist/verbs/limit_up.js +156 -0
- package/dist/verbs/lookup.js +1 -1
- package/dist/verbs/market.js +185 -0
- package/dist/verbs/news.js +17 -3
- package/dist/verbs/quote.js +1 -1
- package/dist/verbs/sentiment.js +191 -6
- package/dist/verbs/views.js +5 -3
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/verbs/sentiment.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `echopai sentiment
|
|
2
|
+
* `echopai sentiment ...` + MCP tools `sentiment` (overview alias, BC) /
|
|
3
|
+
* `sentiment_overview` / `sentiment_breadth` / `sentiment_turnover` /
|
|
4
|
+
* `sentiment_pct_distribution`.
|
|
5
|
+
*
|
|
6
|
+
* 四个子端点全部支持 `--scope` 切换股票池;`overview` / `breadth` /
|
|
7
|
+
* `turnover` 进一步支持 `--date YYYY-MM-DD` 取历史任意交易日的当日聚合
|
|
8
|
+
* (盘中实时为 Redis cache,历史走 ClickHouse `market_breadth_intraday` /
|
|
9
|
+
* `market_turnover_intraday`)。`turnover` 还支持 `--days 1-5` 拉同分钟同比。
|
|
10
|
+
*
|
|
11
|
+
* 顶层 `echopai sentiment` 无子命令时仍走 overview(保持 v2.2.0 兼容)。
|
|
3
12
|
*/
|
|
4
13
|
import { Command, Option } from "commander";
|
|
5
14
|
import { z } from "zod";
|
|
@@ -14,33 +23,209 @@ const SCOPE_VALUES = [
|
|
|
14
23
|
"chinext",
|
|
15
24
|
"bse",
|
|
16
25
|
];
|
|
26
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
27
|
+
function clampInt(raw, min, max, fallback) {
|
|
28
|
+
const n = Math.floor(Number(raw));
|
|
29
|
+
if (!Number.isFinite(n))
|
|
30
|
+
return fallback;
|
|
31
|
+
if (n < min)
|
|
32
|
+
return min;
|
|
33
|
+
if (n > max)
|
|
34
|
+
return max;
|
|
35
|
+
return n;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Legacy `sentiment` spec — kept as alias of overview for v2.2.0 backward
|
|
39
|
+
* compatibility (MCP clients that registered `sentiment` keep working).
|
|
40
|
+
*/
|
|
17
41
|
export const sentimentSpec = {
|
|
18
42
|
name: "sentiment",
|
|
19
|
-
description: "Aggregate market sentiment indicators (limit-up/down counts, breadth, divergence index, top movers). Defaults to all_a_ex_st (all A-shares ex-ST).",
|
|
43
|
+
description: "Aggregate market sentiment indicators (limit-up/down counts, breadth, divergence index, top movers). Defaults to all_a_ex_st (all A-shares ex-ST). Pass `at_date` (YYYY-MM-DD) for any historical trading day; omit for today's latest snapshot. Backward-compat alias of `sentiment_overview` — prefer the latter in new agent code.",
|
|
20
44
|
inputSchema: {
|
|
21
45
|
scope: z
|
|
22
46
|
.enum(SCOPE_VALUES)
|
|
23
47
|
.default("all_a_ex_st")
|
|
24
48
|
.describe("Universe filter"),
|
|
49
|
+
at_date: z
|
|
50
|
+
.string()
|
|
51
|
+
.regex(DATE_RE)
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Trade date YYYY-MM-DD; omit for today's realtime snapshot"),
|
|
25
54
|
},
|
|
26
55
|
handler: async (args, ctx) => {
|
|
27
56
|
const op = OPERATIONS["sentiment.overview"];
|
|
28
57
|
if (!op)
|
|
29
58
|
throw new Error("sentiment.overview op missing");
|
|
30
|
-
|
|
59
|
+
const callArgs = { scope: args.scope };
|
|
60
|
+
if (args.at_date)
|
|
61
|
+
callArgs.trade_date = args.at_date;
|
|
62
|
+
return callOp(op, callArgs, ctx);
|
|
31
63
|
},
|
|
32
64
|
backingOps: ["sentiment.overview"],
|
|
33
65
|
};
|
|
34
|
-
export
|
|
35
|
-
|
|
66
|
+
export const sentimentOverviewSpec = {
|
|
67
|
+
name: "sentiment_overview",
|
|
68
|
+
description: "Aggregate sentiment snapshot for one trading day's latest minute: up/down/flat counts, limit-up/down, gt3/lt3 breadth, divergence index + label, MA20/50/200 breadth, 52w new high/low. `at_date` omitted = today realtime (Redis); explicit YYYY-MM-DD = historical from ClickHouse `market_breadth_intraday` last minute.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
scope: z.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
|
|
71
|
+
at_date: z
|
|
72
|
+
.string()
|
|
73
|
+
.regex(DATE_RE)
|
|
74
|
+
.optional()
|
|
75
|
+
.describe("Trade date YYYY-MM-DD; omit for today realtime"),
|
|
76
|
+
},
|
|
77
|
+
handler: async (args, ctx) => {
|
|
78
|
+
const op = OPERATIONS["sentiment.overview"];
|
|
79
|
+
if (!op)
|
|
80
|
+
throw new Error("sentiment.overview op missing");
|
|
81
|
+
const callArgs = { scope: args.scope };
|
|
82
|
+
if (args.at_date)
|
|
83
|
+
callArgs.trade_date = args.at_date;
|
|
84
|
+
return callOp(op, callArgs, ctx);
|
|
85
|
+
},
|
|
86
|
+
backingOps: ["sentiment.overview"],
|
|
87
|
+
};
|
|
88
|
+
export const sentimentBreadthSpec = {
|
|
89
|
+
name: "sentiment_breadth",
|
|
90
|
+
description: "Intraday breadth time series for one trading day (up to 241 minute bars; 13:00 excluded due to sparse Sina ticks). `at_date` omitted = today (pre-open returns empty); explicit YYYY-MM-DD = historical replay. Returns per-minute rows of up/down/flat/limit_up/limit_down/breadth/divergence fields — same shape as `sentiment_overview` per row.",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
scope: z.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
|
|
93
|
+
at_date: z
|
|
94
|
+
.string()
|
|
95
|
+
.regex(DATE_RE)
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Trade date YYYY-MM-DD; omit for today"),
|
|
98
|
+
},
|
|
99
|
+
handler: async (args, ctx) => {
|
|
100
|
+
const op = OPERATIONS["sentiment.breadth"];
|
|
101
|
+
if (!op)
|
|
102
|
+
throw new Error("sentiment.breadth op missing");
|
|
103
|
+
const callArgs = { scope: args.scope };
|
|
104
|
+
if (args.at_date)
|
|
105
|
+
callArgs.trade_date = args.at_date;
|
|
106
|
+
return callOp(op, callArgs, ctx);
|
|
107
|
+
},
|
|
108
|
+
backingOps: ["sentiment.breadth"],
|
|
109
|
+
};
|
|
110
|
+
export const sentimentTurnoverSpec = {
|
|
111
|
+
name: "sentiment_turnover",
|
|
112
|
+
description: "Intraday turnover time series with `current_turnover` / `predicted_total` / `delta_vs_yesterday` headline fields. `at_date` omitted = today (pre-open returns nulls); explicit YYYY-MM-DD = historical. `days` (1-5) pulls that day + N-1 prior trading days for same-minute YoY comparison.",
|
|
113
|
+
inputSchema: {
|
|
114
|
+
scope: z.enum(SCOPE_VALUES).default("all_a_ex_st").describe("Universe filter"),
|
|
115
|
+
at_date: z
|
|
116
|
+
.string()
|
|
117
|
+
.regex(DATE_RE)
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Trade date YYYY-MM-DD; omit for today"),
|
|
120
|
+
days: z
|
|
121
|
+
.number()
|
|
122
|
+
.int()
|
|
123
|
+
.min(1)
|
|
124
|
+
.max(5)
|
|
125
|
+
.default(1)
|
|
126
|
+
.describe("at_date 当日 + 之前 days-1 天(1-5,默认 1)"),
|
|
127
|
+
},
|
|
128
|
+
handler: async (args, ctx) => {
|
|
129
|
+
const op = OPERATIONS["sentiment.turnover"];
|
|
130
|
+
if (!op)
|
|
131
|
+
throw new Error("sentiment.turnover op missing");
|
|
132
|
+
const callArgs = { scope: args.scope, days: args.days };
|
|
133
|
+
if (args.at_date)
|
|
134
|
+
callArgs.trade_date = args.at_date;
|
|
135
|
+
return callOp(op, callArgs, ctx);
|
|
136
|
+
},
|
|
137
|
+
backingOps: ["sentiment.turnover"],
|
|
138
|
+
};
|
|
139
|
+
export const sentimentPctDistributionSpec = {
|
|
140
|
+
name: "sentiment_pct_distribution",
|
|
141
|
+
description: "Real-time A-share market pct-change distribution: bucket counts at ≤-10% / -9% / ... / +10% / 涨停, plus universe total. Snapshot only — historical replay not yet exposed.",
|
|
142
|
+
inputSchema: {},
|
|
143
|
+
handler: async (_args, ctx) => {
|
|
144
|
+
const op = OPERATIONS["sentiment.pct-distribution"];
|
|
145
|
+
if (!op)
|
|
146
|
+
throw new Error("sentiment.pct-distribution op missing");
|
|
147
|
+
return callOp(op, {}, ctx);
|
|
148
|
+
},
|
|
149
|
+
backingOps: ["sentiment.pct-distribution"],
|
|
150
|
+
};
|
|
151
|
+
function addScope(cmd) {
|
|
36
152
|
cmd.addOption(new Option("--scope <universe>", "Universe filter")
|
|
37
153
|
.choices([...SCOPE_VALUES])
|
|
38
154
|
.default("all_a_ex_st"));
|
|
39
|
-
|
|
155
|
+
}
|
|
156
|
+
function addDate(cmd) {
|
|
157
|
+
cmd.addOption(new Option("--date <YYYY-MM-DD>", "Trade date; omit for today's realtime"));
|
|
158
|
+
}
|
|
159
|
+
export function buildSentimentCommand() {
|
|
160
|
+
const cmd = new Command("sentiment").description("Market sentiment — overview / breadth / turnover / pct-distribution; supports any historical trade_date via --date.");
|
|
161
|
+
// No-subcommand path: `echopai sentiment [--scope X]` → overview (BC with
|
|
162
|
+
// v2.2.0 where sentiment was a single-shot verb). NOTE: `--date` is
|
|
163
|
+
// intentionally NOT declared at parent level — commander would consume
|
|
164
|
+
// it from `sentiment overview --date X` and never propagate to the
|
|
165
|
+
// subcommand. For historical date queries, always use the explicit
|
|
166
|
+
// subcommand form: `echopai sentiment overview --date 2026-05-20`.
|
|
167
|
+
addScope(cmd);
|
|
168
|
+
cmd.action(async (opts, command) => {
|
|
169
|
+
// commander invokes parent action even when a subcommand runs unless
|
|
170
|
+
// we guard. Skip if any argv token after the noun is a registered
|
|
171
|
+
// subcommand name.
|
|
172
|
+
const subNames = new Set(command.commands.map((c) => c.name()));
|
|
173
|
+
const argv = process.argv.slice(2);
|
|
174
|
+
if (argv.some((tok) => subNames.has(tok)))
|
|
175
|
+
return;
|
|
40
176
|
if (!OPERATIONS["sentiment.overview"]) {
|
|
41
177
|
emitVerbError("internal_error", "sentiment.overview missing", undefined, 2);
|
|
42
178
|
}
|
|
43
179
|
await executeVerb(async (ctx) => sentimentSpec.handler({ scope: opts.scope }, ctx));
|
|
44
180
|
});
|
|
181
|
+
const overview = cmd.command("overview").description(sentimentOverviewSpec.description);
|
|
182
|
+
addScope(overview);
|
|
183
|
+
addDate(overview);
|
|
184
|
+
overview.action(async (opts) => {
|
|
185
|
+
if (!OPERATIONS["sentiment.overview"]) {
|
|
186
|
+
emitVerbError("internal_error", "sentiment.overview missing", undefined, 2);
|
|
187
|
+
}
|
|
188
|
+
const args = { scope: opts.scope };
|
|
189
|
+
if (opts.date)
|
|
190
|
+
args.at_date = opts.date;
|
|
191
|
+
await executeVerb(async (ctx) => sentimentOverviewSpec.handler(args, ctx));
|
|
192
|
+
});
|
|
193
|
+
const breadth = cmd.command("breadth").description(sentimentBreadthSpec.description);
|
|
194
|
+
addScope(breadth);
|
|
195
|
+
addDate(breadth);
|
|
196
|
+
breadth.action(async (opts) => {
|
|
197
|
+
if (!OPERATIONS["sentiment.breadth"]) {
|
|
198
|
+
emitVerbError("internal_error", "sentiment.breadth missing", undefined, 2);
|
|
199
|
+
}
|
|
200
|
+
const args = { scope: opts.scope };
|
|
201
|
+
if (opts.date)
|
|
202
|
+
args.at_date = opts.date;
|
|
203
|
+
await executeVerb(async (ctx) => sentimentBreadthSpec.handler(args, ctx));
|
|
204
|
+
});
|
|
205
|
+
const turnover = cmd.command("turnover").description(sentimentTurnoverSpec.description);
|
|
206
|
+
addScope(turnover);
|
|
207
|
+
addDate(turnover);
|
|
208
|
+
turnover.addOption(new Option("--days <n>", "at_date 当日 + 之前 days-1 天 (1-5)").default("1"));
|
|
209
|
+
turnover.action(async (opts) => {
|
|
210
|
+
if (!OPERATIONS["sentiment.turnover"]) {
|
|
211
|
+
emitVerbError("internal_error", "sentiment.turnover missing", undefined, 2);
|
|
212
|
+
}
|
|
213
|
+
const args = {
|
|
214
|
+
scope: opts.scope,
|
|
215
|
+
days: clampInt(opts.days, 1, 5, 1),
|
|
216
|
+
};
|
|
217
|
+
if (opts.date)
|
|
218
|
+
args.at_date = opts.date;
|
|
219
|
+
await executeVerb(async (ctx) => sentimentTurnoverSpec.handler(args, ctx));
|
|
220
|
+
});
|
|
221
|
+
const pctDist = cmd
|
|
222
|
+
.command("pct-distribution")
|
|
223
|
+
.description(sentimentPctDistributionSpec.description);
|
|
224
|
+
pctDist.action(async () => {
|
|
225
|
+
if (!OPERATIONS["sentiment.pct-distribution"]) {
|
|
226
|
+
emitVerbError("internal_error", "sentiment.pct-distribution missing", undefined, 2);
|
|
227
|
+
}
|
|
228
|
+
await executeVerb(async (ctx) => sentimentPctDistributionSpec.handler({}, ctx));
|
|
229
|
+
});
|
|
45
230
|
return cmd;
|
|
46
231
|
}
|
package/dist/verbs/views.js
CHANGED
|
@@ -10,13 +10,15 @@ import { executeVerb, emitVerbError } from "../runtime/verb_cmd.js";
|
|
|
10
10
|
import { callOp } from "../runtime/verb_runner.js";
|
|
11
11
|
export const viewsSpec = {
|
|
12
12
|
name: "views",
|
|
13
|
-
description: "PRIMARY research source for stock judgement: analyst views / sell-side reports / long-form opinion stream, with research_entity_id attribution. Prefer this over `news` when forming an investment opinion.",
|
|
13
|
+
description: "PRIMARY research source for stock judgement: analyst views / sell-side reports / long-form opinion stream, with research_entity_id attribution. Prefer this over `news` when forming an investment opinion. AH dual-listing: for an A+H listed company pass the A-share canonical to get the consolidated coverage (most domestic analyst views attribute to the A-share security); use HK code only if the user explicitly asks for the HK perspective.",
|
|
14
14
|
inputSchema: {
|
|
15
15
|
code: z
|
|
16
16
|
.string()
|
|
17
|
-
.
|
|
17
|
+
// Any-market:与 openapi components.schemas.CanonicalCodeAny 一致
|
|
18
|
+
// 研究/观点类 endpoint 不限 A 股;HK:00700 / US:AAPL 也允许(PR #D 拉齐)
|
|
19
|
+
.regex(/^((SSE|SZSE|BSE):[0-9]{6}|HK:[0-9]{5}|US:[A-Z][A-Z0-9.\-]{0,5})$/)
|
|
18
20
|
.optional()
|
|
19
|
-
.describe("Filter by security canonical_code (e.g. SSE:600519)"),
|
|
21
|
+
.describe("Filter by security canonical_code (e.g. SSE:600519, HK:00700, US:AAPL)"),
|
|
20
22
|
analyst: z.string().optional().describe("Analyst Chinese name (exact / fuzzy)"),
|
|
21
23
|
institution: z.string().optional().describe("Broker / institution Chinese name"),
|
|
22
24
|
since_days: z
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echopai",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Command-line interface for the EchoPai Open Platform: stock-market data, news, analyst views, sentiment, signals, backtests.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://echopai.com",
|