tickflow-assist 0.2.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.
Files changed (246) hide show
  1. package/README.md +151 -0
  2. package/day_future.txt +8797 -0
  3. package/dist/analysis/orchestrators/composite-analysis.orchestrator.d.ts +35 -0
  4. package/dist/analysis/orchestrators/composite-analysis.orchestrator.js +232 -0
  5. package/dist/analysis/parsers/json-block.parser.d.ts +1 -0
  6. package/dist/analysis/parsers/json-block.parser.js +13 -0
  7. package/dist/analysis/parsers/key-levels.parser.d.ts +5 -0
  8. package/dist/analysis/parsers/key-levels.parser.js +61 -0
  9. package/dist/analysis/parsers/post-close-review.parser.d.ts +2 -0
  10. package/dist/analysis/parsers/post-close-review.parser.js +79 -0
  11. package/dist/analysis/parsers/watchlist-profile.parser.d.ts +6 -0
  12. package/dist/analysis/parsers/watchlist-profile.parser.js +93 -0
  13. package/dist/analysis/providers/financial-analysis.provider.d.ts +12 -0
  14. package/dist/analysis/providers/financial-analysis.provider.js +85 -0
  15. package/dist/analysis/providers/market-analysis.provider.d.ts +27 -0
  16. package/dist/analysis/providers/market-analysis.provider.js +187 -0
  17. package/dist/analysis/providers/news-analysis.provider.d.ts +9 -0
  18. package/dist/analysis/providers/news-analysis.provider.js +60 -0
  19. package/dist/analysis/tasks/analysis-step-task.d.ts +11 -0
  20. package/dist/analysis/tasks/analysis-step-task.js +1 -0
  21. package/dist/analysis/tasks/analysis-task.d.ts +13 -0
  22. package/dist/analysis/tasks/analysis-task.js +1 -0
  23. package/dist/analysis/tasks/composite-stock-analysis.task.d.ts +17 -0
  24. package/dist/analysis/tasks/composite-stock-analysis.task.js +47 -0
  25. package/dist/analysis/tasks/financial-fundamental-lite.task.d.ts +10 -0
  26. package/dist/analysis/tasks/financial-fundamental-lite.task.js +21 -0
  27. package/dist/analysis/tasks/financial-fundamental.task.d.ts +12 -0
  28. package/dist/analysis/tasks/financial-fundamental.task.js +64 -0
  29. package/dist/analysis/tasks/kline-technical-signal.task.d.ts +10 -0
  30. package/dist/analysis/tasks/kline-technical-signal.task.js +41 -0
  31. package/dist/analysis/tasks/kline-technical.task.d.ts +32 -0
  32. package/dist/analysis/tasks/kline-technical.task.js +61 -0
  33. package/dist/analysis/tasks/news-catalyst.task.d.ts +11 -0
  34. package/dist/analysis/tasks/news-catalyst.task.js +62 -0
  35. package/dist/analysis/tasks/post-close-review.task.d.ts +12 -0
  36. package/dist/analysis/tasks/post-close-review.task.js +35 -0
  37. package/dist/analysis/types/composite-analysis.d.ts +123 -0
  38. package/dist/analysis/types/composite-analysis.js +1 -0
  39. package/dist/background/daily-update.worker.d.ts +50 -0
  40. package/dist/background/daily-update.worker.js +546 -0
  41. package/dist/background/realtime-monitor.worker.d.ts +8 -0
  42. package/dist/background/realtime-monitor.worker.js +28 -0
  43. package/dist/bootstrap.d.ts +45 -0
  44. package/dist/bootstrap.js +214 -0
  45. package/dist/config/normalize.d.ts +4 -0
  46. package/dist/config/normalize.js +99 -0
  47. package/dist/config/schema.d.ts +23 -0
  48. package/dist/config/schema.js +18 -0
  49. package/dist/config/tickflow-access.d.ts +4 -0
  50. package/dist/config/tickflow-access.js +28 -0
  51. package/dist/constants/market-indexes.d.ts +5 -0
  52. package/dist/constants/market-indexes.js +4 -0
  53. package/dist/dev/run-daily-update-loop.d.ts +1 -0
  54. package/dist/dev/run-daily-update-loop.js +48 -0
  55. package/dist/dev/run-monitor-loop.d.ts +1 -0
  56. package/dist/dev/run-monitor-loop.js +60 -0
  57. package/dist/dev/run-tool.d.ts +1 -0
  58. package/dist/dev/run-tool.js +49 -0
  59. package/dist/dev/tickflow-assist-cli.d.ts +2 -0
  60. package/dist/dev/tickflow-assist-cli.js +525 -0
  61. package/dist/dev/validate-mx-search.d.ts +1 -0
  62. package/dist/dev/validate-mx-search.js +212 -0
  63. package/dist/plugin-commands.d.ts +3 -0
  64. package/dist/plugin-commands.js +229 -0
  65. package/dist/plugin.d.ts +8 -0
  66. package/dist/plugin.js +151 -0
  67. package/dist/prompts/analysis/common-system-prompt.d.ts +1 -0
  68. package/dist/prompts/analysis/common-system-prompt.js +44 -0
  69. package/dist/prompts/analysis/composite-analysis-user-prompt.d.ts +11 -0
  70. package/dist/prompts/analysis/composite-analysis-user-prompt.js +102 -0
  71. package/dist/prompts/analysis/financial-analysis-user-prompt.d.ts +7 -0
  72. package/dist/prompts/analysis/financial-analysis-user-prompt.js +106 -0
  73. package/dist/prompts/analysis/financial-lite-analysis-user-prompt.d.ts +7 -0
  74. package/dist/prompts/analysis/financial-lite-analysis-user-prompt.js +59 -0
  75. package/dist/prompts/analysis/index.d.ts +8 -0
  76. package/dist/prompts/analysis/index.js +8 -0
  77. package/dist/prompts/analysis/kline-analysis-user-prompt.d.ts +13 -0
  78. package/dist/prompts/analysis/kline-analysis-user-prompt.js +215 -0
  79. package/dist/prompts/analysis/news-analysis-user-prompt.d.ts +8 -0
  80. package/dist/prompts/analysis/news-analysis-user-prompt.js +57 -0
  81. package/dist/prompts/analysis/post-close-review-user-prompt.d.ts +3 -0
  82. package/dist/prompts/analysis/post-close-review-user-prompt.js +129 -0
  83. package/dist/prompts/analysis/watchlist-profile-extraction-prompt.d.ts +7 -0
  84. package/dist/prompts/analysis/watchlist-profile-extraction-prompt.js +52 -0
  85. package/dist/prompts/analysis-system-prompt.d.ts +1 -0
  86. package/dist/prompts/analysis-system-prompt.js +1 -0
  87. package/dist/prompts/analysis-user-prompt.d.ts +1 -0
  88. package/dist/prompts/analysis-user-prompt.js +1 -0
  89. package/dist/runtime/daily-update-process.d.ts +3 -0
  90. package/dist/runtime/daily-update-process.js +24 -0
  91. package/dist/runtime/monitor-process.d.ts +3 -0
  92. package/dist/runtime/monitor-process.js +24 -0
  93. package/dist/runtime/plugin-api.d.ts +22 -0
  94. package/dist/runtime/plugin-api.js +2 -0
  95. package/dist/runtime/process-config.d.ts +8 -0
  96. package/dist/runtime/process-config.js +35 -0
  97. package/dist/services/alert-service.d.ts +45 -0
  98. package/dist/services/alert-service.js +198 -0
  99. package/dist/services/analysis-service.d.ts +20 -0
  100. package/dist/services/analysis-service.js +73 -0
  101. package/dist/services/analysis-view-service.d.ts +23 -0
  102. package/dist/services/analysis-view-service.js +343 -0
  103. package/dist/services/financial-lite-service.d.ts +23 -0
  104. package/dist/services/financial-lite-service.js +201 -0
  105. package/dist/services/financial-service.d.ts +20 -0
  106. package/dist/services/financial-service.js +47 -0
  107. package/dist/services/indicator-service.d.ts +9 -0
  108. package/dist/services/indicator-service.js +67 -0
  109. package/dist/services/instrument-service.d.ts +6 -0
  110. package/dist/services/instrument-service.js +21 -0
  111. package/dist/services/key-level-service.d.ts +2 -0
  112. package/dist/services/key-level-service.js +2 -0
  113. package/dist/services/key-levels-backtest-service.d.ts +71 -0
  114. package/dist/services/key-levels-backtest-service.js +427 -0
  115. package/dist/services/kline-service.d.ts +19 -0
  116. package/dist/services/kline-service.js +91 -0
  117. package/dist/services/monitor-service.d.ts +44 -0
  118. package/dist/services/monitor-service.js +598 -0
  119. package/dist/services/mx-search-service.d.ts +22 -0
  120. package/dist/services/mx-search-service.js +286 -0
  121. package/dist/services/post-close-review-service.d.ts +31 -0
  122. package/dist/services/post-close-review-service.js +402 -0
  123. package/dist/services/quote-service.d.ts +7 -0
  124. package/dist/services/quote-service.js +9 -0
  125. package/dist/services/review-memory-service.d.ts +7 -0
  126. package/dist/services/review-memory-service.js +76 -0
  127. package/dist/services/tickflow-client.d.ts +43 -0
  128. package/dist/services/tickflow-client.js +126 -0
  129. package/dist/services/trading-calendar-service.d.ts +20 -0
  130. package/dist/services/trading-calendar-service.js +102 -0
  131. package/dist/services/update-service.d.ts +21 -0
  132. package/dist/services/update-service.js +130 -0
  133. package/dist/services/watchlist-profile-service.d.ts +20 -0
  134. package/dist/services/watchlist-profile-service.js +76 -0
  135. package/dist/services/watchlist-service.d.ts +43 -0
  136. package/dist/services/watchlist-service.js +204 -0
  137. package/dist/storage/db.d.ts +23 -0
  138. package/dist/storage/db.js +70 -0
  139. package/dist/storage/repositories/alert-log-repo.d.ts +15 -0
  140. package/dist/storage/repositories/alert-log-repo.js +54 -0
  141. package/dist/storage/repositories/analysis-log-repo.d.ts +8 -0
  142. package/dist/storage/repositories/analysis-log-repo.js +48 -0
  143. package/dist/storage/repositories/composite-analysis-repo.d.ts +9 -0
  144. package/dist/storage/repositories/composite-analysis-repo.js +116 -0
  145. package/dist/storage/repositories/financial-analysis-repo.d.ts +9 -0
  146. package/dist/storage/repositories/financial-analysis-repo.js +107 -0
  147. package/dist/storage/repositories/indicators-repo.d.ts +8 -0
  148. package/dist/storage/repositories/indicators-repo.js +98 -0
  149. package/dist/storage/repositories/intraday-klines-repo.d.ts +9 -0
  150. package/dist/storage/repositories/intraday-klines-repo.js +102 -0
  151. package/dist/storage/repositories/key-levels-history-repo.d.ts +9 -0
  152. package/dist/storage/repositories/key-levels-history-repo.js +91 -0
  153. package/dist/storage/repositories/key-levels-repo.d.ts +9 -0
  154. package/dist/storage/repositories/key-levels-repo.js +83 -0
  155. package/dist/storage/repositories/klines-repo.d.ts +8 -0
  156. package/dist/storage/repositories/klines-repo.js +60 -0
  157. package/dist/storage/repositories/news-analysis-repo.d.ts +9 -0
  158. package/dist/storage/repositories/news-analysis-repo.js +107 -0
  159. package/dist/storage/repositories/technical-analysis-repo.d.ts +9 -0
  160. package/dist/storage/repositories/technical-analysis-repo.js +80 -0
  161. package/dist/storage/repositories/watchlist-repo.d.ts +10 -0
  162. package/dist/storage/repositories/watchlist-repo.js +124 -0
  163. package/dist/storage/schemas.d.ts +13 -0
  164. package/dist/storage/schemas.js +177 -0
  165. package/dist/tools/add-stock.tool.d.ts +13 -0
  166. package/dist/tools/add-stock.tool.js +123 -0
  167. package/dist/tools/analyze.tool.d.ts +8 -0
  168. package/dist/tools/analyze.tool.js +24 -0
  169. package/dist/tools/backtest-key-levels.tool.d.ts +8 -0
  170. package/dist/tools/backtest-key-levels.tool.js +43 -0
  171. package/dist/tools/daily-update-status.tool.d.ts +6 -0
  172. package/dist/tools/daily-update-status.tool.js +9 -0
  173. package/dist/tools/fetch-financials.tool.d.ts +8 -0
  174. package/dist/tools/fetch-financials.tool.js +224 -0
  175. package/dist/tools/fetch-intraday-klines.tool.d.ts +11 -0
  176. package/dist/tools/fetch-intraday-klines.tool.js +58 -0
  177. package/dist/tools/fetch-klines.tool.d.ts +11 -0
  178. package/dist/tools/fetch-klines.tool.js +61 -0
  179. package/dist/tools/list-watchlist.tool.d.ts +6 -0
  180. package/dist/tools/list-watchlist.tool.js +22 -0
  181. package/dist/tools/monitor-status.tool.d.ts +6 -0
  182. package/dist/tools/monitor-status.tool.js +9 -0
  183. package/dist/tools/mx-search.tool.d.ts +8 -0
  184. package/dist/tools/mx-search.tool.js +77 -0
  185. package/dist/tools/mx-select-stock.tool.d.ts +8 -0
  186. package/dist/tools/mx-select-stock.tool.js +118 -0
  187. package/dist/tools/query-database.tool.d.ts +8 -0
  188. package/dist/tools/query-database.tool.js +283 -0
  189. package/dist/tools/refresh-watchlist-names.tool.d.ts +7 -0
  190. package/dist/tools/refresh-watchlist-names.tool.js +17 -0
  191. package/dist/tools/refresh-watchlist-profiles.tool.d.ts +9 -0
  192. package/dist/tools/refresh-watchlist-profiles.tool.js +67 -0
  193. package/dist/tools/remove-stock.tool.d.ts +9 -0
  194. package/dist/tools/remove-stock.tool.js +27 -0
  195. package/dist/tools/start-daily-update.tool.d.ts +10 -0
  196. package/dist/tools/start-daily-update.tool.js +23 -0
  197. package/dist/tools/start-monitor.tool.d.ts +9 -0
  198. package/dist/tools/start-monitor.tool.js +52 -0
  199. package/dist/tools/stop-daily-update.tool.d.ts +9 -0
  200. package/dist/tools/stop-daily-update.tool.js +20 -0
  201. package/dist/tools/stop-monitor.tool.d.ts +9 -0
  202. package/dist/tools/stop-monitor.tool.js +44 -0
  203. package/dist/tools/test-alert.tool.d.ts +7 -0
  204. package/dist/tools/test-alert.tool.js +21 -0
  205. package/dist/tools/update-all.tool.d.ts +9 -0
  206. package/dist/tools/update-all.tool.js +17 -0
  207. package/dist/tools/view-analysis.tool.d.ts +8 -0
  208. package/dist/tools/view-analysis.tool.js +95 -0
  209. package/dist/types/daily-update.d.ts +26 -0
  210. package/dist/types/daily-update.js +1 -0
  211. package/dist/types/domain.d.ts +140 -0
  212. package/dist/types/domain.js +1 -0
  213. package/dist/types/indicator.d.ts +43 -0
  214. package/dist/types/indicator.js +1 -0
  215. package/dist/types/monitor.d.ts +17 -0
  216. package/dist/types/monitor.js +1 -0
  217. package/dist/types/mx-search.d.ts +14 -0
  218. package/dist/types/mx-search.js +1 -0
  219. package/dist/types/mx-select-stock.d.ts +28 -0
  220. package/dist/types/mx-select-stock.js +1 -0
  221. package/dist/types/tickflow.d.ts +133 -0
  222. package/dist/types/tickflow.js +1 -0
  223. package/dist/utils/abortable-sleep.d.ts +1 -0
  224. package/dist/utils/abortable-sleep.js +20 -0
  225. package/dist/utils/china-time.d.ts +3 -0
  226. package/dist/utils/china-time.js +18 -0
  227. package/dist/utils/cost-price.d.ts +3 -0
  228. package/dist/utils/cost-price.js +18 -0
  229. package/dist/utils/format.d.ts +1 -0
  230. package/dist/utils/format.js +3 -0
  231. package/dist/utils/process.d.ts +5 -0
  232. package/dist/utils/process.js +1 -0
  233. package/dist/utils/symbol.d.ts +1 -0
  234. package/dist/utils/symbol.js +13 -0
  235. package/docs/installation.md +391 -0
  236. package/docs/usage.md +244 -0
  237. package/openclaw.plugin.json +116 -0
  238. package/package.json +57 -0
  239. package/python/indicator_runner.py +31 -0
  240. package/python/indicators.py +148 -0
  241. package/python/pyproject.toml +9 -0
  242. package/python/requirements.txt +3 -0
  243. package/python/uv.lock +366 -0
  244. package/skills/database-query/SKILL.md +58 -0
  245. package/skills/stock-analysis/SKILL.md +106 -0
  246. package/skills/usage-help/SKILL.md +102 -0
