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
@@ -1,4 +1,6 @@
1
1
  import { formatChinaDateTime } from "../utils/china-time.js";
2
+ import { formatCostPrice } from "../utils/cost-price.js";
3
+ import { normalizeTickFlowChangePct, resolveTickFlowKlineChangePct } from "../utils/tickflow-quote.js";
2
4
  const LEVEL_BUFFER = 0.005;
3
5
  const INTRADAY_PERIOD = "1m";
4
6
  const MARKET_OVERVIEW_FLASH_KEYWORDS = [
@@ -17,7 +19,8 @@ export class PostCloseReviewService {
17
19
  intradayKlinesRepository;
18
20
  flashDeliveryRepository;
19
21
  flashRepository;
20
- constructor(watchlistService, compositeAnalysisOrchestrator, analysisService, postCloseReviewTask, keyLevelsRepository, keyLevelsHistoryRepository, klinesRepository, intradayKlinesRepository, flashDeliveryRepository, flashRepository) {
22
+ industryPeerService;
23
+ constructor(watchlistService, compositeAnalysisOrchestrator, analysisService, postCloseReviewTask, keyLevelsRepository, keyLevelsHistoryRepository, klinesRepository, intradayKlinesRepository, flashDeliveryRepository, flashRepository, industryPeerService) {
21
24
  this.watchlistService = watchlistService;
22
25
  this.compositeAnalysisOrchestrator = compositeAnalysisOrchestrator;
23
26
  this.analysisService = analysisService;
@@ -28,6 +31,7 @@ export class PostCloseReviewService {
28
31
  this.intradayKlinesRepository = intradayKlinesRepository;
29
32
  this.flashDeliveryRepository = flashDeliveryRepository;
30
33
  this.flashRepository = flashRepository;
34
+ this.industryPeerService = industryPeerService;
31
35
  }
32
36
  async run() {
33
37
  const watchlist = await this.watchlistService.list();
@@ -44,11 +48,35 @@ export class PostCloseReviewService {
44
48
  let marketOverview = null;
45
49
  for (const item of watchlist) {
46
50
  let compositeResult = null;
51
+ let marketSummary = null;
47
52
  try {
48
53
  const input = await this.compositeAnalysisOrchestrator.buildInput(item.symbol);
54
+ marketSummary = buildReviewMarketSummary(input.market.klines, input.market.realtimeQuote);
49
55
  marketOverview ??= input.market.marketOverview;
50
56
  const tradeDate = input.market.klines[input.market.klines.length - 1]?.trade_date ?? formatChinaDateTime().slice(0, 10);
51
57
  const validation = await this.buildValidationContext(item.symbol, tradeDate);
58
+ const peerContext = await this.industryPeerService.buildContext(item.symbol)
59
+ .catch((error) => ({
60
+ available: false,
61
+ summary: "未获取到申万三级同业表现。",
62
+ sw1Name: null,
63
+ sw2Name: null,
64
+ sw3Name: null,
65
+ sw3UniverseId: null,
66
+ peerCount: 0,
67
+ otherStockCount: 0,
68
+ advanceCount: 0,
69
+ declineCount: 0,
70
+ flatCount: 0,
71
+ averageChangePct: null,
72
+ medianChangePct: null,
73
+ targetChangePct: null,
74
+ targetRank: null,
75
+ targetPercentile: null,
76
+ leaders: [],
77
+ laggards: [],
78
+ note: error instanceof Error ? error.message : String(error),
79
+ }));
52
80
  compositeResult = await this.compositeAnalysisOrchestrator.analyzeInput(input);
53
81
  const flashContext = await this.buildFlashContext(item.symbol, tradeDate);
54
82
  const review = await this.analysisService.runTask(this.postCloseReviewTask, {
@@ -56,8 +84,9 @@ export class PostCloseReviewService {
56
84
  compositeResult,
57
85
  validation,
58
86
  flashContext,
87
+ peerContext,
59
88
  });
60
- const message = this.formatDetailMessage(item, validation, review);
89
+ const message = this.formatDetailMessage(item, validation, review, marketSummary, peerContext);
61
90
  await this.persistReview(item.symbol, message, review);
62
91
  entries.push({
63
92
  ok: true,
@@ -73,7 +102,7 @@ export class PostCloseReviewService {
73
102
  await this.persistFallbackCompositeReview(item.symbol, compositeResult);
74
103
  }
75
104
  entries.push({ ok: false, item, errorMessage: message });
76
- detailMessages.push(this.formatFailureMessage(item, message, compositeResult));
105
+ detailMessages.push(this.formatFailureMessage(item, message, compositeResult, marketSummary));
77
106
  }
78
107
  }
79
108
  const overviewMessage = this.formatOverviewMessage(marketOverview, entries);
@@ -207,17 +236,20 @@ export class PostCloseReviewService {
207
236
  ];
208
237
  return `**🧭 收盘复盘总览**\n\n${lines.join("\n")}`.trim();
209
238
  }
210
- formatDetailMessage(item, validation, review) {
211
- return formatPostCloseReviewDetailMessage(item, validation, review);
239
+ formatDetailMessage(item, validation, review, marketSummary, peerContext = null) {
240
+ return formatPostCloseReviewDetailMessage(item, validation, review, marketSummary, peerContext);
212
241
  }
213
- formatFailureMessage(item, errorMessage, compositeResult) {
214
- return formatPostCloseReviewFailureMessage(item, errorMessage, compositeResult);
242
+ formatFailureMessage(item, errorMessage, compositeResult, marketSummary) {
243
+ return formatPostCloseReviewFailureMessage(item, errorMessage, compositeResult, marketSummary);
215
244
  }
216
245
  }
217
- export function formatPostCloseReviewDetailMessage(item, validation, review) {
246
+ export function formatPostCloseReviewDetailMessage(item, validation, review, marketSummary = null, peerContext = null) {
247
+ const marketMeta = formatReviewMarketMeta(item, marketSummary);
248
+ const industryPosition = formatIndustryPosition(peerContext);
218
249
  const lines = [
219
250
  `**📘 收盘复盘|${item.name}(${item.symbol})**`,
220
251
  `${formatValidationVerdictBadge(validation.verdict)} 昨日验证:${formatValidationVerdictLabel(validation.verdict)} | ${formatDecisionBadge(review.decision)} 明日处理:${formatDecisionLabel(review.decision)}`,
252
+ ...(marketMeta ? [marketMeta] : []),
221
253
  "",
222
254
  formatSectionTitle("📍", "昨日关键位验证"),
223
255
  `• 结论:${validation.summary}`,
@@ -227,7 +259,11 @@ export function formatPostCloseReviewDetailMessage(item, validation, review) {
227
259
  review.sessionSummary || "未生成盘面一句话总结。",
228
260
  "",
229
261
  formatSectionTitle("🌐", "大盘与板块"),
230
- `• 风向:大盘 ${formatMarketBiasBadge(review.marketBias)}${formatMarketBiasLabel(review.marketBias)} | 板块 ${formatMarketBiasBadge(review.sectorBias)}${formatMarketBiasLabel(review.sectorBias)}`,
262
+ [
263
+ `• 风向:大盘 ${formatMarketBiasBadge(review.marketBias)}${formatMarketBiasLabel(review.marketBias)}`,
264
+ `板块 ${formatMarketBiasBadge(review.sectorBias)}${formatMarketBiasLabel(review.sectorBias)}`,
265
+ industryPosition ? `同业 ${industryPosition}` : null,
266
+ ].filter(Boolean).join(" | "),
231
267
  review.marketSectorSummary || "未生成大盘/板块总结。",
232
268
  "",
233
269
  formatSectionTitle("📰", "新闻与公告"),
@@ -255,12 +291,14 @@ export function formatPostCloseReviewDetailMessage(item, validation, review) {
255
291
  lines.push(`• 支撑 ${formatMaybePrice(review.levels.support)} | 压力 ${formatMaybePrice(review.levels.resistance)} | 突破 ${formatMaybePrice(review.levels.breakthrough)}`, `• 止损 ${formatMaybePrice(review.levels.stop_loss)} | 止盈 ${formatMaybePrice(review.levels.take_profit)} | 评分 ${review.levels.score}/10`, ...(levelRail ? [`• 价位框架:${levelRail}`] : []), "", formatSectionTitle("✅", "操作建议"), review.actionAdvice || "按关键位和次日量价配合再决定是否执行。");
256
292
  return lines.join("\n");
257
293
  }
258
- export function formatPostCloseReviewFailureMessage(item, errorMessage, compositeResult) {
294
+ export function formatPostCloseReviewFailureMessage(item, errorMessage, compositeResult, marketSummary = null) {
259
295
  const fallback = compositeResult?.levels
260
296
  ? "已保留综合分析生成的关键位,可稍后用 view_analysis 或 analyze 复核。"
261
297
  : "本轮未生成可用关键位。";
298
+ const marketMeta = formatReviewMarketMeta(item, marketSummary);
262
299
  return [
263
300
  `**⚠️ 收盘复盘|${item.name}(${item.symbol})**`,
301
+ ...(marketMeta ? ["", marketMeta] : []),
264
302
  "",
265
303
  formatSectionTitle("❌", "失败原因"),
266
304
  errorMessage,
@@ -289,6 +327,71 @@ function toHistoryEntry(symbol, analysisText, levels) {
289
327
  score: levels.score,
290
328
  };
291
329
  }
330
+ function buildReviewMarketSummary(klines, realtimeQuote) {
331
+ const latestKline = klines[klines.length - 1] ?? null;
332
+ if (!latestKline && !realtimeQuote) {
333
+ return null;
334
+ }
335
+ const latestClose = latestKline?.close ?? realtimeQuote?.last_price ?? null;
336
+ const dailyChangePct = normalizeTickFlowChangePct(realtimeQuote?.ext?.change_pct)
337
+ ?? resolveTickFlowKlineChangePct(latestKline);
338
+ return {
339
+ latestClose,
340
+ dailyChangePct,
341
+ };
342
+ }
343
+ function formatReviewMarketMeta(item, marketSummary) {
344
+ const parts = [];
345
+ if (marketSummary?.latestClose != null && Number.isFinite(marketSummary.latestClose)) {
346
+ parts.push(`• 收盘 ${marketSummary.latestClose.toFixed(2)}`);
347
+ }
348
+ if (marketSummary?.dailyChangePct != null && Number.isFinite(marketSummary.dailyChangePct)) {
349
+ parts.push(`当日 ${formatSignedPct(marketSummary.dailyChangePct)}`);
350
+ }
351
+ if (item.costPrice != null && Number.isFinite(item.costPrice) && item.costPrice > 0) {
352
+ parts.push(`成本 ${formatCostPrice(item.costPrice)}`);
353
+ }
354
+ return parts.length > 0 ? parts.join(" | ") : null;
355
+ }
356
+ function formatSignedPct(value) {
357
+ return `${value >= 0 ? "+" : ""}${value.toFixed(2)}%`;
358
+ }
359
+ function formatIndustryPosition(context) {
360
+ if (!context?.available || !context.targetRank || !(context.peerCount > 0)) {
361
+ return null;
362
+ }
363
+ return `${classifyIndustryPosition(context)}(${context.targetRank}/${context.peerCount})`;
364
+ }
365
+ function classifyIndustryPosition(context) {
366
+ const { targetRank, peerCount } = context;
367
+ if (!targetRank || !(peerCount > 0)) {
368
+ return "位置未知";
369
+ }
370
+ if (targetRank === 1) {
371
+ return "领涨";
372
+ }
373
+ if (targetRank === peerCount) {
374
+ return "领跌";
375
+ }
376
+ if (peerCount <= 3) {
377
+ return "中游";
378
+ }
379
+ const percentile = context.targetPercentile
380
+ ?? (peerCount > 1 ? 1 - ((targetRank - 1) / (peerCount - 1)) : 1);
381
+ if (percentile >= 0.8) {
382
+ return "领涨区";
383
+ }
384
+ if (percentile >= 0.6) {
385
+ return "偏强";
386
+ }
387
+ if (percentile > 0.4) {
388
+ return "中游";
389
+ }
390
+ if (percentile > 0.2) {
391
+ return "偏弱";
392
+ }
393
+ return "领跌区";
394
+ }
292
395
  function evaluateSupport(snapshot, row) {
293
396
  if (!(snapshot.support != null && snapshot.support > 0)) {
294
397
  return "支撑: 昨日未设置支撑位。";