tickflow-assist 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +11 -42
  2. package/dist/analysis/types/composite-analysis.d.ts +27 -0
  3. package/dist/background/realtime-monitor.worker.d.ts +1 -1
  4. package/dist/background/realtime-monitor.worker.js +3 -4
  5. package/dist/bootstrap.js +24 -4
  6. package/dist/config/tickflow-access.d.ts +2 -1
  7. package/dist/config/tickflow-access.js +10 -3
  8. package/dist/dev/run-monitor-loop.js +0 -1
  9. package/dist/dev/tickflow-assist-cli.js +4 -3
  10. package/dist/dev/validate-mx-search.js +10 -2
  11. package/dist/plugin-commands.js +27 -0
  12. package/dist/plugin.js +4 -6
  13. package/dist/prompts/analysis/kline-analysis-user-prompt.js +2 -1
  14. package/dist/prompts/analysis/post-close-review-user-prompt.js +40 -1
  15. package/dist/prompts/analysis/pre-market-brief-prompt.d.ts +3 -1
  16. package/dist/prompts/analysis/pre-market-brief-prompt.js +8 -3
  17. package/dist/services/industry-peer-service.d.ts +9 -0
  18. package/dist/services/industry-peer-service.js +152 -0
  19. package/dist/services/jin10-flash-monitor-service.js +2 -1
  20. package/dist/services/monitor-service.d.ts +1 -1
  21. package/dist/services/monitor-service.js +21 -26
  22. package/dist/services/mx-search-service.d.ts +8 -1
  23. package/dist/services/mx-search-service.js +400 -10
  24. package/dist/services/post-close-review-service.d.ts +11 -4
  25. package/dist/services/post-close-review-service.js +113 -10
  26. package/dist/services/pre-market-brief-service.js +500 -42
  27. package/dist/services/tickflow-client.d.ts +4 -1
  28. package/dist/services/tickflow-client.js +32 -0
  29. package/dist/services/tickflow-universe-service.d.ts +26 -0
  30. package/dist/services/tickflow-universe-service.js +213 -0
  31. package/dist/services/watchlist-profile-service.d.ts +4 -1
  32. package/dist/services/watchlist-profile-service.js +58 -29
  33. package/dist/services/watchlist-service.d.ts +5 -1
  34. package/dist/services/watchlist-service.js +9 -4
  35. package/dist/storage/repositories/universe-membership-repo.d.ts +11 -0
  36. package/dist/storage/repositories/universe-membership-repo.js +38 -0
  37. package/dist/storage/repositories/universe-repo.d.ts +17 -0
  38. package/dist/storage/repositories/universe-repo.js +62 -0
  39. package/dist/storage/schemas.d.ts +2 -0
  40. package/dist/storage/schemas.js +13 -0
  41. package/dist/tools/add-stock.tool.d.ts +2 -1
  42. package/dist/tools/add-stock.tool.js +10 -1
  43. package/dist/tools/eastmoney-watchlist.tool.d.ts +31 -0
  44. package/dist/tools/eastmoney-watchlist.tool.js +294 -0
  45. package/dist/tools/mx-data.tool.d.ts +8 -0
  46. package/dist/tools/mx-data.tool.js +94 -0
  47. package/dist/tools/mx-select-stock.tool.js +6 -2
  48. package/dist/tools/query-database.tool.js +6 -0
  49. package/dist/tools/refresh-watchlist-profiles.tool.d.ts +2 -1
  50. package/dist/tools/refresh-watchlist-profiles.tool.js +11 -1
  51. package/dist/tools/screen-stock-candidates.tool.d.ts +34 -0
  52. package/dist/tools/screen-stock-candidates.tool.js +477 -0
  53. package/dist/tools/test-alert.tool.js +56 -19
  54. package/dist/types/mx-data.d.ts +23 -0
  55. package/dist/types/mx-data.js +1 -0
  56. package/dist/types/mx-select-stock.d.ts +1 -0
  57. package/dist/types/mx-self-select.d.ts +30 -0
  58. package/dist/types/mx-self-select.js +1 -0
  59. package/dist/types/tickflow.d.ts +12 -0
  60. package/dist/utils/tickflow-quote.d.ts +5 -0
  61. package/dist/utils/tickflow-quote.js +31 -0
  62. package/openclaw.plugin.json +83 -6
  63. package/package.json +6 -6
  64. package/skills/stock-analysis/SKILL.md +39 -20
  65. package/skills/usage-help/SKILL.md +33 -0
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  基于 [OpenClaw](https://openclaw.ai) 的 A 股监控与分析插件。它使用 [TickFlow](https://tickflow.org/auth/register?ref=BUJ54JEDGE) 获取行情与财务数据,并可选接入 [金十数据 MCP](https://mcp.jin10.com/app/) 快讯流,结合 LLM 生成技术面、基本面、资讯面的综合判断,并把结果持久化到本地 LanceDB。
4
4
 
5
- 最近更新:`v0.3.5` 对齐 OpenClaw `2026.4.11` metadata 与社区安装提示,修复源码升级时本地链接扫描 `node_modules` 失败的问题,并支持通过环境变量回退 TickFlow / LLM / MX / Jin10 配置。完整发布记录见 <https://github.com/robinspt/tickflow-assist/blob/main/CHANGELOG.md>。
5
+ 最近更新:`v0.3.8` 接入东方财富自选同步、妙想官方数据/智能选股兼容与小规模候选池联动,新增 `/ta_screenstocks` / `/ta_screenstocks_llm`,并优化盘前简报提炼和监控阶段提醒去重。完整发布记录见 <https://github.com/robinspt/tickflow-assist/blob/main/CHANGELOG.md>。
6
6
 
7
- 当前主线按 OpenClaw `v2026.3.31+` 对齐,并已验证社区安装在 `v2026.4.11` 上兼容。
7
+ 当前主线按 OpenClaw `v2026.3.31+` 对齐,并已验证社区安装在 `v2026.4.22` 上兼容。
8
8
 
9
9
  ## 安装前准备
10
10
 
@@ -32,41 +32,9 @@ openclaw config validate
32
32
  openclaw gateway restart
33
33
  ```
34
34
 
35
- 安装阶段允许先落插件,再通过第二条命令写入 `tickflowApiKey`、`llmApiKey`、`llmBaseUrl`、`llmModel` 等正式配置。
36
- `configure-openclaw` 会写入 `~/.openclaw/openclaw.json` 中的 `plugins.entries["tickflow-assist"].config`,并打印后续建议执行的命令;它不再自动执行 `openclaw`、`uv` 或系统包安装命令,也不会重新执行插件安装;如果你已经设置了环境变量,密钥项可留空,输入 `-` 可主动清空已有配置并切回环境变量。
37
- 如果检测到 `plugins.installs["tickflow-assist"]` 来自 `clawhub`,向导还会把被旧版本钉死的 `spec` 归一化为 `clawhub:tickflow-assist`,避免后续升级继续锁在旧版本。
38
-
39
- 如果你希望先审阅配置,再只打印最少的后续步骤,可使用:
40
-
41
- ```bash
42
- node ~/.openclaw/extensions/tickflow-assist/dist/dev/tickflow-assist-cli.js configure-openclaw --no-enable --no-restart
43
- ```
44
-
45
- 如果你在 Linux 或 macOS 上需要 PNG 告警卡正常显示中文,请额外手动安装 `fontconfig` 与 Noto CJK 一类中文字体,例如:
46
-
47
- ```bash
48
- # Debian / Ubuntu
49
- sudo apt-get update
50
- sudo apt-get install -y fontconfig fonts-noto-cjk
51
- fc-cache -fv
52
-
53
- # RHEL / Fedora / Rocky / AlmaLinux
54
- sudo dnf install -y fontconfig google-noto-sans-cjk-ttc-fonts
55
- fc-cache -fv
56
-
57
- # Arch / Manjaro
58
- sudo pacman -Sy --noconfirm fontconfig noto-fonts-cjk
59
- fc-cache -fv
60
-
61
- # Alpine
62
- sudo apk add fontconfig font-noto-cjk
63
- fc-cache -fv
64
-
65
- # macOS (Homebrew)
66
- brew install fontconfig
67
- brew install --cask font-noto-sans-cjk
68
- fc-cache -fv
69
- ```
35
+ - `configure-openclaw` 会把配置写入 `~/.openclaw/openclaw.json` 的 `plugins.entries["tickflow-assist"].config`。
36
+ - 核心必填建议先准备:`tickflowApiKey`、`tickflowApiKeyLevel`、`llmApiKey`、`llmBaseUrl`、`llmModel`;告警场景再补 `alertChannel`、`alertTarget`、`alertAccount`。
37
+ - 如果你不想把密钥落盘,优先把环境变量写进 `~/.openclaw/.env`,再运行配置向导补齐非密钥项;如需 PNG 告警卡正常显示中文,请自行安装 `fontconfig` Noto CJK 字体。
70
38
 
71
39
  社区安装后的升级方式:
72
40
 
@@ -96,7 +64,7 @@ plugins.entries["tickflow-assist"].config
96
64
  - 告警投递:`alertChannel`、`alertTarget`、`alertAccount`
97
65
  - 能力补充:`mxSearchApiKey`、`jin10ApiToken`
98
66
 
99
- 其中,`mxSearchApiKey` 用于 `mx_search`、`mx_select_stock` 以及非 `Expert` 财务链路的 lite 补充;`jin10ApiToken` 用于 24 小时金十数据快讯监控;`jin10FlashNightAlert` 默认 `false`(开启夜间静默),设为 `true` 可恢复 24 小时快讯告警;`alertTarget`、`alertAccount` 建议在准备启用 `test_alert`、实时监控告警、金十数据快讯告警和定时通知前一并配好,避免配置不完整导致功能缺失。
67
+ 其中,`mxSearchApiKey` 用于 `mx_search`、`mx_data`、`mx_select_stock`、东方财富自选同步以及非 `Expert` 财务链路的 lite 补充;东方财富自选管理接口每日额度 200 次;`jin10ApiToken` 用于 24 小时金十数据快讯监控;`jin10FlashNightAlert` 默认 `false`(开启夜间静默),设为 `true` 可恢复 24 小时快讯告警;`alertTarget`、`alertAccount` 建议在准备启用 `test_alert`、实时监控告警、金十数据快讯告警和定时通知前一并配好,避免配置不完整导致功能缺失。
100
68
  如果你使用环境变量,运行时支持以下回退:
101
69
 
102
70
  - `tickflowApiUrl`:`TICKFLOW_ASSIST_TICKFLOW_API_URL` / `TICKFLOW_API_URL`
@@ -112,7 +80,8 @@ plugins.entries["tickflow-assist"].config
112
80
 
113
81
  ## 功能
114
82
 
115
- - 自选股管理、日 K / 分钟 K 抓取与指标计算
83
+ - 自选股管理、东方财富自选同步、日 K / 分钟 K 抓取与指标计算
84
+ - 妙想资讯搜索、官方金融数据查询、智能选股,以及限量候选池 + TickFlow 补数据联动
116
85
  - 技术面、财务面、资讯面的综合分析
117
86
  - 实时监控、定时日更、收盘后复盘
118
87
  - 金十数据 24 小时快讯监控与自选关联提醒
@@ -126,9 +95,9 @@ plugins.entries["tickflow-assist"].config
126
95
 
127
96
  ## 依赖与可选能力
128
97
 
129
- - [TickFlow](https://tickflow.org/auth/register?ref=BUJ54JEDGE):提供日线、分钟线、实时行情与财务数据接口。
130
- - [金十数据 MCP](https://mcp.jin10.com/app/):可选,用于 24 小时快讯流接入、自选关联筛选与事件驱动告警。
131
- - [东方财富妙想 Skills](https://marketing.dfcfs.com/views/finskillshub/):可选,用于 `mx_search`、`mx_select_stock` 与非 `Expert` 财务链路的 lite 补充。
98
+ - [TickFlow](https://tickflow.org/auth/register?ref=BUJ54JEDGE):`Free` 可用日线与实时行情;`Starter` 起可用标的池,插件会用来做申万行业映射与申万 3 级同业表现;`Pro` 起可用分钟K;`Expert` 才走 TickFlow 财务数据,非 `Expert` 默认回退妙想 lite。
99
+ - [金十数据 MCP](https://mcp.jin10.com/app/):可选,用于 24 小时快讯流接入、自选关联筛选与事件驱动告警。独立的金十数据 Skill 详见 [OpenClaw Skill](https://clawhub.ai/robinspt/jin10) / [Hermes Skill](https://github.com/robinspt/hermes-skills)。
100
+ - [东方财富妙想 Skills](https://marketing.dfcfs.com/views/finskillshub/):可选,用于 `mx_search`、`mx_data`、`mx_select_stock`、东方财富自选同步与非 `Expert` 财务链路的 lite 补充。
132
101
 
133
102
  ## 仓库
134
103
 
@@ -114,10 +114,37 @@ export interface FlashNewsContext {
114
114
  stockAlerts: FlashNewsItem[];
115
115
  marketOverviewFlashes: FlashNewsItem[];
116
116
  }
117
+ export interface IndustryPeerMover {
118
+ symbol: string;
119
+ name: string;
120
+ changePct: number;
121
+ }
122
+ export interface IndustryPeerContext {
123
+ available: boolean;
124
+ summary: string;
125
+ sw1Name: string | null;
126
+ sw2Name: string | null;
127
+ sw3Name: string | null;
128
+ sw3UniverseId: string | null;
129
+ peerCount: number;
130
+ otherStockCount: number;
131
+ advanceCount: number;
132
+ declineCount: number;
133
+ flatCount: number;
134
+ averageChangePct: number | null;
135
+ medianChangePct: number | null;
136
+ targetChangePct: number | null;
137
+ targetRank: number | null;
138
+ targetPercentile: number | null;
139
+ leaders: IndustryPeerMover[];
140
+ laggards: IndustryPeerMover[];
141
+ note: string | null;
142
+ }
117
143
  export interface PostCloseReviewInput extends CompositeAnalysisInput {
118
144
  compositeResult: CompositeAnalysisResult;
119
145
  validation: PriorKeyLevelValidationContext;
120
146
  flashContext: FlashNewsContext;
147
+ peerContext: IndustryPeerContext;
121
148
  }
122
149
  export interface PostCloseReviewResult {
123
150
  analysisText: string;
@@ -3,6 +3,6 @@ export declare class RealtimeMonitorWorker {
3
3
  private readonly monitorService;
4
4
  private readonly intervalMs;
5
5
  constructor(monitorService: MonitorService, intervalMs: number);
6
- runOnce(): Promise<number>;
6
+ runOnce(runtimeHost?: "plugin_service" | "fallback_process"): Promise<number>;
7
7
  runLoop(signal?: AbortSignal, runtimeHost?: "plugin_service" | "fallback_process"): Promise<void>;
8
8
  }
@@ -6,18 +6,17 @@ export class RealtimeMonitorWorker {
6
6
  this.monitorService = monitorService;
7
7
  this.intervalMs = intervalMs;
8
8
  }
9
- async runOnce() {
9
+ async runOnce(runtimeHost) {
10
10
  const state = await this.monitorService.getState();
11
11
  if (!state.running) {
12
12
  return 0;
13
13
  }
14
- return this.monitorService.runMonitorOnce();
14
+ return this.monitorService.runMonitorOnce(runtimeHost);
15
15
  }
16
16
  async runLoop(signal, runtimeHost) {
17
17
  while (!signal?.aborted) {
18
- await this.monitorService.recordHeartbeat(runtimeHost);
19
18
  try {
20
- await this.runOnce();
19
+ await this.runOnce(runtimeHost);
21
20
  }
22
21
  catch (error) {
23
22
  await this.monitorService.recordLoopError(error);
package/dist/bootstrap.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { supportsUniverseAccess } from "./config/tickflow-access.js";
2
3
  import { TickFlowClient } from "./services/tickflow-client.js";
3
4
  import { InstrumentService } from "./services/instrument-service.js";
4
5
  import { KlineService } from "./services/kline-service.js";
@@ -22,6 +23,8 @@ import { NewsAnalysisRepository } from "./storage/repositories/news-analysis-rep
22
23
  import { CompositeAnalysisRepository } from "./storage/repositories/composite-analysis-repo.js";
23
24
  import { Jin10FlashRepository } from "./storage/repositories/jin10-flash-repo.js";
24
25
  import { Jin10FlashDeliveryRepository } from "./storage/repositories/jin10-flash-delivery-repo.js";
26
+ import { UniverseRepository } from "./storage/repositories/universe-repo.js";
27
+ import { UniverseMembershipRepository } from "./storage/repositories/universe-membership-repo.js";
25
28
  import { WatchlistService } from "./services/watchlist-service.js";
26
29
  import { WatchlistProfileService } from "./services/watchlist-profile-service.js";
27
30
  import { AnalysisService } from "./services/analysis-service.js";
@@ -37,6 +40,8 @@ import { KeyLevelsBacktestService } from "./services/key-levels-backtest-service
37
40
  import { PostCloseReviewService } from "./services/post-close-review-service.js";
38
41
  import { PreMarketBriefService } from "./services/pre-market-brief-service.js";
39
42
  import { ReviewMemoryService } from "./services/review-memory-service.js";
43
+ import { TickFlowUniverseService } from "./services/tickflow-universe-service.js";
44
+ import { IndustryPeerService } from "./services/industry-peer-service.js";
40
45
  import { CompositeAnalysisOrchestrator } from "./analysis/orchestrators/composite-analysis.orchestrator.js";
41
46
  import { MarketAnalysisProvider } from "./analysis/providers/market-analysis.provider.js";
42
47
  import { FinancialAnalysisProvider } from "./analysis/providers/financial-analysis.provider.js";
@@ -53,8 +58,10 @@ import { fetchKlinesTool } from "./tools/fetch-klines.tool.js";
53
58
  import { fetchIntradayKlinesTool } from "./tools/fetch-intraday-klines.tool.js";
54
59
  import { fetchFinancialsTool } from "./tools/fetch-financials.tool.js";
55
60
  import { flashMonitorStatusTool } from "./tools/flash-monitor-status.tool.js";
61
+ import { mxDataTool } from "./tools/mx-data.tool.js";
56
62
  import { mxSearchTool } from "./tools/mx-search.tool.js";
57
63
  import { mxSelectStockTool } from "./tools/mx-select-stock.tool.js";
64
+ import { listEastmoneyWatchlistTool, pushEastmoneyWatchlistTool, removeEastmoneyWatchlistTool, syncEastmoneyWatchlistTool, } from "./tools/eastmoney-watchlist.tool.js";
58
65
  import { listWatchlistTool } from "./tools/list-watchlist.tool.js";
59
66
  import { dailyUpdateStatusTool } from "./tools/daily-update-status.tool.js";
60
67
  import { monitorStatusTool } from "./tools/monitor-status.tool.js";
@@ -62,6 +69,7 @@ import { refreshWatchlistNamesTool } from "./tools/refresh-watchlist-names.tool.
62
69
  import { refreshWatchlistProfilesTool } from "./tools/refresh-watchlist-profiles.tool.js";
63
70
  import { queryDatabaseTool } from "./tools/query-database.tool.js";
64
71
  import { removeStockTool } from "./tools/remove-stock.tool.js";
72
+ import { screenStockCandidatesTool } from "./tools/screen-stock-candidates.tool.js";
65
73
  import { startDailyUpdateTool } from "./tools/start-daily-update.tool.js";
66
74
  import { startMonitorTool } from "./tools/start-monitor.tool.js";
67
75
  import { stopDailyUpdateTool } from "./tools/stop-daily-update.tool.js";
@@ -100,6 +108,8 @@ export function createAppContext(config, options = {}) {
100
108
  const compositeAnalysisRepository = new CompositeAnalysisRepository(database);
101
109
  const jin10FlashRepository = new Jin10FlashRepository(database);
102
110
  const jin10FlashDeliveryRepository = new Jin10FlashDeliveryRepository(database);
111
+ const universeRepository = new UniverseRepository(database);
112
+ const universeMembershipRepository = new UniverseMembershipRepository(database);
103
113
  const instrumentService = new InstrumentService(tickflowClient);
104
114
  const klineService = new KlineService(tickflowClient);
105
115
  const quoteService = new QuoteService(tickflowClient);
@@ -108,7 +118,10 @@ export function createAppContext(config, options = {}) {
108
118
  const jin10McpService = new Jin10McpService(config.jin10McpUrl, config.jin10ApiToken);
109
119
  const financialLiteService = new FinancialLiteService(mxApiService);
110
120
  const analysisService = new AnalysisService(config.llmBaseUrl, config.llmApiKey, config.llmModel, analysisLogRepository);
111
- const watchlistProfileService = new WatchlistProfileService(mxApiService, analysisService);
121
+ const tickFlowUniverseService = supportsUniverseAccess(config.tickflowApiKeyLevel)
122
+ ? new TickFlowUniverseService(tickflowClient, universeRepository, universeMembershipRepository)
123
+ : null;
124
+ const watchlistProfileService = new WatchlistProfileService(tickFlowUniverseService, mxApiService, analysisService);
112
125
  const tradingCalendarService = new TradingCalendarService(config.calendarFile);
113
126
  const alertDiagnosticLogger = createAlertDiagnosticLogger(config.databasePath);
114
127
  const alertService = new AlertService({
@@ -143,7 +156,8 @@ export function createAppContext(config, options = {}) {
143
156
  const monitorService = new MonitorService(config.databasePath, config.requestInterval, config.alertChannel, watchlistService, quoteService, tradingCalendarService, keyLevelsRepository, alertLogRepository, klinesRepository, intradayKlinesRepository, klineService, alertService, alertMediaService, alertDiagnosticLogger);
144
157
  const jin10FlashMonitorService = new Jin10FlashMonitorService(config.databasePath, config.jin10FlashPollInterval, config.jin10FlashRetentionDays, config.jin10FlashNightAlert, watchlistService, jin10McpService, analysisService, alertService, jin10FlashRepository, jin10FlashDeliveryRepository);
145
158
  const updateService = new UpdateService(klineService, config.tickflowApiKeyLevel, indicatorService, klinesRepository, indicatorsRepository, intradayKlinesRepository, watchlistService, tradingCalendarService);
146
- const postCloseReviewService = new PostCloseReviewService(watchlistService, compositeAnalysisOrchestrator, analysisService, postCloseReviewTask, keyLevelsRepository, keyLevelsHistoryRepository, klinesRepository, intradayKlinesRepository, jin10FlashDeliveryRepository, jin10FlashRepository);
159
+ const industryPeerService = new IndustryPeerService(tickFlowUniverseService, quoteService);
160
+ const postCloseReviewService = new PostCloseReviewService(watchlistService, compositeAnalysisOrchestrator, analysisService, postCloseReviewTask, keyLevelsRepository, keyLevelsHistoryRepository, klinesRepository, intradayKlinesRepository, jin10FlashDeliveryRepository, jin10FlashRepository, industryPeerService);
147
161
  const preMarketBriefService = new PreMarketBriefService(watchlistService, jin10McpService, jin10FlashRepository, analysisService);
148
162
  const realtimeMonitorWorker = new RealtimeMonitorWorker(monitorService, config.requestInterval * 1000);
149
163
  const jin10FlashWorker = new Jin10FlashWorker(jin10FlashMonitorService, config.jin10FlashPollInterval * 1000);
@@ -153,7 +167,7 @@ export function createAppContext(config, options = {}) {
153
167
  return {
154
168
  config,
155
169
  tools: [
156
- addStockTool(watchlistService, klineService, klinesRepository, indicatorService, indicatorsRepository),
170
+ addStockTool(config.tickflowApiKeyLevel, watchlistService, klineService, klinesRepository, indicatorService, indicatorsRepository),
157
171
  analyzeTool(compositeAnalysisOrchestrator),
158
172
  backtestKeyLevelsTool(keyLevelsBacktestService),
159
173
  dailyUpdateStatusTool(dailyUpdateWorker, runtime.configSource),
@@ -162,15 +176,21 @@ export function createAppContext(config, options = {}) {
162
176
  flashMonitorStatusTool(jin10FlashMonitorService),
163
177
  fetchKlinesTool(klineService, klinesRepository, indicatorService, indicatorsRepository),
164
178
  listWatchlistTool(watchlistService),
179
+ listEastmoneyWatchlistTool(mxApiService),
165
180
  monitorStatusTool(monitorService),
181
+ mxDataTool(mxApiService),
166
182
  mxSearchTool(mxApiService),
167
183
  mxSelectStockTool(mxApiService),
184
+ pushEastmoneyWatchlistTool(mxApiService, watchlistService),
168
185
  queryDatabaseTool(database),
169
186
  refreshWatchlistNamesTool(watchlistService),
170
- refreshWatchlistProfilesTool(watchlistService),
187
+ refreshWatchlistProfilesTool(config.tickflowApiKeyLevel, watchlistService),
188
+ removeEastmoneyWatchlistTool(mxApiService),
171
189
  removeStockTool(watchlistService),
190
+ screenStockCandidatesTool(config.tickflowApiKeyLevel, mxApiService, quoteService, klineService, financialService, watchlistService, analysisService),
172
191
  startDailyUpdateTool(dailyUpdateWorker, config, runtime.configSource, runtime),
173
192
  startMonitorTool(monitorService, runtime),
193
+ syncEastmoneyWatchlistTool(mxApiService, watchlistService),
174
194
  stopDailyUpdateTool(dailyUpdateWorker, runtime),
175
195
  stopMonitorTool(monitorService, runtime),
176
196
  testAlertTool(alertService, alertMediaService, runtime.configSource),
@@ -1,4 +1,5 @@
1
- export type TickflowApiKeyLevel = "free" | "start" | "pro" | "expert";
1
+ export type TickflowApiKeyLevel = "free" | "starter" | "pro" | "expert";
2
2
  export declare function normalizeTickflowApiKeyLevel(value: unknown, fallback?: TickflowApiKeyLevel): TickflowApiKeyLevel;
3
3
  export declare function supportsIntradayKlines(level: TickflowApiKeyLevel): boolean;
4
+ export declare function supportsUniverseAccess(level: TickflowApiKeyLevel): boolean;
4
5
  export declare function formatTickflowApiKeyLevel(level: TickflowApiKeyLevel): string;
@@ -1,4 +1,5 @@
1
1
  const INTRADAY_ENABLED_LEVELS = new Set(["pro", "expert"]);
2
+ const UNIVERSE_ENABLED_LEVELS = new Set(["starter", "pro", "expert"]);
2
3
  export function normalizeTickflowApiKeyLevel(value, fallback = "free") {
3
4
  const normalized = String(value ?? "")
4
5
  .trim()
@@ -6,7 +7,10 @@ export function normalizeTickflowApiKeyLevel(value, fallback = "free") {
6
7
  if (normalized === "export") {
7
8
  return "expert";
8
9
  }
9
- if (normalized === "free" || normalized === "start" || normalized === "pro" || normalized === "expert") {
10
+ if (normalized === "start") {
11
+ return "starter";
12
+ }
13
+ if (normalized === "free" || normalized === "starter" || normalized === "pro" || normalized === "expert") {
10
14
  return normalized;
11
15
  }
12
16
  return fallback;
@@ -14,12 +18,15 @@ export function normalizeTickflowApiKeyLevel(value, fallback = "free") {
14
18
  export function supportsIntradayKlines(level) {
15
19
  return INTRADAY_ENABLED_LEVELS.has(level);
16
20
  }
21
+ export function supportsUniverseAccess(level) {
22
+ return UNIVERSE_ENABLED_LEVELS.has(level);
23
+ }
17
24
  export function formatTickflowApiKeyLevel(level) {
18
25
  switch (level) {
19
26
  case "free":
20
27
  return "Free";
21
- case "start":
22
- return "Start";
28
+ case "starter":
29
+ return "Starter";
23
30
  case "pro":
24
31
  return "Pro";
25
32
  case "expert":
@@ -9,7 +9,6 @@ async function main() {
9
9
  const flashWorker = app.services.jin10FlashWorker;
10
10
  const alertService = app.services.alertService;
11
11
  const monitorService = app.services.monitorService;
12
- await monitorService.recordHeartbeat("fallback_process");
13
12
  await monitorService.setWorkerPid(process.pid);
14
13
  process.stdout.write(`TickFlow realtime loop started, price_interval=${config.requestInterval}s, jin10_interval=${config.jin10FlashPollInterval}s\n`);
15
14
  const controller = new AbortController();
@@ -46,7 +46,7 @@ Options:
46
46
  --no-font-setup Do not print Chinese font setup guidance
47
47
  --openclaw-bin <path> OpenClaw CLI binary name used in printed next steps
48
48
  --tickflow-api-key <key>
49
- --tickflow-api-key-level <Free|Start|Pro|Expert>
49
+ --tickflow-api-key-level <Free|Starter|Pro|Expert>
50
50
  --mx-search-api-key <key>
51
51
  --jin10-mcp-url <url>
52
52
  --jin10-api-token <token>
@@ -198,7 +198,8 @@ function normalizeApiKeyLevel(value) {
198
198
  case "free":
199
199
  return "Free";
200
200
  case "start":
201
- return "Start";
201
+ case "starter":
202
+ return "Starter";
202
203
  case "pro":
203
204
  return "Pro";
204
205
  case "expert":
@@ -485,7 +486,7 @@ async function promptForConfig(options, existing, pluginDir, configPath) {
485
486
  });
486
487
  seed.tickflowApiKeyLevel = normalizeApiKeyLevel(await promptSelect(rl, "TickFlow 订阅等级", [
487
488
  { value: "Free", label: "Free" },
488
- { value: "Start", label: "Start" },
489
+ { value: "Starter", label: "Starter" },
489
490
  { value: "Pro", label: "Pro" },
490
491
  { value: "Expert", label: "Expert" },
491
492
  ], seed.tickflowApiKeyLevel));
@@ -7,8 +7,12 @@ import { buildWatchlistProfileExtractionUserPrompt, } from "../prompts/analysis/
7
7
  import { AnalysisService } from "../services/analysis-service.js";
8
8
  import { MxApiService, normalizeMxSearchDocuments } from "../services/mx-search-service.js";
9
9
  import { buildBoardNewsQuery, formatWatchlistProfileDocuments, WatchlistProfileService, } from "../services/watchlist-profile-service.js";
10
+ import { TickFlowClient } from "../services/tickflow-client.js";
11
+ import { TickFlowUniverseService } from "../services/tickflow-universe-service.js";
10
12
  import { Database } from "../storage/db.js";
11
13
  import { AnalysisLogRepository } from "../storage/repositories/analysis-log-repo.js";
14
+ import { UniverseMembershipRepository } from "../storage/repositories/universe-membership-repo.js";
15
+ import { UniverseRepository } from "../storage/repositories/universe-repo.js";
12
16
  import { WatchlistRepository } from "../storage/repositories/watchlist-repo.js";
13
17
  async function main() {
14
18
  runFixtureValidation();
@@ -119,7 +123,7 @@ function runFixtureValidation() {
119
123
  sector: "计算机-软件开发-垂直应用软件",
120
124
  themes: ["OpenClaw概念", "托育概念", "一体机概念", "信创"],
121
125
  });
122
- assert.equal(boardQuery, "计算机-软件开发-垂直应用软件 OpenClaw概念 托育概念 一体机概念 板块 题材 最新新闻 政策 资金");
126
+ assert.equal(boardQuery, "计算机 软件开发 垂直应用软件 OpenClaw概念 托育概念 一体机概念 板块 题材 最新新闻 政策 资金");
123
127
  const emptyBoardQuery = buildBoardNewsQuery({
124
128
  sector: null,
125
129
  themes: [],
@@ -135,7 +139,11 @@ async function runLiveValidation(limit) {
135
139
  const database = new Database(config.databasePath);
136
140
  const watchlistRepository = new WatchlistRepository(database);
137
141
  const analysisLogRepository = new AnalysisLogRepository(database);
142
+ const universeRepository = new UniverseRepository(database);
143
+ const universeMembershipRepository = new UniverseMembershipRepository(database);
138
144
  const mxApiService = new MxApiService(config.mxSearchApiUrl, config.mxSearchApiKey);
145
+ const tickFlowClient = new TickFlowClient(config.tickflowApiUrl, config.tickflowApiKey);
146
+ const tickFlowUniverseService = new TickFlowUniverseService(tickFlowClient, universeRepository, universeMembershipRepository);
139
147
  const analysisService = new AnalysisService(config.llmBaseUrl, config.llmApiKey, config.llmModel, analysisLogRepository);
140
148
  const mxConfigError = mxApiService.getConfigurationError();
141
149
  if (mxConfigError) {
@@ -152,7 +160,7 @@ async function runLiveValidation(limit) {
152
160
  console.log("[validate:mx-search] live validation skipped: no samples available");
153
161
  return;
154
162
  }
155
- const watchlistProfileService = new WatchlistProfileService(mxApiService, analysisService);
163
+ const watchlistProfileService = new WatchlistProfileService(tickFlowUniverseService, mxApiService, analysisService);
156
164
  console.log(`[validate:mx-search] live validation start: ${samples.length} samples`);
157
165
  for (const sample of samples) {
158
166
  const profile = await watchlistProfileService.resolve(sample.symbol, sample.name, currentTimestamp());
@@ -33,6 +33,13 @@ function parseRequiredSymbol(args, usage) {
33
33
  }
34
34
  return symbol;
35
35
  }
36
+ function parseRequiredKeyword(args, usage) {
37
+ const keyword = (args ?? "").trim();
38
+ if (!keyword) {
39
+ throw new Error(`用法: ${usage}`);
40
+ }
41
+ return keyword;
42
+ }
36
43
  async function runToolText(tool, rawInput) {
37
44
  return tool.run({ rawInput });
38
45
  }
@@ -87,6 +94,7 @@ export function registerPluginCommands(api, tools, app) {
87
94
  const updateAll = getTool(tools, "update_all");
88
95
  const dailyUpdateStatus = getTool(tools, "daily_update_status");
89
96
  const testAlert = getTool(tools, "test_alert");
97
+ const screenStockCandidates = getTool(tools, "screen_stock_candidates");
90
98
  const commands = [
91
99
  {
92
100
  name: "ta_addstock",
@@ -202,6 +210,25 @@ export function registerPluginCommands(api, tools, app) {
202
210
  requireAuth: true,
203
211
  handler: async () => runCommandText(() => runToolText(testAlert)),
204
212
  },
213
+ {
214
+ name: "ta_screenstocks",
215
+ description: "智能选股并生成小规模候选池,不经过 AI 对话。用法: /ta_screenstocks <自然语言选股条件>",
216
+ acceptsArgs: true,
217
+ requireAuth: true,
218
+ handler: async ({ args }) => runCommandText(() => runToolText(screenStockCandidates, {
219
+ keyword: parseRequiredKeyword(args, "/ta_screenstocks <自然语言选股条件>"),
220
+ })),
221
+ },
222
+ {
223
+ name: "ta_screenstocks_llm",
224
+ description: "智能选股并生成小规模候选池,再调用 LLM 做候选整理。用法: /ta_screenstocks_llm <自然语言选股条件>",
225
+ acceptsArgs: true,
226
+ requireAuth: true,
227
+ handler: async ({ args }) => runCommandText(() => runToolText(screenStockCandidates, {
228
+ keyword: parseRequiredKeyword(args, "/ta_screenstocks_llm <自然语言选股条件>"),
229
+ summarize: true,
230
+ })),
231
+ },
205
232
  {
206
233
  name: "ta_debug",
207
234
  description: "查看 TickFlow 插件运行时调试信息。",
package/dist/plugin.js CHANGED
@@ -10,12 +10,10 @@ const PLUGIN_DESCRIPTION = "A-share watchlist analysis, monitoring, and alert de
10
10
  const STOCK_AGENT_ID = "stock";
11
11
  const STOCK_PROMPT_ENFORCEMENT = [
12
12
  "You are handling the stock agent.",
13
- "For watchlist management and stock status intents, prefer TickFlow Assist plugin tools over generic built-in tools.",
14
- "If the user asks to add a stock and provides a symbol, your first action must be calling add_stock.",
15
- "If the user asks to remove a stock and provides symbol, your first action must be calling remove_stock.",
16
- "If the user asks for watchlist, your first action must be calling list_watchlist.",
17
- "Do not call read, write, edit, query_database, session tools, or environment-inspection tools to figure out how to perform add/remove/list watchlist actions.",
18
- "Do not say you need to inspect the environment, confirm available tools, or find the method first when add_stock/remove_stock/list_watchlist are available.",
13
+ "Prefer TickFlow Assist plugin tools for watchlist, stock status, analysis, monitoring, and alert intents.",
14
+ "If the user asks to add a stock and provides a symbol, call add_stock first.",
15
+ "If the user asks to remove a stock and provides a symbol, call remove_stock first.",
16
+ "If the user asks for the watchlist, call list_watchlist first.",
19
17
  "If a required tool parameter is missing, ask only for that missing parameter.",
20
18
  ].join("\n");
21
19
  const GENERIC_TOOL_PARAMETERS_SCHEMA = Type.Object({}, {
@@ -1,4 +1,5 @@
1
1
  import { formatCostPrice, formatCostRelationship } from "../../utils/cost-price.js";
2
+ import { resolveTickFlowQuoteChangePct } from "../../utils/tickflow-quote.js";
2
3
  const MAX_INTRADAY_FULL_ROWS = 40;
3
4
  const MAX_INTRADAY_OPEN_ROWS = 8;
4
5
  const MAX_INTRADAY_CLOSE_ROWS = 12;
@@ -139,7 +140,7 @@ function buildRealtimeLines(quote) {
139
140
  "",
140
141
  `- 最新价: ${fmt(quote.last_price)}`,
141
142
  `- 前收: ${fmt(quote.prev_close)}`,
142
- `- 涨跌幅: ${fmtPercent(quote.ext?.change_pct)}`,
143
+ `- 涨跌幅: ${fmtPercent(resolveTickFlowQuoteChangePct(quote))}`,
143
144
  `- 成交量: ${Math.trunc(Number(quote.volume ?? 0))}`,
144
145
  `- 行情时间: ${formatTimestamp(quote.timestamp)}`,
145
146
  ];
@@ -1,4 +1,5 @@
1
1
  import { formatCostPrice, formatCostRelationship } from "../../utils/cost-price.js";
2
+ import { normalizeTickFlowChangePct, resolveTickFlowKlineChangePct } from "../../utils/tickflow-quote.js";
2
3
  import { buildReferencedNarrative, truncatePromptText } from "./prompt-text-utils.js";
3
4
  import { indentPromptBlock, KEY_LEVELS_FIELD_GUIDANCE, KEY_LEVELS_JSON_SCHEMA_INNER, } from "./shared-schema.js";
4
5
  const MAX_VALIDATION_SUMMARY_LENGTH = 220;
@@ -50,11 +51,13 @@ export function buildPostCloseReviewUserPrompt(input) {
50
51
  const latestClose = input.market.klines[input.market.klines.length - 1]?.close ?? 0;
51
52
  const latestRealtimePrice = input.market.realtimeQuote?.last_price ?? latestClose;
52
53
  const watchlistItem = input.market.watchlistItem;
54
+ const latestChangePct = resolveDailyChangePct(input);
53
55
  return [
54
56
  `请对 ${input.market.companyName}(${input.market.symbol})生成收盘复盘。`,
55
57
  `用户成本价: ${formatCostPrice(watchlistItem?.costPrice ?? null)}`,
56
58
  `最新收盘价: ${latestClose.toFixed(2)}`,
57
59
  `最新实时价: ${latestRealtimePrice.toFixed(2)}`,
60
+ `当日涨跌幅: ${formatSignedPct(latestChangePct)}`,
58
61
  `相对成本价: ${formatCostRelationship(latestRealtimePrice, watchlistItem?.costPrice ?? null)}`,
59
62
  `申万行业分类: ${watchlistItem?.sector ?? "未记录"}`,
60
63
  `概念板块: ${watchlistItem?.themes.length ? watchlistItem.themes.join(";") : "未记录"}`,
@@ -79,6 +82,9 @@ export function buildPostCloseReviewUserPrompt(input) {
79
82
  "## 行业分类/概念板块资讯摘要",
80
83
  input.news.boardDocuments.length > 0 ? formatDocuments(input.news.boardDocuments) : "未获取到有效行业分类/概念板块资讯。",
81
84
  "",
85
+ "## 申万三级同业表现",
86
+ formatIndustryPeerContext(input.peerContext),
87
+ "",
82
88
  "## 结构化参考",
83
89
  `当前综合关键位: ${formatLevels(input.compositeResult.levels ?? input.technicalResult.levels)}`,
84
90
  `基本面评分/倾向: ${input.financialResult.score ?? "-"} / ${input.financialResult.bias}`,
@@ -88,7 +94,7 @@ export function buildPostCloseReviewUserPrompt(input) {
88
94
  `资讯催化: ${joinList(input.newsResult.catalysts)}`,
89
95
  `资讯风险: ${joinList(input.newsResult.risks)}`,
90
96
  "",
91
- "请按系统要求输出正文和最终 JSON。正文重点回答:昨天关键位到底是否有效;今天盘面是否得到大盘、行业分类/概念板块、新闻的解释;明天该沿用、微调、重算还是暂停关键位。",
97
+ "请按系统要求输出正文和最终 JSON。正文重点回答:昨天关键位到底是否有效;今天盘面是否得到大盘、行业分类/概念板块、申万三级同业强弱、新闻的解释;明天该沿用、微调、重算还是暂停关键位。",
92
98
  ].join("\n");
93
99
  }
94
100
  function formatDocuments(documents) {
@@ -143,3 +149,36 @@ function formatFlashSection(label, items) {
143
149
  }),
144
150
  ].join("\n");
145
151
  }
152
+ function formatIndustryPeerContext(context) {
153
+ if (!context.available) {
154
+ return context.note ?? context.summary ?? "未获取到申万三级同业表现。";
155
+ }
156
+ return [
157
+ context.summary,
158
+ `行业层级: ${joinList([context.sw1Name, context.sw2Name, context.sw3Name].filter(isNonEmptyText))}`,
159
+ `领涨样本: ${formatPeerMovers(context.leaders)}`,
160
+ `领跌样本: ${formatPeerMovers(context.laggards)}`,
161
+ ].join("\n");
162
+ }
163
+ function formatPeerMovers(movers) {
164
+ if (movers.length === 0) {
165
+ return "无";
166
+ }
167
+ return movers
168
+ .map((item) => `${item.name || item.symbol}(${item.symbol} ${item.changePct >= 0 ? "+" : ""}${item.changePct.toFixed(2)}%)`)
169
+ .join(";");
170
+ }
171
+ function isNonEmptyText(value) {
172
+ return typeof value === "string" && value.trim().length > 0;
173
+ }
174
+ function resolveDailyChangePct(input) {
175
+ const latestKline = input.market.klines[input.market.klines.length - 1];
176
+ return normalizeTickFlowChangePct(input.market.realtimeQuote?.ext?.change_pct)
177
+ ?? resolveTickFlowKlineChangePct(latestKline);
178
+ }
179
+ function formatSignedPct(value) {
180
+ if (value == null || !Number.isFinite(value)) {
181
+ return "未获取";
182
+ }
183
+ return `${value >= 0 ? "+" : ""}${value.toFixed(2)}%`;
184
+ }
@@ -1,5 +1,5 @@
1
1
  import type { WatchlistItem } from "../../types/domain.js";
2
- export declare const PRE_MARKET_BRIEF_SYSTEM_PROMPT = "\n\u4F60\u662F\u4E00\u4F4DA\u80A1\u5F00\u76D8\u524D\u8D44\u8BAF\u7B80\u62A5\u7F16\u8F91\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u57FA\u4E8E\u76D8\u524D\u7A97\u53E3\u5185\u7684\u91D1\u5341\u6570\u636E\u6574\u7406\u5FEB\u8BAF\uFF0C\u4EE5\u53CA\u7528\u6237\u81EA\u9009\u80A1\u7684\u884C\u4E1A/\u9898\u6750\u4FE1\u606F\uFF0C\u751F\u6210\u4E00\u4EFD\u9002\u5408 9:20 \u63A8\u9001\u7684\u5F00\u76D8\u524D\u6458\u8981\u3002\n\n\u8F93\u51FA\u8981\u6C42\uFF1A\n1. \u6309\u4EE5\u4E0B\u6807\u9898\u8F93\u51FA\uFF0C\u6BCF\u8282\u4F7F\u7528 2-5 \u6761\u7B80\u6D01\u4E2D\u6587\u8981\u70B9\uFF1A\n- \u91CD\u5927\u8981\u95FB\n- \u81EA\u9009\u76F8\u5173\n- \u6F5C\u5728\u673A\u4F1A\n- \u98CE\u9669\u63D0\u793A\n- \u5F00\u76D8\u524D\u5173\u6CE8\u6E05\u5355\n2. \u201C\u91CD\u5927\u8981\u95FB\u201D\u53EA\u63D0\u70BC\u4F1A\u5F71\u54CD A \u80A1\u5F00\u76D8\u60C5\u7EEA\u3001\u884C\u4E1A\u98CE\u9669\u504F\u597D\u6216\u91CD\u8981\u4EA4\u6613\u7EBF\u7D22\u7684\u5185\u5BB9\uFF0C\u4E0D\u8981\u673A\u68B0\u7F57\u5217\u6240\u6709\u5FEB\u8BAF\u3002\n3. \u201C\u81EA\u9009\u76F8\u5173\u201D\u8981\u4F18\u5148\u70B9\u540D\u4E0E\u81EA\u9009\u80A1\u3001\u884C\u4E1A\u6216\u9898\u6750\u76F4\u63A5\u76F8\u5173\u7684\u5185\u5BB9\uFF1B\u82E5\u6CA1\u6709\u76F4\u63A5\u547D\u4E2D\uFF0C\u4E5F\u8981\u660E\u786E\u5199\u51FA\u201C\u672A\u53D1\u73B0\u76F4\u63A5\u547D\u4E2D\u81EA\u9009\u80A1\u201D\u3002\n4. \u201C\u6F5C\u5728\u673A\u4F1A\u201D\u53EA\u4FDD\u7559\u5B58\u5728\u6E05\u6670\u50AC\u5316\u94FE\u6761\u7684\u65B9\u5411\uFF0C\u4F8B\u5982\u653F\u7B56\u3001\u4EA7\u4E1A\u8D8B\u52BF\u3001\u4E1A\u7EE9\u3001\u8BA2\u5355\u3001\u8D44\u91D1\u504F\u597D\u53D8\u5316\uFF1B\u6CA1\u6709\u660E\u786E\u673A\u4F1A\u65F6\u8981\u76F4\u8BF4\u3002\n5. \u201C\u98CE\u9669\u63D0\u793A\u201D\u8981\u6307\u51FA\u4E0D\u5229\u4E8E\u5F00\u76D8\u51B3\u7B56\u7684\u6270\u52A8\u9879\uFF0C\u4F8B\u5982\u76D1\u7BA1\u3001\u6D77\u5916\u6270\u52A8\u3001\u4E1A\u7EE9\u98CE\u9669\u3001\u9898\u6750\u9000\u6F6E\u3001\u6D88\u606F\u4E0D\u786E\u5B9A\u6027\u3002\n6. \u201C\u5F00\u76D8\u524D\u5173\u6CE8\u6E05\u5355\u201D\u8F93\u51FA 3-6 \u6761\u53EF\u6267\u884C\u89C2\u5BDF\u70B9\uFF0C\u5C3D\u91CF\u5199\u6E05\u695A\u5E94\u89C2\u5BDF\u7684\u80A1\u7968\u3001\u677F\u5757\u6216\u4FE1\u53F7\u3002\n7. \u4E0D\u8981\u7F16\u9020\u672A\u5728\u8F93\u5165\u4E2D\u51FA\u73B0\u7684\u516C\u53F8\u3001\u653F\u7B56\u3001\u884C\u4E1A\u4FE1\u606F\u6216\u5FEB\u8BAF\u7ED3\u8BBA\u3002\n8. \u8F93\u51FA\u6B63\u6587\u5373\u53EF\uFF0C\u4E0D\u8981\u9644\u52A0 JSON\u3002\n";
2
+ export declare const PRE_MARKET_BRIEF_SYSTEM_PROMPT = "\n\u4F60\u662F\u4E00\u4F4DA\u80A1\u5F00\u76D8\u524D\u8D44\u8BAF\u7B80\u62A5\u7F16\u8F91\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u57FA\u4E8E\u76D8\u524D\u7A97\u53E3\u5185\u7684\u91D1\u5341\u6570\u636E\u6574\u7406\u5FEB\u8BAF\uFF0C\u4EE5\u53CA\u7528\u6237\u81EA\u9009\u80A1\u7684\u884C\u4E1A/\u9898\u6750\u4FE1\u606F\uFF0C\u751F\u6210\u4E00\u4EFD\u9002\u5408 9:20 \u63A8\u9001\u7684\u5F00\u76D8\u524D\u6458\u8981\u3002\n\n\u8F93\u51FA\u8981\u6C42\uFF1A\n1. \u6309\u4EE5\u4E0B\u6807\u9898\u8F93\u51FA\uFF0C\u6BCF\u8282\u4F7F\u7528 2-5 \u6761\u7B80\u6D01\u4E2D\u6587\u8981\u70B9\uFF1A\n- \u91CD\u5927\u8981\u95FB\n- \u81EA\u9009\u76F8\u5173\n- \u6F5C\u5728\u673A\u4F1A\n- \u98CE\u9669\u63D0\u793A\n- \u5F00\u76D8\u524D\u5173\u6CE8\u6E05\u5355\n2. \u201C\u91CD\u5927\u8981\u95FB\u201D\u5FC5\u987B\u5148\u4ECE\u5168\u90E8\u5FEB\u8BAF\u4E2D\u63D0\u70BC\u5B8F\u89C2/\u4EA7\u4E1A\u4E3B\u9898\uFF0C\u4F8B\u5982\u5730\u7F18\u98CE\u9669\u3001\u80FD\u6E90\u4F9B\u9700\u3001\u79D1\u6280\u4EA7\u4E1A\u3001\u653F\u7B56\u76D1\u7BA1\u3001\u8DE8\u5883\u652F\u4ED8\u3001\u5B8F\u89C2\u8D44\u91D1\u7B49\uFF1B\u4E0D\u8981\u6309\u81EA\u9009\u80A1\u547D\u4E2D\u7ED3\u679C\u6392\u5E8F\uFF0C\u4E5F\u4E0D\u8981\u53EA\u590D\u8FF0\u4E0E\u81EA\u9009\u76F8\u5173\u7684\u51E0\u6761\u3002\n3. \u201C\u81EA\u9009\u76F8\u5173\u201D\u53EA\u89E3\u91CA\u4E0E\u81EA\u9009\u80A1\u3001\u884C\u4E1A\u6216\u9898\u6750\u76F4\u63A5\u76F8\u5173\u7684\u5185\u5BB9\uFF1B\u82E5\u540C\u4E00\u4E8B\u5B9E\u5DF2\u5728\u201C\u91CD\u5927\u8981\u95FB\u201D\u51FA\u73B0\uFF0C\u8FD9\u91CC\u8981\u6539\u5199\u6210\u201C\u4E3A\u4EC0\u4E48\u5173\u8054\u8BE5\u81EA\u9009\u80A1/\u9898\u6750\u201D\uFF0C\u4E0D\u8981\u590D\u5236\u540C\u4E00\u53E5\u3002\n4. \u201C\u6F5C\u5728\u673A\u4F1A\u201D\u53EA\u4FDD\u7559\u5B58\u5728\u6E05\u6670\u50AC\u5316\u94FE\u6761\u7684\u65B9\u5411\uFF0C\u4F8B\u5982\u653F\u7B56\u3001\u4EA7\u4E1A\u8D8B\u52BF\u3001\u4E1A\u7EE9\u3001\u8BA2\u5355\u3001\u8D44\u91D1\u504F\u597D\u53D8\u5316\uFF1B\u6CA1\u6709\u660E\u786E\u673A\u4F1A\u65F6\u8981\u76F4\u8BF4\u3002\n5. \u201C\u98CE\u9669\u63D0\u793A\u201D\u8981\u6307\u51FA\u4E0D\u5229\u4E8E\u5F00\u76D8\u51B3\u7B56\u7684\u6270\u52A8\u9879\uFF0C\u4F8B\u5982\u76D1\u7BA1\u3001\u6D77\u5916\u6270\u52A8\u3001\u4E1A\u7EE9\u98CE\u9669\u3001\u9898\u6750\u9000\u6F6E\u3001\u6D88\u606F\u4E0D\u786E\u5B9A\u6027\u3002\n6. \u201C\u5F00\u76D8\u524D\u5173\u6CE8\u6E05\u5355\u201D\u8F93\u51FA 3-6 \u6761\u53EF\u6267\u884C\u89C2\u5BDF\u70B9\uFF0C\u5C3D\u91CF\u5199\u6E05\u695A\u5E94\u89C2\u5BDF\u7684\u80A1\u7968\u3001\u677F\u5757\u6216\u4FE1\u53F7\u3002\n7. \u4E0D\u8981\u7F16\u9020\u672A\u5728\u8F93\u5165\u4E2D\u51FA\u73B0\u7684\u516C\u53F8\u3001\u653F\u7B56\u3001\u884C\u4E1A\u4FE1\u606F\u6216\u5FEB\u8BAF\u7ED3\u8BBA\u3002\n8. \u4E25\u7981\u53EA\u590D\u8FF0\u201C\u91D1\u5341\u6570\u636E\u6574\u7406\uFF1A...\u201D\u6807\u9898\u3002\u6BCF\u6761\u8981\u70B9\u90FD\u5FC5\u987B\u4F18\u5148\u4F7F\u7528\u8F93\u5165\u4E2D\u7684\u201C\u63D0\u70BC\u6458\u8981\u201D\u6216\u201C\u6B63\u6587\u8981\u70B9\u201D\uFF0C\u5199\u51FA\u81F3\u5C11\u4E00\u4E2A\u5177\u4F53\u4E8B\u5B9E\u3001\u5F71\u54CD\u94FE\u6761\u6216\u89C2\u5BDF\u65B9\u5411\u3002\n9. \u5982\u679C\u67D0\u6761\u6574\u7406\u5FEB\u8BAF\u53EA\u6709\u6807\u9898\u3001\u6CA1\u6709\u53EF\u7528\u7EC6\u8282\uFF0C\u53EF\u4EE5\u660E\u786E\u5199\u201C\u4EC5\u4E3A\u6807\u9898\u7EA7\u7EBF\u7D22\uFF0C\u7EC6\u8282\u4E0D\u8DB3\u201D\uFF0C\u4F46\u4E0D\u8981\u628A\u6807\u9898\u672C\u8EAB\u5F53\u6210\u5B8C\u6574\u7ED3\u8BBA\u3002\n10. \u5C3D\u91CF\u907F\u514D\u8DE8\u680F\u76EE\u91CD\u590D\u540C\u4E00\u6761\u4E8B\u5B9E\uFF1B\u786E\u9700\u91CD\u590D\u65F6\uFF0C\u6BCF\u4E2A\u680F\u76EE\u5FC5\u987B\u7ED9\u51FA\u4E0D\u540C\u89D2\u5EA6\uFF1A\u91CD\u5927\u8981\u95FB\u8BB2\u5E02\u573A\u5F71\u54CD\uFF0C\u81EA\u9009\u76F8\u5173\u8BB2\u5173\u8054\u539F\u56E0\uFF0C\u673A\u4F1A/\u98CE\u9669\u8BB2\u4EA4\u6613\u9A8C\u8BC1\u70B9\u3002\n11. \u8F93\u51FA\u6B63\u6587\u5373\u53EF\uFF0C\u4E0D\u8981\u9644\u52A0 JSON\u3002\n";
3
3
  export declare function buildPreMarketBriefUserPrompt(params: {
4
4
  windowStartAt: string;
5
5
  windowEndAt: string;
@@ -7,6 +7,8 @@ export declare function buildPreMarketBriefUserPrompt(params: {
7
7
  flashes: Array<{
8
8
  publishedAt: string;
9
9
  headline: string;
10
+ summary: string;
11
+ keyPoints: string[];
10
12
  content: string;
11
13
  url: string;
12
14
  matchedSymbols: string[];
@@ -8,13 +8,16 @@ export const PRE_MARKET_BRIEF_SYSTEM_PROMPT = `
8
8
  - 潜在机会
9
9
  - 风险提示
10
10
  - 开盘前关注清单
11
- 2. “重大要闻”只提炼会影响 A 股开盘情绪、行业风险偏好或重要交易线索的内容,不要机械罗列所有快讯。
12
- 3. “自选相关”要优先点名与自选股、行业或题材直接相关的内容;若没有直接命中,也要明确写出“未发现直接命中自选股”。
11
+ 2. “重大要闻”必须先从全部快讯中提炼宏观/产业主题,例如地缘风险、能源供需、科技产业、政策监管、跨境支付、宏观资金等;不要按自选股命中结果排序,也不要只复述与自选相关的几条。
12
+ 3. “自选相关”只解释与自选股、行业或题材直接相关的内容;若同一事实已在“重大要闻”出现,这里要改写成“为什么关联该自选股/题材”,不要复制同一句。
13
13
  4. “潜在机会”只保留存在清晰催化链条的方向,例如政策、产业趋势、业绩、订单、资金偏好变化;没有明确机会时要直说。
14
14
  5. “风险提示”要指出不利于开盘决策的扰动项,例如监管、海外扰动、业绩风险、题材退潮、消息不确定性。
15
15
  6. “开盘前关注清单”输出 3-6 条可执行观察点,尽量写清楚应观察的股票、板块或信号。
16
16
  7. 不要编造未在输入中出现的公司、政策、行业信息或快讯结论。
17
- 8. 输出正文即可,不要附加 JSON。
17
+ 8. 严禁只复述“金十数据整理:...”标题。每条要点都必须优先使用输入中的“提炼摘要”或“正文要点”,写出至少一个具体事实、影响链条或观察方向。
18
+ 9. 如果某条整理快讯只有标题、没有可用细节,可以明确写“仅为标题级线索,细节不足”,但不要把标题本身当成完整结论。
19
+ 10. 尽量避免跨栏目重复同一条事实;确需重复时,每个栏目必须给出不同角度:重大要闻讲市场影响,自选相关讲关联原因,机会/风险讲交易验证点。
20
+ 11. 输出正文即可,不要附加 JSON。
18
21
  `;
19
22
  export function buildPreMarketBriefUserPrompt(params) {
20
23
  return [
@@ -43,6 +46,8 @@ function formatFlash(index, flash) {
43
46
  return [
44
47
  `${index}. [${flash.publishedAt}] ${flash.headline || "未提取到标题"}`,
45
48
  ` 关联提示: ${flash.matchedSymbols.length > 0 ? flash.matchedSymbols.join("、") : "无直接规则命中"}`,
49
+ ` 提炼摘要: ${flash.summary}`,
50
+ ` 正文要点: ${flash.keyPoints.length > 0 ? flash.keyPoints.map((item) => `- ${item}`).join("; ") : "未提取到稳定要点"}`,
46
51
  ` 正文: ${flash.content}`,
47
52
  ` 来源: ${flash.url}`,
48
53
  ].join("\n");
@@ -0,0 +1,9 @@
1
+ import type { IndustryPeerContext } from "../analysis/types/composite-analysis.js";
2
+ import { QuoteService } from "./quote-service.js";
3
+ import { TickFlowUniverseService } from "./tickflow-universe-service.js";
4
+ export declare class IndustryPeerService {
5
+ private readonly universeService;
6
+ private readonly quoteService;
7
+ constructor(universeService: TickFlowUniverseService | null, quoteService: QuoteService);
8
+ buildContext(symbol: string): Promise<IndustryPeerContext>;
9
+ }