@@ -0,0 +1,187 @@
1
+ import { DEFAULT_MARKET_INDEXES } from "../../constants/market-indexes.js";
2
+ import { formatTickflowApiKeyLevel, supportsIntradayKlines, } from "../../config/tickflow-access.js";
3
+ const ANALYZE_INTRADAY_PERIOD = "1m";
4
+ const ANALYZE_INTRADAY_RETENTION_DAYS = 30;
5
+ export class MarketAnalysisProvider {
6
+ tickflowApiKeyLevel;
7
+ watchlistService;
8
+ klineService;
9
+ quoteService;
10
+ indicatorService;
11
+ reviewMemoryService;
12
+ tradingCalendarService;
13
+ klinesRepository;
14
+ intradayKlinesRepository;
15
+ indicatorsRepository;
16
+ constructor(tickflowApiKeyLevel, watchlistService, klineService, quoteService, indicatorService, reviewMemoryService, tradingCalendarService, klinesRepository, intradayKlinesRepository, indicatorsRepository) {
17
+ this.tickflowApiKeyLevel = tickflowApiKeyLevel;
18
+ this.watchlistService = watchlistService;
19
+ this.klineService = klineService;
20
+ this.quoteService = quoteService;
21
+ this.indicatorService = indicatorService;
22
+ this.reviewMemoryService = reviewMemoryService;
23
+ this.tradingCalendarService = tradingCalendarService;
24
+ this.klinesRepository = klinesRepository;
25
+ this.intradayKlinesRepository = intradayKlinesRepository;
26
+ this.indicatorsRepository = indicatorsRepository;
27
+ }
28
+ async load(symbol) {
29
+ const [watchlistItem, klines, indicators, quotes, reviewMemory, marketOverview] = await Promise.all([
30
+ this.watchlistService.getBySymbol(symbol, { refreshConceptBoards: true }),
31
+ this.klinesRepository.listBySymbol(symbol),
32
+ this.indicatorsRepository.listBySymbol(symbol),
33
+ this.quoteService.fetchQuotes([symbol]),
34
+ this.reviewMemoryService.getSymbolContext(symbol),
35
+ this.loadMarketOverview(),
36
+ ]);
37
+ let intradayKlines = [];
38
+ let intradayIndicators = [];
39
+ if (supportsIntradayKlines(this.tickflowApiKeyLevel)) {
40
+ try {
41
+ intradayKlines = await this.klineService.fetchIntradayKlines(symbol, {
42
+ period: ANALYZE_INTRADAY_PERIOD,
43
+ });
44
+ if (intradayKlines.length > 0) {
45
+ await this.intradayKlinesRepository.saveAll(symbol, ANALYZE_INTRADAY_PERIOD, intradayKlines);
46
+ const keepTradeDates = await this.tradingCalendarService.getRecentTradingDays(ANALYZE_INTRADAY_RETENTION_DAYS, new Date(intradayKlines[intradayKlines.length - 1].timestamp));
47
+ await this.intradayKlinesRepository.pruneToTradeDates(symbol, ANALYZE_INTRADAY_PERIOD, keepTradeDates);
48
+ intradayIndicators = await this.indicatorService.calculate(intradayKlines);
49
+ }
50
+ }
51
+ catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ console.warn(`[analyze] intraday fetch skipped for ${symbol} (${formatTickflowApiKeyLevel(this.tickflowApiKeyLevel)}): ${message}`);
54
+ }
55
+ }
56
+ return {
57
+ symbol,
58
+ companyName: watchlistItem?.name || symbol,
59
+ watchlistItem,
60
+ klines,
61
+ indicators,
62
+ intradayKlines,
63
+ intradayIndicators,
64
+ realtimeQuote: quotes[0] ?? null,
65
+ reviewMemory,
66
+ marketOverview,
67
+ };
68
+ }
69
+ async loadMarketOverview() {
70
+ const snapshots = (await Promise.all(DEFAULT_MARKET_INDEXES.map((spec) => this.loadIndexSnapshot(spec)))).filter((item) => item != null);
71
+ if (snapshots.length === 0) {
72
+ return {
73
+ available: false,
74
+ bias: "neutral",
75
+ summary: "未获取到上证指数/深证成指数据,本轮无法判断大盘顺风还是逆风。",
76
+ indices: [],
77
+ };
78
+ }
79
+ const bias = deriveMarketBias(snapshots);
80
+ const summary = [
81
+ ...snapshots.map((snapshot) => snapshot.summary),
82
+ `市场风格: ${formatMarketBiasLabel(bias)}`,
83
+ ].join(";");
84
+ return {
85
+ available: true,
86
+ bias,
87
+ summary,
88
+ indices: snapshots,
89
+ };
90
+ }
91
+ async loadIndexSnapshot(spec) {
92
+ const [klines, indicators, intradayRows] = await Promise.all([
93
+ this.klinesRepository.listBySymbol(spec.symbol),
94
+ this.indicatorsRepository.listBySymbol(spec.symbol),
95
+ this.intradayKlinesRepository.listBySymbol(spec.symbol, ANALYZE_INTRADAY_PERIOD),
96
+ ]);
97
+ const latest = klines[klines.length - 1];
98
+ if (!latest) {
99
+ return null;
100
+ }
101
+ const prevClose = latest.prev_close > 0 ? latest.prev_close : (klines[klines.length - 2]?.close ?? null);
102
+ const latestIndicator = indicators[indicators.length - 1] ?? null;
103
+ const changePct = prevClose != null && prevClose > 0
104
+ ? ((latest.close - prevClose) / prevClose) * 100
105
+ : null;
106
+ const snapshot = {
107
+ symbol: spec.symbol,
108
+ name: spec.name,
109
+ latestClose: latest.close,
110
+ prevClose,
111
+ changePct,
112
+ intradayClose: intradayRows[intradayRows.length - 1]?.close ?? null,
113
+ aboveMa5: isAbove(latest.close, latestIndicator?.ma5),
114
+ aboveMa10: isAbove(latest.close, latestIndicator?.ma10),
115
+ summary: "",
116
+ };
117
+ snapshot.summary = buildIndexSummary(snapshot);
118
+ return snapshot;
119
+ }
120
+ }
121
+ function isAbove(price, reference) {
122
+ if (!(reference != null && Number.isFinite(reference))) {
123
+ return null;
124
+ }
125
+ return price >= reference;
126
+ }
127
+ function buildIndexSummary(snapshot) {
128
+ const parts = [`${snapshot.name} ${formatMaybePrice(snapshot.latestClose)}`];
129
+ if (snapshot.changePct != null) {
130
+ parts.push(`${snapshot.changePct >= 0 ? "+" : ""}${snapshot.changePct.toFixed(2)}%`);
131
+ }
132
+ if (snapshot.aboveMa5 === true && snapshot.aboveMa10 === true) {
133
+ parts.push("站上 MA5/MA10");
134
+ }
135
+ else if (snapshot.aboveMa5 === true) {
136
+ parts.push("站上 MA5");
137
+ }
138
+ else if (snapshot.aboveMa10 === false) {
139
+ parts.push("位于 MA10 下方");
140
+ }
141
+ return parts.join(" | ");
142
+ }
143
+ function deriveMarketBias(snapshots) {
144
+ let score = 0;
145
+ for (const snapshot of snapshots) {
146
+ if (snapshot.changePct != null) {
147
+ if (snapshot.changePct >= 0.6) {
148
+ score += 2;
149
+ }
150
+ else if (snapshot.changePct > 0) {
151
+ score += 1;
152
+ }
153
+ else if (snapshot.changePct <= -0.6) {
154
+ score -= 2;
155
+ }
156
+ else if (snapshot.changePct < 0) {
157
+ score -= 1;
158
+ }
159
+ }
160
+ if (snapshot.aboveMa5 === true) {
161
+ score += 1;
162
+ }
163
+ else if (snapshot.aboveMa5 === false) {
164
+ score -= 1;
165
+ }
166
+ }
167
+ if (score >= 3) {
168
+ return "tailwind";
169
+ }
170
+ if (score <= -3) {
171
+ return "headwind";
172
+ }
173
+ return "neutral";
174
+ }
175
+ function formatMarketBiasLabel(bias) {
176
+ switch (bias) {
177
+ case "tailwind":
178
+ return "大盘偏顺风";
179
+ case "headwind":
180
+ return "大盘偏逆风";
181
+ default:
182
+ return "大盘中性";
183
+ }
184
+ }
185
+ function formatMaybePrice(value) {
186
+ return value == null ? "-" : value.toFixed(2);
187
+ }
@@ -0,0 +1,9 @@
1
+ import type { WatchlistItem } from "../../types/domain.js";
2
+ import { MxApiService } from "../../services/mx-search-service.js";
3
+ import type { NewsAnalysisContext } from "../types/composite-analysis.js";
4
+ export declare class NewsAnalysisProvider {
5
+ private readonly mxApiService;
6
+ constructor(mxApiService: MxApiService);
7
+ load(symbol: string, companyName: string, watchlistItem?: WatchlistItem | null): Promise<NewsAnalysisContext>;
8
+ private searchDocuments;
9
+ }
@@ -0,0 +1,60 @@
1
+ import { buildBoardNewsQuery } from "../../services/watchlist-profile-service.js";
2
+ const MAX_NEWS_DOCUMENTS = 8;
3
+ const MAX_BOARD_DOCUMENTS = 6;
4
+ export class NewsAnalysisProvider {
5
+ mxApiService;
6
+ constructor(mxApiService) {
7
+ this.mxApiService = mxApiService;
8
+ }
9
+ async load(symbol, companyName, watchlistItem = null) {
10
+ const query = buildNewsQuery(symbol, companyName);
11
+ const boardQuery = watchlistItem
12
+ ? buildBoardNewsQuery({
13
+ sector: watchlistItem.sector,
14
+ themes: watchlistItem.themes,
15
+ })
16
+ : null;
17
+ if (!this.mxApiService.isConfigured()) {
18
+ return {
19
+ symbol,
20
+ companyName,
21
+ query,
22
+ documents: [],
23
+ available: false,
24
+ boardQuery,
25
+ boardDocuments: [],
26
+ boardAvailable: false,
27
+ };
28
+ }
29
+ const [documents, boardDocuments] = await Promise.all([
30
+ this.searchDocuments(query, MAX_NEWS_DOCUMENTS, symbol, "company"),
31
+ boardQuery ? this.searchDocuments(boardQuery, MAX_BOARD_DOCUMENTS, symbol, "board") : Promise.resolve([]),
32
+ ]);
33
+ return {
34
+ symbol,
35
+ companyName,
36
+ query,
37
+ documents,
38
+ available: documents.length > 0,
39
+ boardQuery,
40
+ boardDocuments,
41
+ boardAvailable: boardDocuments.length > 0,
42
+ };
43
+ }
44
+ async searchDocuments(query, limit, symbol, scope) {
45
+ try {
46
+ return (await this.mxApiService.search(query)).slice(0, limit);
47
+ }
48
+ catch (error) {
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ console.warn(`[analyze] ${scope} news fetch skipped for ${symbol}: ${message}`);
51
+ return [];
52
+ }
53
+ }
54
+ }
55
+ function buildNewsQuery(symbol, companyName) {
56
+ if (companyName && companyName !== symbol) {
57
+ return `${companyName} ${symbol} 最新新闻 公告 研报`;
58
+ }
59
+ return `${symbol} 最新新闻 公告 研报`;
60
+ }
@@ -0,0 +1,11 @@
1
+ export interface AnalysisStepTask<TInput, TResult> {
2
+ taskName: string;
3
+ prepare(input: TInput): Promise<{
4
+ systemPrompt: string;
5
+ userPrompt: string;
6
+ }> | {
7
+ systemPrompt: string;
8
+ userPrompt: string;
9
+ };
10
+ parseResult(analysisText: string, input: TInput): Promise<TResult> | TResult;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export interface AnalysisTask<TInput, TResult> {
2
+ taskName: string;
3
+ prepare(input: TInput): Promise<{
4
+ systemPrompt: string;
5
+ userPrompt: string;
6
+ }> | {
7
+ systemPrompt: string;
8
+ userPrompt: string;
9
+ };
10
+ parseResult(analysisText: string, input: TInput): Promise<TResult> | TResult;
11
+ persistResult(result: TResult, input: TInput): Promise<void> | void;
12
+ formatForUser(result: TResult): string;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { KeyLevelsRepository } from "../../storage/repositories/key-levels-repo.js";
2
+ import { AnalysisLogRepository } from "../../storage/repositories/analysis-log-repo.js";
3
+ import type { AnalysisTask } from "./analysis-task.js";
4
+ import type { CompositeAnalysisInput, CompositeAnalysisResult } from "../types/composite-analysis.js";
5
+ export declare class CompositeStockAnalysisTask implements AnalysisTask<CompositeAnalysisInput, CompositeAnalysisResult> {
6
+ private readonly keyLevelsRepository;
7
+ private readonly analysisLogRepository;
8
+ readonly taskName = "composite_stock";
9
+ constructor(keyLevelsRepository: KeyLevelsRepository, analysisLogRepository: AnalysisLogRepository);
10
+ prepare(input: CompositeAnalysisInput): {
11
+ systemPrompt: string;
12
+ userPrompt: string;
13
+ };
14
+ parseResult(analysisText: string, input: CompositeAnalysisInput): CompositeAnalysisResult;
15
+ persistResult(result: CompositeAnalysisResult, input: CompositeAnalysisInput): Promise<void>;
16
+ formatForUser(result: CompositeAnalysisResult): string;
17
+ }
@@ -0,0 +1,47 @@
1
+ import { COMPOSITE_ANALYSIS_SYSTEM_PROMPT, buildCompositeAnalysisUserPrompt, } from "../../prompts/analysis/index.js";
2
+ import { formatChinaDateTime } from "../../utils/china-time.js";
3
+ import { formatKeyLevelsAnalysis, parseKeyLevels, validateKeyLevels, } from "../parsers/key-levels.parser.js";
4
+ export class CompositeStockAnalysisTask {
5
+ keyLevelsRepository;
6
+ analysisLogRepository;
7
+ taskName = "composite_stock";
8
+ constructor(keyLevelsRepository, analysisLogRepository) {
9
+ this.keyLevelsRepository = keyLevelsRepository;
10
+ this.analysisLogRepository = analysisLogRepository;
11
+ }
12
+ prepare(input) {
13
+ return {
14
+ systemPrompt: COMPOSITE_ANALYSIS_SYSTEM_PROMPT,
15
+ userPrompt: buildCompositeAnalysisUserPrompt(input),
16
+ };
17
+ }
18
+ parseResult(analysisText, input) {
19
+ const parsed = parseKeyLevels(analysisText);
20
+ if (!parsed) {
21
+ return { analysisText, levels: null };
22
+ }
23
+ const levels = {
24
+ ...parsed,
25
+ symbol: input.market.symbol,
26
+ analysis_date: formatChinaDateTime().slice(0, 10),
27
+ analysis_text: analysisText,
28
+ };
29
+ validateKeyLevels(levels);
30
+ return { analysisText, levels };
31
+ }
32
+ async persistResult(result, input) {
33
+ const logEntry = {
34
+ symbol: input.market.symbol,
35
+ analysis_date: formatChinaDateTime().slice(0, 10),
36
+ analysis_text: result.analysisText,
37
+ structured_ok: result.levels != null,
38
+ };
39
+ if (result.levels) {
40
+ await this.keyLevelsRepository.save(input.market.symbol, result.levels);
41
+ }
42
+ await this.analysisLogRepository.append(logEntry);
43
+ }
44
+ formatForUser(result) {
45
+ return formatKeyLevelsAnalysis(result.analysisText, result.levels);
46
+ }
47
+ }
@@ -0,0 +1,10 @@
1
+ import type { AnalysisStepTask } from "./analysis-step-task.js";
2
+ import type { FinancialAnalysisContext, FinancialInsightResult } from "../types/composite-analysis.js";
3
+ export declare class FinancialFundamentalLiteTask implements AnalysisStepTask<FinancialAnalysisContext, FinancialInsightResult> {
4
+ readonly taskName = "financial_fundamental_lite";
5
+ prepare(input: FinancialAnalysisContext): {
6
+ systemPrompt: string;
7
+ userPrompt: string;
8
+ };
9
+ parseResult(analysisText: string): FinancialInsightResult;
10
+ }
@@ -0,0 +1,21 @@
1
+ import { FINANCIAL_LITE_ANALYSIS_SYSTEM_PROMPT, buildFinancialLiteAnalysisUserPrompt, } from "../../prompts/analysis/index.js";
2
+ import { parseFinancialInsightResult, } from "./financial-fundamental.task.js";
3
+ export class FinancialFundamentalLiteTask {
4
+ taskName = "financial_fundamental_lite";
5
+ prepare(input) {
6
+ if (!input.liteSnapshot) {
7
+ throw new Error(`没有找到 ${input.symbol} 的 lite 财务指标数据`);
8
+ }
9
+ return {
10
+ systemPrompt: FINANCIAL_LITE_ANALYSIS_SYSTEM_PROMPT,
11
+ userPrompt: buildFinancialLiteAnalysisUserPrompt({
12
+ symbol: input.symbol,
13
+ companyName: input.companyName,
14
+ snapshot: input.liteSnapshot,
15
+ }),
16
+ };
17
+ }
18
+ parseResult(analysisText) {
19
+ return parseFinancialInsightResult(analysisText);
20
+ }
21
+ }
@@ -0,0 +1,12 @@
1
+ import type { AnalysisStepTask } from "./analysis-step-task.js";
2
+ import type { FinancialAnalysisContext, FinancialInsightResult } from "../types/composite-analysis.js";
3
+ export declare class FinancialFundamentalTask implements AnalysisStepTask<FinancialAnalysisContext, FinancialInsightResult> {
4
+ readonly taskName = "financial_fundamental";
5
+ prepare(input: FinancialAnalysisContext): {
6
+ systemPrompt: string;
7
+ userPrompt: string;
8
+ };
9
+ parseResult(analysisText: string): FinancialInsightResult;
10
+ }
11
+ export declare function buildFinancialFallbackResult(): FinancialInsightResult;
12
+ export declare function parseFinancialInsightResult(analysisText: string): FinancialInsightResult;
@@ -0,0 +1,64 @@
1
+ import { FINANCIAL_ANALYSIS_SYSTEM_PROMPT, buildFinancialAnalysisUserPrompt, } from "../../prompts/analysis/index.js";
2
+ import { parseJsonBlock } from "../parsers/json-block.parser.js";
3
+ export class FinancialFundamentalTask {
4
+ taskName = "financial_fundamental";
5
+ prepare(input) {
6
+ if (input.mode !== "full" || !input.snapshot) {
7
+ throw new Error(`没有找到 ${input.symbol} 的财务数据`);
8
+ }
9
+ return {
10
+ systemPrompt: FINANCIAL_ANALYSIS_SYSTEM_PROMPT,
11
+ userPrompt: buildFinancialAnalysisUserPrompt({
12
+ symbol: input.symbol,
13
+ companyName: input.companyName,
14
+ snapshot: input.snapshot,
15
+ }),
16
+ };
17
+ }
18
+ parseResult(analysisText) {
19
+ return parseFinancialInsightResult(analysisText);
20
+ }
21
+ }
22
+ export function buildFinancialFallbackResult() {
23
+ return {
24
+ analysisText: "未获取到有效财务数据,本轮未执行基本面子分析。",
25
+ score: null,
26
+ bias: "neutral",
27
+ strengths: [],
28
+ risks: [],
29
+ watchItems: [],
30
+ };
31
+ }
32
+ export function parseFinancialInsightResult(analysisText) {
33
+ const parsed = parseJsonBlock(analysisText);
34
+ return {
35
+ analysisText: analysisText.trim(),
36
+ score: normalizeScore(parsed?.score),
37
+ bias: normalizeBias(parsed?.bias),
38
+ strengths: normalizeStringList(parsed?.strengths),
39
+ risks: normalizeStringList(parsed?.risks),
40
+ watchItems: normalizeStringList(parsed?.watch_items),
41
+ };
42
+ }
43
+ function normalizeScore(value) {
44
+ if (!Number.isFinite(Number(value))) {
45
+ return null;
46
+ }
47
+ const score = Math.trunc(Number(value));
48
+ return score >= 1 && score <= 10 ? score : null;
49
+ }
50
+ function normalizeBias(value) {
51
+ if (value === "positive" || value === "negative") {
52
+ return value;
53
+ }
54
+ return "neutral";
55
+ }
56
+ function normalizeStringList(value) {
57
+ if (!Array.isArray(value)) {
58
+ return [];
59
+ }
60
+ return value
61
+ .map((item) => String(item ?? "").trim())
62
+ .filter(Boolean)
63
+ .slice(0, 3);
64
+ }
@@ -0,0 +1,10 @@
1
+ import type { AnalysisStepTask } from "./analysis-step-task.js";
2
+ import type { MarketAnalysisContext, TechnicalSignalResult } from "../types/composite-analysis.js";
3
+ export declare class KlineTechnicalSignalTask implements AnalysisStepTask<MarketAnalysisContext, TechnicalSignalResult> {
4
+ readonly taskName = "kline_technical_signal";
5
+ prepare(input: MarketAnalysisContext): {
6
+ systemPrompt: string;
7
+ userPrompt: string;
8
+ };
9
+ parseResult(analysisText: string, input: MarketAnalysisContext): TechnicalSignalResult;
10
+ }
@@ -0,0 +1,41 @@
1
+ import { ANALYSIS_COMMON_SYSTEM_PROMPT, buildKlineAnalysisUserPrompt, } from "../../prompts/analysis/index.js";
2
+ import { formatChinaDateTime } from "../../utils/china-time.js";
3
+ import { parseKeyLevels, validateKeyLevels } from "../parsers/key-levels.parser.js";
4
+ export class KlineTechnicalSignalTask {
5
+ taskName = "kline_technical_signal";
6
+ prepare(input) {
7
+ if (input.klines.length === 0) {
8
+ throw new Error(`没有找到 ${input.symbol} 的K线数据,请先执行 fetch-klines`);
9
+ }
10
+ if (input.indicators.length === 0) {
11
+ throw new Error(`没有找到 ${input.symbol} 的指标数据,请先执行 fetch-klines`);
12
+ }
13
+ return {
14
+ systemPrompt: ANALYSIS_COMMON_SYSTEM_PROMPT,
15
+ userPrompt: buildKlineAnalysisUserPrompt({
16
+ symbol: input.symbol,
17
+ costPrice: input.watchlistItem?.costPrice ?? null,
18
+ klines: input.klines,
19
+ indicators: input.indicators,
20
+ intradayKlines: input.intradayKlines,
21
+ intradayIndicators: input.intradayIndicators,
22
+ realtimeQuote: input.realtimeQuote,
23
+ reviewMemory: input.reviewMemory,
24
+ }),
25
+ };
26
+ }
27
+ parseResult(analysisText, input) {
28
+ const parsed = parseKeyLevels(analysisText);
29
+ if (!parsed) {
30
+ return { analysisText, levels: null };
31
+ }
32
+ const levels = {
33
+ ...parsed,
34
+ symbol: input.symbol,
35
+ analysis_date: formatChinaDateTime().slice(0, 10),
36
+ analysis_text: analysisText,
37
+ };
38
+ validateKeyLevels(levels);
39
+ return { analysisText, levels };
40
+ }
41
+ }
@@ -0,0 +1,32 @@
1
+ import type { KeyLevels, WatchlistItem } from "../../types/domain.js";
2
+ import type { IndicatorRow } from "../../types/indicator.js";
3
+ import type { TickFlowIntradayKlineRow, TickFlowKlineRow, TickFlowQuote } from "../../types/tickflow.js";
4
+ import { KeyLevelsRepository } from "../../storage/repositories/key-levels-repo.js";
5
+ import { AnalysisLogRepository } from "../../storage/repositories/analysis-log-repo.js";
6
+ import { AnalysisTask } from "./analysis-task.js";
7
+ export interface KlineTechnicalAnalysisInput {
8
+ symbol: string;
9
+ watchlistItem: WatchlistItem | null;
10
+ klines: TickFlowKlineRow[];
11
+ indicators: IndicatorRow[];
12
+ intradayKlines: TickFlowIntradayKlineRow[];
13
+ intradayIndicators: IndicatorRow[];
14
+ realtimeQuote: TickFlowQuote | null;
15
+ }
16
+ export interface KlineTechnicalAnalysisResult {
17
+ analysisText: string;
18
+ levels: KeyLevels | null;
19
+ }
20
+ export declare class KlineTechnicalAnalysisTask implements AnalysisTask<KlineTechnicalAnalysisInput, KlineTechnicalAnalysisResult> {
21
+ private readonly keyLevelsRepository;
22
+ private readonly analysisLogRepository;
23
+ readonly taskName = "kline_technical";
24
+ constructor(keyLevelsRepository: KeyLevelsRepository, analysisLogRepository: AnalysisLogRepository);
25
+ prepare(input: KlineTechnicalAnalysisInput): {
26
+ systemPrompt: string;
27
+ userPrompt: string;
28
+ };
29
+ parseResult(analysisText: string, input: KlineTechnicalAnalysisInput): KlineTechnicalAnalysisResult;
30
+ persistResult(result: KlineTechnicalAnalysisResult, input: KlineTechnicalAnalysisInput): Promise<void>;
31
+ formatForUser(result: KlineTechnicalAnalysisResult): string;
32
+ }
@@ -0,0 +1,61 @@
1
+ import { ANALYSIS_COMMON_SYSTEM_PROMPT, buildKlineAnalysisUserPrompt, } from "../../prompts/analysis/index.js";
2
+ import { formatChinaDateTime } from "../../utils/china-time.js";
3
+ import { formatKeyLevelsAnalysis, parseKeyLevels, validateKeyLevels, } from "../parsers/key-levels.parser.js";
4
+ export class KlineTechnicalAnalysisTask {
5
+ keyLevelsRepository;
6
+ analysisLogRepository;
7
+ taskName = "kline_technical";
8
+ constructor(keyLevelsRepository, analysisLogRepository) {
9
+ this.keyLevelsRepository = keyLevelsRepository;
10
+ this.analysisLogRepository = analysisLogRepository;
11
+ }
12
+ prepare(input) {
13
+ if (input.klines.length === 0) {
14
+ throw new Error(`没有找到 ${input.symbol} 的K线数据,请先执行 fetch-klines`);
15
+ }
16
+ if (input.indicators.length === 0) {
17
+ throw new Error(`没有找到 ${input.symbol} 的指标数据,请先执行 fetch-klines`);
18
+ }
19
+ return {
20
+ systemPrompt: ANALYSIS_COMMON_SYSTEM_PROMPT,
21
+ userPrompt: buildKlineAnalysisUserPrompt({
22
+ symbol: input.symbol,
23
+ costPrice: input.watchlistItem?.costPrice ?? null,
24
+ klines: input.klines,
25
+ indicators: input.indicators,
26
+ intradayKlines: input.intradayKlines,
27
+ intradayIndicators: input.intradayIndicators,
28
+ realtimeQuote: input.realtimeQuote,
29
+ }),
30
+ };
31
+ }
32
+ parseResult(analysisText, input) {
33
+ const parsed = parseKeyLevels(analysisText);
34
+ if (!parsed) {
35
+ return { analysisText, levels: null };
36
+ }
37
+ const levels = {
38
+ ...parsed,
39
+ symbol: input.symbol,
40
+ analysis_date: formatChinaDateTime().slice(0, 10),
41
+ analysis_text: analysisText,
42
+ };
43
+ validateKeyLevels(levels);
44
+ return { analysisText, levels };
45
+ }
46
+ async persistResult(result, input) {
47
+ const logEntry = {
48
+ symbol: input.symbol,
49
+ analysis_date: formatChinaDateTime().slice(0, 10),
50
+ analysis_text: result.analysisText,
51
+ structured_ok: result.levels != null,
52
+ };
53
+ if (result.levels) {
54
+ await this.keyLevelsRepository.save(input.symbol, result.levels);
55
+ }
56
+ await this.analysisLogRepository.append(logEntry);
57
+ }
58
+ formatForUser(result) {
59
+ return formatKeyLevelsAnalysis(result.analysisText, result.levels);
60
+ }
61
+ }
@@ -0,0 +1,11 @@
1
+ import type { AnalysisStepTask } from "./analysis-step-task.js";
2
+ import type { NewsAnalysisContext, NewsInsightResult } from "../types/composite-analysis.js";
3
+ export declare class NewsCatalystTask implements AnalysisStepTask<NewsAnalysisContext, NewsInsightResult> {
4
+ readonly taskName = "news_catalyst";
5
+ prepare(input: NewsAnalysisContext): {
6
+ systemPrompt: string;
7
+ userPrompt: string;
8
+ };
9
+ parseResult(analysisText: string): NewsInsightResult;
10
+ }
11
+ export declare function buildNewsFallbackResult(): NewsInsightResult;