tickflow-assist 0.2.4 → 0.2.6
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 +33 -101
- package/dist/analysis/parsers/json-block.parser.d.ts +4 -1
- package/dist/analysis/parsers/json-block.parser.js +109 -6
- package/dist/analysis/parsers/key-levels.parser.js +2 -11
- package/dist/analysis/parsers/watchlist-profile.parser.js +1 -1
- package/dist/config/normalize.js +0 -3
- package/dist/dev/tickflow-assist-cli.js +5 -48
- package/dist/plugin-registration.test.d.ts +1 -0
- package/dist/plugin-registration.test.js +93 -0
- package/dist/prompts/analysis/common-system-prompt.d.ts +1 -1
- package/dist/prompts/analysis/common-system-prompt.js +7 -15
- package/dist/prompts/analysis/composite-analysis-user-prompt.d.ts +1 -1
- package/dist/prompts/analysis/composite-analysis-user-prompt.js +18 -26
- package/dist/prompts/analysis/financial-analysis-user-prompt.d.ts +1 -1
- package/dist/prompts/analysis/financial-analysis-user-prompt.js +38 -6
- package/dist/prompts/analysis/financial-lite-analysis-user-prompt.d.ts +1 -1
- package/dist/prompts/analysis/financial-lite-analysis-user-prompt.js +10 -6
- package/dist/prompts/analysis/kline-analysis-user-prompt.js +151 -34
- package/dist/prompts/analysis/news-analysis-user-prompt.d.ts +1 -1
- package/dist/prompts/analysis/news-analysis-user-prompt.js +44 -9
- package/dist/prompts/analysis/post-close-review-user-prompt.d.ts +1 -1
- package/dist/prompts/analysis/post-close-review-user-prompt.js +28 -30
- package/dist/prompts/analysis/prompt-text-utils.d.ts +4 -0
- package/dist/prompts/analysis/prompt-text-utils.js +28 -0
- package/dist/prompts/analysis/shared-schema.d.ts +4 -0
- package/dist/prompts/analysis/shared-schema.js +40 -0
- package/dist/prompts/analysis/watchlist-profile-extraction-prompt.js +3 -2
- package/dist/utils/cost-price.d.ts +1 -0
- package/dist/utils/cost-price.js +15 -0
- package/openclaw.plugin.json +64 -19
- package/package.json +9 -3
- package/skills/database-query/SKILL.md +2 -1
- package/skills/stock-analysis/SKILL.md +4 -1
- package/dist/runtime/monitor-process.d.ts +0 -3
- package/dist/runtime/monitor-process.js +0 -24
- package/docs/installation.md +0 -393
- package/docs/usage.md +0 -244
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FinancialSnapshot } from "../../services/financial-service.js";
|
|
2
|
-
export declare const FINANCIAL_ANALYSIS_SYSTEM_PROMPT = "\n\u4F60\u662F\u4E00\u4F4D\u4E13\u4E1A\u7684A\u80A1\u57FA\u672C\u9762\u5206\u6790\u5E08\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u4EC5\u57FA\u4E8E\u63D0\u4F9B\u7684\u8D22\u52A1\u6570\u636E\uFF0C\u8BC4\u4F30\u516C\u53F8\u7684\u76C8\u5229\u80FD\u529B\u3001\u6210\u957F\u6027\u3001\u73B0\u91D1\u8D28\u91CF\u4E0E\u507F\u503A\u538B\u529B\u3002\n\n\u8F93\u51FA\u8981\u6C42\uFF1A\n1. \u5148\u7ED9\u51FA\u4E00\u6BB5 80-120 \u5B57\u4E2D\u6587\u6838\u5FC3\u7ED3\u8BBA\uFF0C\u4E0D\u8981\u5728\u6B63\u6587\u4E2D\u6DF7\u5165 JSON\u3002\n2. \u6838\u5FC3\u7ED3\u8BBA\u540E\u6309\u4EE5\u4E0B\u5C0F\u8282\u5206\u6BB5\u5C55\u5F00\uFF0C\u6BCF\u8282 1-3 \u53E5\uFF1A\n- \u76C8\u5229\u8D28\u91CF\u4E0E\u6210\u957F\u6027\n- \u73B0\u91D1\u6D41\u8D28\u91CF\n- \u8D44\u4EA7\u8D1F\u503A\u7ED3\u6784\u4E0E\u507F\u503A\u538B\u529B\n3. \u5206\u6BB5\u5185\u5BB9\u5FC5\u987B\u5C3D\u91CF\u5F15\u7528\u5DF2\u63D0\u4F9B\u7684\u8D22\u52A1\u6307\u6807\u3001\u540C\u6BD4\u53D8\u5316\u6216\u62A5\u8868\u9879\u76EE\uFF0C\u4E0D\u8981\u7A7A\u6CDB\u8868\u8FF0\u3002\n4. \u6700\u540E\u8F93\u51FA ```json \u4EE3\u7801\u5757\uFF0C\u7ED3\u6784\u5982\u4E0B\uFF1A\n{\n \"score\":
|
|
2
|
+
export declare const FINANCIAL_ANALYSIS_SYSTEM_PROMPT = "\n\u4F60\u662F\u4E00\u4F4D\u4E13\u4E1A\u7684A\u80A1\u57FA\u672C\u9762\u5206\u6790\u5E08\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u4EC5\u57FA\u4E8E\u63D0\u4F9B\u7684\u8D22\u52A1\u6570\u636E\uFF0C\u8BC4\u4F30\u516C\u53F8\u7684\u76C8\u5229\u80FD\u529B\u3001\u6210\u957F\u6027\u3001\u73B0\u91D1\u8D28\u91CF\u4E0E\u507F\u503A\u538B\u529B\u3002\n\n\u8F93\u51FA\u8981\u6C42\uFF1A\n1. \u5148\u7ED9\u51FA\u4E00\u6BB5 80-120 \u5B57\u4E2D\u6587\u6838\u5FC3\u7ED3\u8BBA\uFF0C\u4E0D\u8981\u5728\u6B63\u6587\u4E2D\u6DF7\u5165 JSON\u3002\n2. \u6838\u5FC3\u7ED3\u8BBA\u540E\u6309\u4EE5\u4E0B\u5C0F\u8282\u5206\u6BB5\u5C55\u5F00\uFF0C\u6BCF\u8282 1-3 \u53E5\uFF1A\n- \u76C8\u5229\u8D28\u91CF\u4E0E\u6210\u957F\u6027\n- \u73B0\u91D1\u6D41\u8D28\u91CF\n- \u8D44\u4EA7\u8D1F\u503A\u7ED3\u6784\u4E0E\u507F\u503A\u538B\u529B\n3. \u5206\u6BB5\u5185\u5BB9\u5FC5\u987B\u5C3D\u91CF\u5F15\u7528\u5DF2\u63D0\u4F9B\u7684\u8D22\u52A1\u6307\u6807\u3001\u540C\u6BD4\u53D8\u5316\u6216\u62A5\u8868\u9879\u76EE\uFF0C\u4E0D\u8981\u7A7A\u6CDB\u8868\u8FF0\u3002\n4. \u6700\u540E\u8F93\u51FA ```json \u4EE3\u7801\u5757\uFF0C\u7ED3\u6784\u5982\u4E0B\uFF1A\n{\n \"score\": integer,\n \"bias\": \"positive\" | \"neutral\" | \"negative\",\n \"strengths\": [\"<\u57FA\u672C\u9762\u4F18\u52BF1>\", \"<\u57FA\u672C\u9762\u4F18\u52BF2>\"],\n \"risks\": [\"<\u57FA\u672C\u9762\u98CE\u96691>\", \"<\u57FA\u672C\u9762\u98CE\u96692>\"],\n \"watch_items\": [\"<\u540E\u7EED\u5173\u6CE8\u70B91>\", \"<\u540E\u7EED\u5173\u6CE8\u70B92>\"]\n}\n\n\u89C4\u5219\uFF1A\n- score \u4E3A 1-10 \u7684\u6574\u6570\u3002\n- bias \u53EA\u80FD\u662F positive / neutral / negative\u3002\n- strengths / risks / watch_items \u5404\u8F93\u51FA 1-3 \u6761\u3002\n- \u4E0D\u8981\u81C6\u9020\u6CA1\u6709\u63D0\u4F9B\u7684\u6570\u636E\uFF0C\u4E0D\u8981\u5F15\u7528\u5E02\u573A\u4EF7\u683C\u4E0EK\u7EBF\u4FE1\u606F\u3002\n- \u82E5\u67D0\u9879\u8D22\u52A1\u5B57\u6BB5\u4E3A null\u3001\u7F3A\u5931\u6216\u672A\u63D0\u4F9B\uFF0C\u53EA\u80FD\u8868\u8FF0\u4E3A\u201C\u6570\u636E\u4E0D\u53EF\u7528\u201D\u6216\u201C\u5F53\u524D\u672A\u8986\u76D6\u201D\uFF0C\u4E0D\u80FD\u76F4\u63A5\u63A8\u65AD\u4E3A\u8D1F\u9762\u4E8B\u5B9E\u3002\n";
|
|
3
3
|
export declare function buildFinancialAnalysisUserPrompt(params: {
|
|
4
4
|
symbol: string;
|
|
5
5
|
companyName: string;
|
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
const FIELD_LABELS = {
|
|
2
|
+
announce_date: "公告日",
|
|
3
|
+
basic_eps: "基本每股收益",
|
|
4
|
+
capex: "资本开支",
|
|
5
|
+
cash_and_equivalents: "货币资金",
|
|
6
|
+
debt_to_asset_ratio: "资产负债率",
|
|
7
|
+
gross_margin: "毛利率",
|
|
8
|
+
long_term_borrowing: "长期借款",
|
|
9
|
+
net_cash_change: "现金净增加额",
|
|
10
|
+
net_financing_cash_flow: "筹资现金流净额",
|
|
11
|
+
net_income: "净利润",
|
|
12
|
+
net_income_attributable: "归母净利润",
|
|
13
|
+
net_income_yoy: "净利润同比",
|
|
14
|
+
net_investing_cash_flow: "投资现金流净额",
|
|
15
|
+
net_margin: "净利率",
|
|
16
|
+
net_operating_cash_flow: "经营现金流净额",
|
|
17
|
+
ocfps: "每股经营现金流",
|
|
18
|
+
operating_cash_to_revenue: "销售现金比率",
|
|
19
|
+
operating_profit: "营业利润",
|
|
20
|
+
period_end: "报告期",
|
|
21
|
+
revenue: "营业收入",
|
|
22
|
+
revenue_yoy: "营收同比",
|
|
23
|
+
roa: "ROA",
|
|
24
|
+
roe: "ROE",
|
|
25
|
+
short_term_borrowing: "短期借款",
|
|
26
|
+
total_assets: "总资产",
|
|
27
|
+
total_current_assets: "流动资产",
|
|
28
|
+
total_current_liabilities: "流动负债",
|
|
29
|
+
total_equity: "股东权益",
|
|
30
|
+
total_liabilities: "总负债",
|
|
31
|
+
};
|
|
1
32
|
export const FINANCIAL_ANALYSIS_SYSTEM_PROMPT = `
|
|
2
33
|
你是一位专业的A股基本面分析师。你的任务是仅基于提供的财务数据,评估公司的盈利能力、成长性、现金质量与偿债压力。
|
|
3
34
|
|
|
@@ -10,11 +41,11 @@ export const FINANCIAL_ANALYSIS_SYSTEM_PROMPT = `
|
|
|
10
41
|
3. 分段内容必须尽量引用已提供的财务指标、同比变化或报表项目,不要空泛表述。
|
|
11
42
|
4. 最后输出 \`\`\`json 代码块,结构如下:
|
|
12
43
|
{
|
|
13
|
-
"score":
|
|
14
|
-
"bias": "neutral",
|
|
15
|
-
"strengths": ["
|
|
16
|
-
"risks": ["
|
|
17
|
-
"watch_items": ["
|
|
44
|
+
"score": integer,
|
|
45
|
+
"bias": "positive" | "neutral" | "negative",
|
|
46
|
+
"strengths": ["<基本面优势1>", "<基本面优势2>"],
|
|
47
|
+
"risks": ["<基本面风险1>", "<基本面风险2>"],
|
|
48
|
+
"watch_items": ["<后续关注点1>", "<后续关注点2>"]
|
|
18
49
|
}
|
|
19
50
|
|
|
20
51
|
规则:
|
|
@@ -22,6 +53,7 @@ export const FINANCIAL_ANALYSIS_SYSTEM_PROMPT = `
|
|
|
22
53
|
- bias 只能是 positive / neutral / negative。
|
|
23
54
|
- strengths / risks / watch_items 各输出 1-3 条。
|
|
24
55
|
- 不要臆造没有提供的数据,不要引用市场价格与K线信息。
|
|
56
|
+
- 若某项财务字段为 null、缺失或未提供,只能表述为“数据不可用”或“当前未覆盖”,不能直接推断为负面事实。
|
|
25
57
|
`;
|
|
26
58
|
export function buildFinancialAnalysisUserPrompt(params) {
|
|
27
59
|
const income = params.snapshot.income.slice(0, 2);
|
|
@@ -91,7 +123,7 @@ function renderRows(rows, keys) {
|
|
|
91
123
|
}
|
|
92
124
|
return rows.map((row, index) => {
|
|
93
125
|
const record = row;
|
|
94
|
-
const parts = keys.map((key) => `${key}=${formatValue(record[key])}`);
|
|
126
|
+
const parts = keys.map((key) => `${FIELD_LABELS[key] ?? key}=${formatValue(record[key])}`);
|
|
95
127
|
return `- 第 ${index + 1} 期: ${parts.join(" | ")}`;
|
|
96
128
|
});
|
|
97
129
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FinancialLiteSnapshot } from "../../services/financial-lite-service.js";
|
|
2
|
-
export declare const FINANCIAL_LITE_ANALYSIS_SYSTEM_PROMPT = "\n\u4F60\u662F\u4E00\u4F4D\u4E13\u4E1A\u7684A\u80A1\u57FA\u672C\u9762\u5206\u6790\u5E08\u3002\u5F53\u524D\u662F financial-lite \u6A21\u5F0F\uFF0C\u53EA\u80FD\u57FA\u4E8E\u5C11\u91CF\u6838\u5FC3\u8D22\u52A1\u6307\u6807\u505A\u7C97\u7C92\u5EA6\u5224\u65AD\uFF0C\u4E0D\u80FD\u628A\u5B83\u5F53\u6210\u5B8C\u6574\u8D22\u62A5\u5206\u6790\u3002\n\n\u8F93\u51FA\u8981\u6C42\uFF1A\n1. \u5148\u7ED9\u51FA\u4E00\u6BB5 80-120 \u5B57\u4E2D\u6587\u6838\u5FC3\u7ED3\u8BBA\uFF0C\u4E0D\u8981\u5728\u6B63\u6587\u4E2D\u6DF7\u5165 JSON\u3002\n2. \u6838\u5FC3\u7ED3\u8BBA\u540E\u6309\u4EE5\u4E0B\u5C0F\u8282\u5206\u6BB5\u5C55\u5F00\uFF0C\u6BCF\u8282 1-3 \u53E5\uFF1A\n- \u76C8\u5229\u80FD\u529B\u4E0E\u6210\u957F\u6027\n- \u6760\u6746\u4E0E\u507F\u503A\u538B\u529B\n- \u73B0\u91D1\u6D41\u4FE1\u53F7\u6216\u8986\u76D6\u4E0D\u8DB3\u8BF4\u660E\n3. \u5982\u679C\u6307\u6807\u8986\u76D6\u4E0D\u8DB3\uFF0C\u5FC5\u987B\u660E\u786E\u6307\u51FA\u201C\u5F53\u524D\u4E3A lite \u6307\u6807\u62D6\u5E95\u6A21\u5F0F\uFF0C\u7ED3\u8BBA\u7F6E\u4FE1\u5EA6\u6709\u9650\u201D\uFF0C\u5E76\u8BF4\u660E\u7F3A\u4E86\u54EA\u4E9B\u5173\u952E\u7EF4\u5EA6\u3002\n4. \u5206\u6BB5\u5185\u5BB9\u53EA\u5141\u8BB8\u57FA\u4E8E\u5DF2\u63D0\u4F9B\u6307\u6807\u63A8\u65AD\uFF0C\u4E0D\u8981\u628A\u7F3A\u5931\u6307\u6807\u76F4\u63A5\u5F53\u6210\u8D1F\u9762\u4E8B\u5B9E\u3002\n5. \u6700\u540E\u8F93\u51FA ```json \u4EE3\u7801\u5757\uFF0C\u7ED3\u6784\u5982\u4E0B\uFF1A\n{\n \"score\":
|
|
2
|
+
export declare const FINANCIAL_LITE_ANALYSIS_SYSTEM_PROMPT = "\n\u4F60\u662F\u4E00\u4F4D\u4E13\u4E1A\u7684A\u80A1\u57FA\u672C\u9762\u5206\u6790\u5E08\u3002\u5F53\u524D\u662F financial-lite \u6A21\u5F0F\uFF0C\u53EA\u80FD\u57FA\u4E8E\u5C11\u91CF\u6838\u5FC3\u8D22\u52A1\u6307\u6807\u505A\u7C97\u7C92\u5EA6\u5224\u65AD\uFF0C\u4E0D\u80FD\u628A\u5B83\u5F53\u6210\u5B8C\u6574\u8D22\u62A5\u5206\u6790\u3002\n\n\u8F93\u51FA\u8981\u6C42\uFF1A\n1. \u5148\u7ED9\u51FA\u4E00\u6BB5 80-120 \u5B57\u4E2D\u6587\u6838\u5FC3\u7ED3\u8BBA\uFF0C\u4E0D\u8981\u5728\u6B63\u6587\u4E2D\u6DF7\u5165 JSON\u3002\n2. \u6838\u5FC3\u7ED3\u8BBA\u540E\u6309\u4EE5\u4E0B\u5C0F\u8282\u5206\u6BB5\u5C55\u5F00\uFF0C\u6BCF\u8282 1-3 \u53E5\uFF1A\n- \u76C8\u5229\u80FD\u529B\u4E0E\u6210\u957F\u6027\n- \u6760\u6746\u4E0E\u507F\u503A\u538B\u529B\n- \u73B0\u91D1\u6D41\u4FE1\u53F7\u6216\u8986\u76D6\u4E0D\u8DB3\u8BF4\u660E\n3. \u5982\u679C\u6307\u6807\u8986\u76D6\u4E0D\u8DB3\uFF0C\u5FC5\u987B\u660E\u786E\u6307\u51FA\u201C\u5F53\u524D\u4E3A lite \u6307\u6807\u62D6\u5E95\u6A21\u5F0F\uFF0C\u7ED3\u8BBA\u7F6E\u4FE1\u5EA6\u6709\u9650\u201D\uFF0C\u5E76\u8BF4\u660E\u7F3A\u4E86\u54EA\u4E9B\u5173\u952E\u7EF4\u5EA6\u3002\n4. \u5206\u6BB5\u5185\u5BB9\u53EA\u5141\u8BB8\u57FA\u4E8E\u5DF2\u63D0\u4F9B\u6307\u6807\u63A8\u65AD\uFF0C\u4E0D\u8981\u628A\u7F3A\u5931\u6307\u6807\u76F4\u63A5\u5F53\u6210\u8D1F\u9762\u4E8B\u5B9E\u3002\n5. \u6700\u540E\u8F93\u51FA ```json \u4EE3\u7801\u5757\uFF0C\u7ED3\u6784\u5982\u4E0B\uFF1A\n{\n \"score\": integer,\n \"bias\": \"positive\" | \"neutral\" | \"negative\",\n \"strengths\": [\"<\u57FA\u672C\u9762\u4F18\u52BF1>\", \"<\u57FA\u672C\u9762\u4F18\u52BF2>\"],\n \"risks\": [\"<\u57FA\u672C\u9762\u98CE\u96691>\", \"<\u57FA\u672C\u9762\u98CE\u96692>\"],\n \"watch_items\": [\"<\u540E\u7EED\u5173\u6CE8\u70B91>\", \"<\u540E\u7EED\u5173\u6CE8\u70B92>\"]\n}\n\n\u89C4\u5219\uFF1A\n- score \u4E3A 1-10 \u7684\u6574\u6570\u3002\n- bias \u53EA\u80FD\u662F positive / neutral / negative\u3002\n- strengths / risks / watch_items \u5404\u8F93\u51FA 1-3 \u6761\u3002\n- \u4E0D\u8981\u81C6\u9020\u4E0D\u5B58\u5728\u7684\u8D22\u62A5\u5B57\u6BB5\uFF0C\u4E0D\u8981\u628A\u7F3A\u5931\u6307\u6807\u5F53\u6210\u8D1F\u9762\u4E8B\u5B9E\u3002\n- \u4E0D\u8981\u5F15\u7528\u5E02\u573A\u4EF7\u683C\u4E0EK\u7EBF\u4FE1\u606F\u3002\n- \u82E5\u67D0\u9879\u6307\u6807\u7F3A\u5931\u3001\u4E3A\u7A7A\u6216\u672A\u8986\u76D6\uFF0C\u53EA\u80FD\u8868\u8FF0\u4E3A\u6570\u636E\u4E0D\u8DB3\uFF0C\u4E0D\u80FD\u76F4\u63A5\u63A8\u65AD\u4E3A\u57FA\u672C\u9762\u6076\u5316\u3002\n";
|
|
3
3
|
export declare function buildFinancialLiteAnalysisUserPrompt(params: {
|
|
4
4
|
symbol: string;
|
|
5
5
|
companyName: string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sanitizeExternalPromptText } from "./prompt-text-utils.js";
|
|
2
|
+
const MAX_PARSER_TEXT_LENGTH = 240;
|
|
1
3
|
export const FINANCIAL_LITE_ANALYSIS_SYSTEM_PROMPT = `
|
|
2
4
|
你是一位专业的A股基本面分析师。当前是 financial-lite 模式,只能基于少量核心财务指标做粗粒度判断,不能把它当成完整财报分析。
|
|
3
5
|
|
|
@@ -11,11 +13,11 @@ export const FINANCIAL_LITE_ANALYSIS_SYSTEM_PROMPT = `
|
|
|
11
13
|
4. 分段内容只允许基于已提供指标推断,不要把缺失指标直接当成负面事实。
|
|
12
14
|
5. 最后输出 \`\`\`json 代码块,结构如下:
|
|
13
15
|
{
|
|
14
|
-
"score":
|
|
15
|
-
"bias": "neutral",
|
|
16
|
-
"strengths": ["
|
|
17
|
-
"risks": ["
|
|
18
|
-
"watch_items": ["
|
|
16
|
+
"score": integer,
|
|
17
|
+
"bias": "positive" | "neutral" | "negative",
|
|
18
|
+
"strengths": ["<基本面优势1>", "<基本面优势2>"],
|
|
19
|
+
"risks": ["<基本面风险1>", "<基本面风险2>"],
|
|
20
|
+
"watch_items": ["<后续关注点1>", "<后续关注点2>"]
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
规则:
|
|
@@ -24,14 +26,16 @@ export const FINANCIAL_LITE_ANALYSIS_SYSTEM_PROMPT = `
|
|
|
24
26
|
- strengths / risks / watch_items 各输出 1-3 条。
|
|
25
27
|
- 不要臆造不存在的财报字段,不要把缺失指标当成负面事实。
|
|
26
28
|
- 不要引用市场价格与K线信息。
|
|
29
|
+
- 若某项指标缺失、为空或未覆盖,只能表述为数据不足,不能直接推断为基本面恶化。
|
|
27
30
|
`;
|
|
28
31
|
export function buildFinancialLiteAnalysisUserPrompt(params) {
|
|
32
|
+
const parserText = sanitizeExternalPromptText(params.snapshot.parserText, MAX_PARSER_TEXT_LENGTH);
|
|
29
33
|
return [
|
|
30
34
|
`请基于 lite 基本面指标分析 ${params.companyName}(${params.symbol})。`,
|
|
31
35
|
`数据来源: mx_select_stock`,
|
|
32
36
|
`检索问句: ${params.snapshot.query}`,
|
|
33
37
|
`指标日期: ${params.snapshot.asOf ?? "-"}`,
|
|
34
|
-
`解析说明: ${
|
|
38
|
+
`解析说明: ${parserText || "未提供或已忽略异常解析说明"}`,
|
|
35
39
|
"",
|
|
36
40
|
"## 可用核心指标",
|
|
37
41
|
...renderMetrics(params.snapshot.metrics),
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { formatCostPrice } from "../../utils/cost-price.js";
|
|
1
|
+
import { formatCostPrice, formatCostRelationship } from "../../utils/cost-price.js";
|
|
2
|
+
const MAX_INTRADAY_FULL_ROWS = 40;
|
|
3
|
+
const MAX_INTRADAY_OPEN_ROWS = 8;
|
|
4
|
+
const MAX_INTRADAY_CLOSE_ROWS = 12;
|
|
5
|
+
const MAX_INTRADAY_EVENT_ROWS = 6;
|
|
6
|
+
const MAX_INTRADAY_INDICATOR_ROWS = 24;
|
|
2
7
|
export function buildKlineAnalysisUserPrompt(params) {
|
|
3
8
|
const recentK = params.klines.slice(-30);
|
|
4
9
|
const recentIndicators = params.indicators.slice(-10);
|
|
@@ -29,19 +34,19 @@ export function buildKlineAnalysisUserPrompt(params) {
|
|
|
29
34
|
"日期,MA5,MA10,MA20,MA60,MACD,Signal,RSI6,RSI12,KDJ_K,KDJ_D,KDJ_J,CCI,ADX",
|
|
30
35
|
...recentIndicators.map((row) => [
|
|
31
36
|
row.trade_date,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
fmtPrice(row.ma5),
|
|
38
|
+
fmtPrice(row.ma10),
|
|
39
|
+
fmtPrice(row.ma20),
|
|
40
|
+
fmtPrice(row.ma60),
|
|
36
41
|
fmt(row.macd, 4),
|
|
37
42
|
fmt(row.macd_signal, 4),
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
fmtOscillator(row.rsi_6),
|
|
44
|
+
fmtOscillator(row.rsi_12),
|
|
45
|
+
fmtOscillator(row.kdj_k),
|
|
46
|
+
fmtOscillator(row.kdj_d),
|
|
47
|
+
fmtOscillator(row.kdj_j),
|
|
48
|
+
fmtWideOscillator(row.cci),
|
|
49
|
+
fmtOscillator(row.adx),
|
|
45
50
|
].join(",")),
|
|
46
51
|
"```",
|
|
47
52
|
];
|
|
@@ -50,19 +55,21 @@ export function buildKlineAnalysisUserPrompt(params) {
|
|
|
50
55
|
"## 最新指标状态",
|
|
51
56
|
"",
|
|
52
57
|
`- MACD: DIF=${fmt(latest.macd, 4)}, DEA=${fmt(latest.macd_signal, 4)}, 柱状=${fmt(latest.macd_hist, 4)}`,
|
|
53
|
-
`- KDJ: K=${
|
|
54
|
-
`- RSI: RSI6=${
|
|
55
|
-
`- CCI: ${
|
|
56
|
-
`- BIAS: 6日=${
|
|
57
|
-
`- DMI: +DI=${
|
|
58
|
-
`- BOLL: 上轨=${
|
|
58
|
+
`- KDJ: K=${fmtOscillator(latest.kdj_k)}, D=${fmtOscillator(latest.kdj_d)}, J=${fmtOscillator(latest.kdj_j)}`,
|
|
59
|
+
`- RSI: RSI6=${fmtOscillator(latest.rsi_6)}, RSI12=${fmtOscillator(latest.rsi_12)}, RSI24=${fmtOscillator(latest.rsi_24)}`,
|
|
60
|
+
`- CCI: ${fmtWideOscillator(latest.cci)}`,
|
|
61
|
+
`- BIAS: 6日=${fmtWideOscillator(latest.bias_6)}, 12日=${fmtWideOscillator(latest.bias_12)}, 24日=${fmtWideOscillator(latest.bias_24)}`,
|
|
62
|
+
`- DMI: +DI=${fmtOscillator(latest.plus_di)}, -DI=${fmtOscillator(latest.minus_di)}, ADX=${fmtOscillator(latest.adx)}`,
|
|
63
|
+
`- BOLL: 上轨=${fmtPrice(latest.boll_upper)}, 中轨=${fmtPrice(latest.boll_mid)}, 下轨=${fmtPrice(latest.boll_lower)}`,
|
|
59
64
|
]
|
|
60
65
|
: [];
|
|
61
66
|
const realtimeLines = buildRealtimeLines(params.realtimeQuote);
|
|
62
67
|
const reviewMemoryLines = buildReviewMemoryLines(params.reviewMemory);
|
|
63
68
|
const intradaySummaryLines = buildIntradaySummaryLines(params.intradayKlines, latestIntradayIndicator, params.realtimeQuote);
|
|
64
|
-
const
|
|
65
|
-
const
|
|
69
|
+
const sampledIntradayRows = selectSampledIntradayRows(params.intradayKlines);
|
|
70
|
+
const sampledIntradayTimes = new Set(sampledIntradayRows.map((row) => row.trade_time));
|
|
71
|
+
const intradayKlineLines = buildIntradayKlineLines(params.intradayKlines, sampledIntradayRows);
|
|
72
|
+
const intradayIndicatorLines = buildIntradayIndicatorLines(params.intradayIndicators, sampledIntradayTimes);
|
|
66
73
|
return [
|
|
67
74
|
"请结合日线、日内分钟线、分钟指标和实时行情分析以下股票的技术面,并补充日内走势判断,给出关键价位。",
|
|
68
75
|
"",
|
|
@@ -70,6 +77,7 @@ export function buildKlineAnalysisUserPrompt(params) {
|
|
|
70
77
|
`**用户成本价**: ${formatCostPrice(params.costPrice, " 元")}`,
|
|
71
78
|
`**最新收盘价**: ${latestClose.toFixed(2)} 元`,
|
|
72
79
|
`**最新实时价**: ${latestRealtimePrice.toFixed(2)} 元`,
|
|
80
|
+
`**相对成本价**: ${formatCostRelationship(latestRealtimePrice, params.costPrice)}`,
|
|
73
81
|
"",
|
|
74
82
|
...realtimeLines,
|
|
75
83
|
"",
|
|
@@ -104,6 +112,15 @@ function fmt(value, digits = 2) {
|
|
|
104
112
|
}
|
|
105
113
|
return stripTrailingZeros(value.toFixed(digits));
|
|
106
114
|
}
|
|
115
|
+
function fmtPrice(value) {
|
|
116
|
+
return fmt(value, 2);
|
|
117
|
+
}
|
|
118
|
+
function fmtOscillator(value) {
|
|
119
|
+
return fmt(value, 1);
|
|
120
|
+
}
|
|
121
|
+
function fmtWideOscillator(value) {
|
|
122
|
+
return fmt(value, 2);
|
|
123
|
+
}
|
|
107
124
|
function fmtPercent(value, digits = 2) {
|
|
108
125
|
return value == null || Number.isNaN(value) ? "-" : `${fmt(value, digits)}%`;
|
|
109
126
|
}
|
|
@@ -149,20 +166,24 @@ function buildIntradaySummaryLines(rows, latestIndicator, quote) {
|
|
|
149
166
|
`- 日内累计成交量: ${Math.trunc(sessionVolume)}`,
|
|
150
167
|
];
|
|
151
168
|
if (latestIndicator) {
|
|
152
|
-
lines.push(`- 分钟指标: MA5=${
|
|
169
|
+
lines.push(`- 分钟指标: MA5=${fmtPrice(latestIndicator.ma5)} | MA10=${fmtPrice(latestIndicator.ma10)} | MACD=${fmt(latestIndicator.macd, 4)} | RSI6=${fmtOscillator(latestIndicator.rsi_6)} | KDJ_K=${fmtOscillator(latestIndicator.kdj_k)}`);
|
|
153
170
|
}
|
|
154
171
|
return lines;
|
|
155
172
|
}
|
|
156
|
-
function buildIntradayKlineLines(rows) {
|
|
173
|
+
function buildIntradayKlineLines(rows, sampledRows) {
|
|
157
174
|
if (rows.length === 0) {
|
|
158
175
|
return ["## 今日分钟K线", "", "- 暂无分钟K线数据"];
|
|
159
176
|
}
|
|
177
|
+
const sampled = sampledRows.length > 0 ? sampledRows : rows;
|
|
178
|
+
const sampledLabel = sampled.length === rows.length
|
|
179
|
+
? `全部 ${rows.length} 根`
|
|
180
|
+
: `抽样 ${sampled.length}/${rows.length} 根(开盘、异动、尾盘)`;
|
|
160
181
|
return [
|
|
161
|
-
`## 今日分钟K
|
|
182
|
+
`## 今日分钟K线(${sampledLabel})`,
|
|
162
183
|
"",
|
|
163
184
|
"```csv",
|
|
164
185
|
"时间,开盘,最高,最低,收盘,成交量,成交额",
|
|
165
|
-
...
|
|
186
|
+
...sampled.map((row) => [
|
|
166
187
|
row.trade_time,
|
|
167
188
|
fmt(row.open),
|
|
168
189
|
fmt(row.high),
|
|
@@ -172,35 +193,131 @@ function buildIntradayKlineLines(rows) {
|
|
|
172
193
|
fmtInteger(row.amount),
|
|
173
194
|
].join(",")),
|
|
174
195
|
"```",
|
|
196
|
+
...(sampled.length === rows.length
|
|
197
|
+
? []
|
|
198
|
+
: ["说明: 已保留开盘段、关键异动分钟与尾盘分钟,其余时段用“今日分钟线概览”压缩。"]),
|
|
175
199
|
];
|
|
176
200
|
}
|
|
177
|
-
function buildIntradayIndicatorLines(rows) {
|
|
201
|
+
function buildIntradayIndicatorLines(rows, sampledTimes) {
|
|
178
202
|
if (rows.length === 0) {
|
|
179
203
|
return ["## 今日分钟指标", "", "- 暂无分钟指标数据"];
|
|
180
204
|
}
|
|
205
|
+
const sampledRows = selectSampledIntradayIndicatorRows(rows, sampledTimes);
|
|
206
|
+
const sampledLabel = sampledRows.length === rows.length
|
|
207
|
+
? `全部 ${rows.length} 条`
|
|
208
|
+
: `抽样 ${sampledRows.length}/${rows.length} 条(对齐分钟K与尾盘)`;
|
|
181
209
|
return [
|
|
182
|
-
`##
|
|
210
|
+
`## 今日分钟指标(${sampledLabel})`,
|
|
183
211
|
"",
|
|
184
212
|
"```csv",
|
|
185
213
|
"时间,MA5,MA10,MA20,MACD,Signal,RSI6,KDJ_K,KDJ_D,KDJ_J",
|
|
186
|
-
...
|
|
214
|
+
...sampledRows.map((row) => [
|
|
187
215
|
row.trade_time ?? "-",
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
216
|
+
fmtPrice(row.ma5),
|
|
217
|
+
fmtPrice(row.ma10),
|
|
218
|
+
fmtPrice(row.ma20),
|
|
191
219
|
fmt(row.macd, 4),
|
|
192
220
|
fmt(row.macd_signal, 4),
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
221
|
+
fmtOscillator(row.rsi_6),
|
|
222
|
+
fmtOscillator(row.kdj_k),
|
|
223
|
+
fmtOscillator(row.kdj_d),
|
|
224
|
+
fmtOscillator(row.kdj_j),
|
|
197
225
|
].join(",")),
|
|
198
226
|
"```",
|
|
227
|
+
...(sampledRows.length === rows.length ? [] : ["说明: 分钟指标按抽样分钟与尾盘区间保留,避免无差别灌入全部 1m 序列。"]),
|
|
199
228
|
];
|
|
200
229
|
}
|
|
201
230
|
function stripTrailingZeros(value) {
|
|
202
231
|
return value.replace(/(?:\.0+|(\.\d*?[1-9])0+)$/, "$1");
|
|
203
232
|
}
|
|
233
|
+
function selectSampledIntradayRows(rows) {
|
|
234
|
+
if (rows.length <= MAX_INTRADAY_FULL_ROWS) {
|
|
235
|
+
return rows;
|
|
236
|
+
}
|
|
237
|
+
const selectedIndexes = new Set();
|
|
238
|
+
addIndexRange(selectedIndexes, 0, Math.min(rows.length - 1, MAX_INTRADAY_OPEN_ROWS - 1));
|
|
239
|
+
addIndexRange(selectedIndexes, Math.max(0, rows.length - MAX_INTRADAY_CLOSE_ROWS), rows.length - 1);
|
|
240
|
+
const eventIndexes = [
|
|
241
|
+
findMaxIndex(rows, (row) => row.high),
|
|
242
|
+
findMinIndex(rows, (row) => row.low),
|
|
243
|
+
findMaxIndex(rows, (row) => row.volume),
|
|
244
|
+
findMaxIndex(rows, (row) => row.amount),
|
|
245
|
+
...findTopMoveIndexes(rows, MAX_INTRADAY_EVENT_ROWS),
|
|
246
|
+
];
|
|
247
|
+
for (const index of eventIndexes) {
|
|
248
|
+
if (index >= 0) {
|
|
249
|
+
selectedIndexes.add(index);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return Array.from(selectedIndexes)
|
|
253
|
+
.sort((left, right) => left - right)
|
|
254
|
+
.map((index) => rows[index])
|
|
255
|
+
.filter(Boolean);
|
|
256
|
+
}
|
|
257
|
+
function selectSampledIntradayIndicatorRows(rows, sampledTimes) {
|
|
258
|
+
if (rows.length <= MAX_INTRADAY_FULL_ROWS) {
|
|
259
|
+
return rows;
|
|
260
|
+
}
|
|
261
|
+
const selectedIndexes = new Set();
|
|
262
|
+
rows.forEach((row, index) => {
|
|
263
|
+
if (row.trade_time && sampledTimes.has(row.trade_time)) {
|
|
264
|
+
selectedIndexes.add(index);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
addIndexRange(selectedIndexes, Math.max(0, rows.length - 12), rows.length - 1);
|
|
268
|
+
let indexes = Array.from(selectedIndexes).sort((left, right) => left - right);
|
|
269
|
+
if (indexes.length === 0) {
|
|
270
|
+
indexes = Array.from({ length: Math.min(rows.length, 12) }, (_, offset) => rows.length - Math.min(rows.length, 12) + offset);
|
|
271
|
+
}
|
|
272
|
+
if (indexes.length > MAX_INTRADAY_INDICATOR_ROWS) {
|
|
273
|
+
indexes = Array.from(new Set([
|
|
274
|
+
...indexes.slice(0, Math.min(8, indexes.length)),
|
|
275
|
+
...indexes.slice(-16),
|
|
276
|
+
])).sort((left, right) => left - right);
|
|
277
|
+
}
|
|
278
|
+
return indexes.map((index) => rows[index]).filter(Boolean);
|
|
279
|
+
}
|
|
280
|
+
function addIndexRange(target, start, end) {
|
|
281
|
+
for (let index = start; index <= end; index += 1) {
|
|
282
|
+
if (index >= 0) {
|
|
283
|
+
target.add(index);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function findMaxIndex(rows, score) {
|
|
288
|
+
let bestIndex = -1;
|
|
289
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
290
|
+
rows.forEach((row, index) => {
|
|
291
|
+
const value = score(row);
|
|
292
|
+
if (Number.isFinite(value) && value > bestScore) {
|
|
293
|
+
bestScore = value;
|
|
294
|
+
bestIndex = index;
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
return bestIndex;
|
|
298
|
+
}
|
|
299
|
+
function findMinIndex(rows, score) {
|
|
300
|
+
let bestIndex = -1;
|
|
301
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
302
|
+
rows.forEach((row, index) => {
|
|
303
|
+
const value = score(row);
|
|
304
|
+
if (Number.isFinite(value) && value < bestScore) {
|
|
305
|
+
bestScore = value;
|
|
306
|
+
bestIndex = index;
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
return bestIndex;
|
|
310
|
+
}
|
|
311
|
+
function findTopMoveIndexes(rows, limit) {
|
|
312
|
+
return rows
|
|
313
|
+
.map((row, index) => ({
|
|
314
|
+
index,
|
|
315
|
+
score: Math.abs(row.open > 0 ? (row.close - row.open) / row.open : row.close - row.open),
|
|
316
|
+
}))
|
|
317
|
+
.sort((left, right) => right.score - left.score)
|
|
318
|
+
.slice(0, limit)
|
|
319
|
+
.map((item) => item.index);
|
|
320
|
+
}
|
|
204
321
|
function formatTimestamp(timestamp) {
|
|
205
322
|
return new Intl.DateTimeFormat("sv-SE", {
|
|
206
323
|
timeZone: "Asia/Shanghai",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MxSearchDocument } from "../../types/mx-search.js";
|
|
2
|
-
export declare const NEWS_ANALYSIS_SYSTEM_PROMPT = "\n\u4F60\u662F\u4E00\u4F4D\u4E13\u4E1A\u7684A\u80A1\u8D44\u8BAF\u5206\u6790\u5E08\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u4EC5\u57FA\u4E8E\u63D0\u4F9B\u7684\u65B0\u95FB\u3001\u516C\u544A\u3001\u7814\u62A5\u548C\u4E8B\u4EF6\u4FE1\u606F\uFF0C\u63D0\u70BC\u77ED\u671F\u50AC\u5316\u3001\u98CE\u9669\u70B9\u4E0E\u4FE1\u606F\u9762\u503E\u5411\u3002\n\n\u8F93\u51FA\u8981\u6C42\uFF1A\n1. \u5148\u7ED9\u51FA\u4E00\u6BB5 80-120 \u5B57\u4E2D\u6587\u6838\u5FC3\u7ED3\u8BBA\uFF0C\u4E0D\u8981\u5728\u6B63\u6587\u4E2D\u6DF7\u5165 JSON\u3002\n2. \u6838\u5FC3\u7ED3\u8BBA\u540E\u6309\u4EE5\u4E0B\u5C0F\u8282\u5206\u6BB5\u5C55\u5F00\uFF0C\u6BCF\u8282 1-3 \u53E5\uFF1A\n- \u4E3B\u8981\u50AC\u5316\n- \u4E3B\u8981\u98CE\u9669\n- \u540E\u7EED\u8DDF\u8E2A\u70B9\n3. \u5206\u6BB5\u5185\u5BB9\u4F18\u5148\u5F15\u7528\u8F83\u65B0\u3001\u8F83\u9AD8\u76F8\u5173\u7684\u8D44\u8BAF\uFF0C\u4E0D\u8981\u6CDB\u5316\u590D\u8FF0\u4F4E\u76F8\u5173\u5185\u5BB9\u3002\n4. \u6700\u540E\u8F93\u51FA ```json \u4EE3\u7801\u5757\uFF0C\u7ED3\u6784\u5982\u4E0B\uFF1A\n{\n \"score\":
|
|
2
|
+
export declare const NEWS_ANALYSIS_SYSTEM_PROMPT = "\n\u4F60\u662F\u4E00\u4F4D\u4E13\u4E1A\u7684A\u80A1\u8D44\u8BAF\u5206\u6790\u5E08\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u4EC5\u57FA\u4E8E\u63D0\u4F9B\u7684\u65B0\u95FB\u3001\u516C\u544A\u3001\u7814\u62A5\u548C\u4E8B\u4EF6\u4FE1\u606F\uFF0C\u63D0\u70BC\u77ED\u671F\u50AC\u5316\u3001\u98CE\u9669\u70B9\u4E0E\u4FE1\u606F\u9762\u503E\u5411\u3002\n\n\u8F93\u51FA\u8981\u6C42\uFF1A\n1. \u5148\u7ED9\u51FA\u4E00\u6BB5 80-120 \u5B57\u4E2D\u6587\u6838\u5FC3\u7ED3\u8BBA\uFF0C\u4E0D\u8981\u5728\u6B63\u6587\u4E2D\u6DF7\u5165 JSON\u3002\n2. \u6838\u5FC3\u7ED3\u8BBA\u540E\u6309\u4EE5\u4E0B\u5C0F\u8282\u5206\u6BB5\u5C55\u5F00\uFF0C\u6BCF\u8282 1-3 \u53E5\uFF1A\n- \u4E3B\u8981\u50AC\u5316\n- \u4E3B\u8981\u98CE\u9669\n- \u540E\u7EED\u8DDF\u8E2A\u70B9\n3. \u5206\u6BB5\u5185\u5BB9\u4F18\u5148\u5F15\u7528\u8F83\u65B0\u3001\u8F83\u9AD8\u76F8\u5173\u7684\u8D44\u8BAF\uFF0C\u4E0D\u8981\u6CDB\u5316\u590D\u8FF0\u4F4E\u76F8\u5173\u5185\u5BB9\u3002\n4. \u6700\u540E\u8F93\u51FA ```json \u4EE3\u7801\u5757\uFF0C\u7ED3\u6784\u5982\u4E0B\uFF1A\n{\n \"score\": integer,\n \"bias\": \"positive\" | \"neutral\" | \"negative\",\n \"catalysts\": [\"<\u77ED\u671F\u50AC\u53161>\", \"<\u77ED\u671F\u50AC\u53162>\"],\n \"risks\": [\"<\u4E3B\u8981\u98CE\u96691>\", \"<\u4E3B\u8981\u98CE\u96692>\"],\n \"watch_items\": [\"<\u540E\u7EED\u8DDF\u8E2A\u70B91>\", \"<\u540E\u7EED\u8DDF\u8E2A\u70B92>\"]\n}\n\n\u89C4\u5219\uFF1A\n- score \u4E3A 1-10 \u7684\u6574\u6570\uFF0C\u4EE3\u8868\u8D44\u8BAF\u9762\u5BF9\u80A1\u4EF7\u7684\u652F\u6301\u5F3A\u5F31\u3002\n- bias \u53EA\u80FD\u662F positive / neutral / negative\u3002\n- catalysts / risks / watch_items \u5404\u8F93\u51FA 1-3 \u6761\u3002\n- \u4F18\u5148\u63D0\u53D6\u9AD8\u76F8\u5173\u3001\u8F83\u65B0\u7684\u4FE1\u606F\uFF0C\u4E0D\u8981\u590D\u8FF0\u65E0\u5173\u514D\u8D23\u58F0\u660E\u3002\n- A\u80A1\u8BED\u5883\u4E0B\uFF0C\u516C\u544A\u3001\u4E1A\u7EE9\u9884\u544A/\u5FEB\u62A5\u3001\u76D1\u7BA1\u95EE\u8BE2/\u5904\u7F5A\u3001\u80A1\u4E1C\u589E\u51CF\u6301\u3001\u4E2D\u6807\u8BA2\u5355\u3001\u8D44\u4EA7\u91CD\u7EC4\u3001\u9898\u6750\u50AC\u5316\u7684\u4F18\u5148\u7EA7\u5E94\u9AD8\u4E8E\u6CDB\u5A92\u4F53\u89E3\u8BFB\u3002\n- \u82E5\u8D44\u8BAF\u53EA\u53CD\u6620\u60C5\u7EEA\u6216\u9898\u6750\u7092\u4F5C\uFF0C\u6CA1\u6709\u5F62\u6210\u786C\u50AC\u5316\uFF0C\u5FC5\u987B\u660E\u786E\u6307\u51FA\u6301\u7EED\u6027\u98CE\u9669\u3002\n";
|
|
3
3
|
export declare function buildNewsAnalysisUserPrompt(params: {
|
|
4
4
|
symbol: string;
|
|
5
5
|
companyName: string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const MAX_PROMPT_DOCUMENTS = 6;
|
|
2
|
+
const MAX_TRUNK_LENGTH = 450;
|
|
1
3
|
export const NEWS_ANALYSIS_SYSTEM_PROMPT = `
|
|
2
4
|
你是一位专业的A股资讯分析师。你的任务是仅基于提供的新闻、公告、研报和事件信息,提炼短期催化、风险点与信息面倾向。
|
|
3
5
|
|
|
@@ -10,11 +12,11 @@ export const NEWS_ANALYSIS_SYSTEM_PROMPT = `
|
|
|
10
12
|
3. 分段内容优先引用较新、较高相关的资讯,不要泛化复述低相关内容。
|
|
11
13
|
4. 最后输出 \`\`\`json 代码块,结构如下:
|
|
12
14
|
{
|
|
13
|
-
"score":
|
|
14
|
-
"bias": "neutral",
|
|
15
|
-
"catalysts": ["
|
|
16
|
-
"risks": ["
|
|
17
|
-
"watch_items": ["
|
|
15
|
+
"score": integer,
|
|
16
|
+
"bias": "positive" | "neutral" | "negative",
|
|
17
|
+
"catalysts": ["<短期催化1>", "<短期催化2>"],
|
|
18
|
+
"risks": ["<主要风险1>", "<主要风险2>"],
|
|
19
|
+
"watch_items": ["<后续跟踪点1>", "<后续跟踪点2>"]
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
规则:
|
|
@@ -26,12 +28,13 @@ export const NEWS_ANALYSIS_SYSTEM_PROMPT = `
|
|
|
26
28
|
- 若资讯只反映情绪或题材炒作,没有形成硬催化,必须明确指出持续性风险。
|
|
27
29
|
`;
|
|
28
30
|
export function buildNewsAnalysisUserPrompt(params) {
|
|
31
|
+
const documents = params.documents.slice(0, MAX_PROMPT_DOCUMENTS);
|
|
29
32
|
return [
|
|
30
33
|
`请分析 ${params.companyName}(${params.symbol})最近资讯的信息面影响。`,
|
|
31
34
|
`检索问句: ${params.query}`,
|
|
32
35
|
"",
|
|
33
|
-
|
|
34
|
-
...renderDocuments(
|
|
36
|
+
`## 检索结果(最多取前 ${MAX_PROMPT_DOCUMENTS} 条)`,
|
|
37
|
+
...renderDocuments(documents),
|
|
35
38
|
"",
|
|
36
39
|
"请重点判断:短期催化、核心风险、是否存在一致性乐观/悲观预期,以及接下来需要继续核实的点。",
|
|
37
40
|
].join("\n");
|
|
@@ -43,9 +46,10 @@ function renderDocuments(documents) {
|
|
|
43
46
|
return documents.map((document, index) => {
|
|
44
47
|
const source = document.source ? ` | 来源=${document.source}` : "";
|
|
45
48
|
const time = document.publishedAt ? ` | 时间=${document.publishedAt}` : "";
|
|
49
|
+
const recency = formatRecencyTag(document.publishedAt);
|
|
46
50
|
return [
|
|
47
|
-
`- 第 ${index + 1}
|
|
48
|
-
` 正文摘要=${truncate(document.trunk,
|
|
51
|
+
`- 第 ${index + 1} 条${recency ? ` ${recency}` : ""}: ${document.title}${time}${source}`,
|
|
52
|
+
` 正文摘要=${truncate(document.trunk, MAX_TRUNK_LENGTH)}`,
|
|
49
53
|
].join("\n");
|
|
50
54
|
});
|
|
51
55
|
}
|
|
@@ -55,3 +59,34 @@ function truncate(text, maxLength) {
|
|
|
55
59
|
}
|
|
56
60
|
return `${text.slice(0, maxLength)}...`;
|
|
57
61
|
}
|
|
62
|
+
function formatRecencyTag(publishedAt) {
|
|
63
|
+
if (!publishedAt) {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
const published = parseDateValue(publishedAt);
|
|
67
|
+
if (!published) {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
const now = new Date();
|
|
71
|
+
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
72
|
+
const startOfPublished = new Date(published.getFullYear(), published.getMonth(), published.getDate());
|
|
73
|
+
const diffDays = Math.floor((startOfToday.getTime() - startOfPublished.getTime()) / 86_400_000);
|
|
74
|
+
if (diffDays <= 0) {
|
|
75
|
+
return "[今日]";
|
|
76
|
+
}
|
|
77
|
+
if (diffDays === 1) {
|
|
78
|
+
return "[1天前]";
|
|
79
|
+
}
|
|
80
|
+
return `[${diffDays}天前]`;
|
|
81
|
+
}
|
|
82
|
+
function parseDateValue(value) {
|
|
83
|
+
const normalized = value.trim();
|
|
84
|
+
if (!normalized) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const candidate = normalized.includes("T")
|
|
88
|
+
? normalized
|
|
89
|
+
: normalized.replace(" ", "T");
|
|
90
|
+
const date = new Date(candidate);
|
|
91
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
92
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { PostCloseReviewInput } from "../../analysis/types/composite-analysis.js";
|
|
2
|
-
export declare const POST_CLOSE_REVIEW_SYSTEM_PROMPT
|
|
2
|
+
export declare const POST_CLOSE_REVIEW_SYSTEM_PROMPT: string;
|
|
3
3
|
export declare function buildPostCloseReviewUserPrompt(input: PostCloseReviewInput): string;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { formatCostPrice } from "../../utils/cost-price.js";
|
|
1
|
+
import { formatCostPrice, formatCostRelationship } from "../../utils/cost-price.js";
|
|
2
|
+
import { buildReferencedNarrative, truncatePromptText } from "./prompt-text-utils.js";
|
|
3
|
+
import { indentPromptBlock, KEY_LEVELS_FIELD_GUIDANCE, KEY_LEVELS_JSON_SCHEMA_INNER, } from "./shared-schema.js";
|
|
4
|
+
const MAX_VALIDATION_SUMMARY_LENGTH = 220;
|
|
5
|
+
const MAX_VALIDATION_LINES = 8;
|
|
6
|
+
const MAX_COMPOSITE_BASELINE_LENGTH = 900;
|
|
2
7
|
export const POST_CLOSE_REVIEW_SYSTEM_PROMPT = `
|
|
3
8
|
你是一位A股收盘复盘分析师,需要在收盘后同时完成“昨日关键位验证 + 今日盘面复盘 + 明日关键位处理决定”。
|
|
4
9
|
|
|
@@ -12,37 +17,32 @@ export const POST_CLOSE_REVIEW_SYSTEM_PROMPT = `
|
|
|
12
17
|
- 操作建议
|
|
13
18
|
2. “昨日关键位验证”必须严格依据输入里给出的验证结果,不得改写成与数据冲突的结论。
|
|
14
19
|
3. “明日关键位处理”必须明确给出四选一结论:keep / adjust / recompute / invalidate。
|
|
15
|
-
4. 最后输出一个 \`\`\`json
|
|
20
|
+
4. 最后输出一个 \`\`\`json 代码块,结构如下(其中 levels 为字段类型示意,不是示例值):
|
|
16
21
|
{
|
|
17
|
-
"session_summary":
|
|
18
|
-
"market_sector_summary":
|
|
19
|
-
"news_summary":
|
|
20
|
-
"decision": "keep|adjust|recompute|invalidate",
|
|
21
|
-
"decision_reason":
|
|
22
|
-
"action_advice":
|
|
23
|
-
"market_bias": "tailwind|neutral|headwind",
|
|
24
|
-
"sector_bias": "tailwind|neutral|headwind",
|
|
25
|
-
"news_impact": "supportive|neutral|disruptive",
|
|
22
|
+
"session_summary": string,
|
|
23
|
+
"market_sector_summary": string,
|
|
24
|
+
"news_summary": string,
|
|
25
|
+
"decision": "keep" | "adjust" | "recompute" | "invalidate",
|
|
26
|
+
"decision_reason": string,
|
|
27
|
+
"action_advice": string,
|
|
28
|
+
"market_bias": "tailwind" | "neutral" | "headwind",
|
|
29
|
+
"sector_bias": "tailwind" | "neutral" | "headwind",
|
|
30
|
+
"news_impact": "supportive" | "neutral" | "disruptive",
|
|
26
31
|
"levels": {
|
|
27
|
-
|
|
28
|
-
"stop_loss": 0.0,
|
|
29
|
-
"breakthrough": 0.0,
|
|
30
|
-
"support": 0.0,
|
|
31
|
-
"cost_level": 0.0,
|
|
32
|
-
"resistance": 0.0,
|
|
33
|
-
"take_profit": 0.0,
|
|
34
|
-
"gap": 0.0,
|
|
35
|
-
"target": 0.0,
|
|
36
|
-
"round_number": 0.0,
|
|
37
|
-
"score": 5
|
|
32
|
+
${indentPromptBlock(KEY_LEVELS_JSON_SCHEMA_INNER, 4)}
|
|
38
33
|
}
|
|
39
34
|
}
|
|
40
35
|
|
|
41
36
|
规则:
|
|
42
37
|
- 若 decision=invalidate,levels 可以为 null;否则 levels 必须完整给出。
|
|
43
|
-
-
|
|
38
|
+
- 以下关键价位字段规则必须遵守:
|
|
39
|
+
${KEY_LEVELS_FIELD_GUIDANCE}
|
|
44
40
|
- 若大盘顺风但行业分类/概念板块偏逆风,必须明确指出冲突,不得笼统给多头结论。
|
|
45
41
|
- 若新闻只是噪音,也要明确写“未构成主要解释”或类似表述。
|
|
42
|
+
- keep: 昨日关键位整体验证有效,今日盘面未破坏原逻辑,明日可直接沿用。
|
|
43
|
+
- adjust: 方向未变,但支撑、压力、突破、止损或止盈只需小幅平移。
|
|
44
|
+
- recompute: 今日出现明显放量突破、破位、结构切换或外部催化改变,原逻辑需要重算。
|
|
45
|
+
- invalidate: 昨日关键位框架已失效,明日不应继续沿用;此时 levels 可为 null。
|
|
46
46
|
- 不要凭空编造概念板块、指数表现或公告内容。
|
|
47
47
|
`;
|
|
48
48
|
export function buildPostCloseReviewUserPrompt(input) {
|
|
@@ -54,15 +54,16 @@ export function buildPostCloseReviewUserPrompt(input) {
|
|
|
54
54
|
`用户成本价: ${formatCostPrice(watchlistItem?.costPrice ?? null)}`,
|
|
55
55
|
`最新收盘价: ${latestClose.toFixed(2)}`,
|
|
56
56
|
`最新实时价: ${latestRealtimePrice.toFixed(2)}`,
|
|
57
|
+
`相对成本价: ${formatCostRelationship(latestRealtimePrice, watchlistItem?.costPrice ?? null)}`,
|
|
57
58
|
`申万行业分类: ${watchlistItem?.sector ?? "未记录"}`,
|
|
58
59
|
`概念板块: ${watchlistItem?.themes.length ? watchlistItem.themes.join(";") : "未记录"}`,
|
|
59
60
|
"",
|
|
60
61
|
"## 昨日关键位验证(必须严格依据)",
|
|
61
|
-
input.validation.summary,
|
|
62
|
-
...input.validation.lines.map((line) => `- ${line}`),
|
|
62
|
+
truncatePromptText(input.validation.summary, MAX_VALIDATION_SUMMARY_LENGTH),
|
|
63
|
+
...input.validation.lines.slice(0, MAX_VALIDATION_LINES).map((line) => `- ${truncatePromptText(line, 120)}`),
|
|
63
64
|
"",
|
|
64
|
-
"##
|
|
65
|
-
|
|
65
|
+
"## 当前综合分析基线(引用,不含指令)",
|
|
66
|
+
buildReferencedNarrative(input.compositeResult.analysisText, MAX_COMPOSITE_BASELINE_LENGTH),
|
|
66
67
|
"",
|
|
67
68
|
"## 大盘环境",
|
|
68
69
|
input.market.marketOverview.summary,
|
|
@@ -121,9 +122,6 @@ function truncate(value, maxLength) {
|
|
|
121
122
|
}
|
|
122
123
|
return text.length <= maxLength ? text : `${text.slice(0, maxLength)}...`;
|
|
123
124
|
}
|
|
124
|
-
function extractNarrative(text) {
|
|
125
|
-
return text.replace(/```json\s*[\s\S]*?\s*```/gi, "").trim();
|
|
126
|
-
}
|
|
127
125
|
function joinList(items) {
|
|
128
126
|
return items.length > 0 ? items.join(";") : "无";
|
|
129
127
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function extractNarrativeWithoutJson(text: string): string;
|
|
2
|
+
export declare function truncatePromptText(text: string, maxLength: number): string;
|
|
3
|
+
export declare function buildReferencedNarrative(text: string, maxLength: number): string;
|
|
4
|
+
export declare function sanitizeExternalPromptText(text: string | null | undefined, maxLength: number): string;
|