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.
- package/README.md +11 -42
- package/dist/analysis/types/composite-analysis.d.ts +27 -0
- package/dist/background/realtime-monitor.worker.d.ts +1 -1
- package/dist/background/realtime-monitor.worker.js +3 -4
- package/dist/bootstrap.js +24 -4
- package/dist/config/tickflow-access.d.ts +2 -1
- package/dist/config/tickflow-access.js +10 -3
- package/dist/dev/run-monitor-loop.js +0 -1
- package/dist/dev/tickflow-assist-cli.js +4 -3
- package/dist/dev/validate-mx-search.js +10 -2
- package/dist/plugin-commands.js +27 -0
- package/dist/plugin.js +4 -6
- package/dist/prompts/analysis/kline-analysis-user-prompt.js +2 -1
- package/dist/prompts/analysis/post-close-review-user-prompt.js +40 -1
- package/dist/prompts/analysis/pre-market-brief-prompt.d.ts +3 -1
- package/dist/prompts/analysis/pre-market-brief-prompt.js +8 -3
- package/dist/services/industry-peer-service.d.ts +9 -0
- package/dist/services/industry-peer-service.js +152 -0
- package/dist/services/jin10-flash-monitor-service.js +2 -1
- package/dist/services/monitor-service.d.ts +1 -1
- package/dist/services/monitor-service.js +21 -26
- package/dist/services/mx-search-service.d.ts +8 -1
- package/dist/services/mx-search-service.js +400 -10
- package/dist/services/post-close-review-service.d.ts +11 -4
- package/dist/services/post-close-review-service.js +113 -10
- package/dist/services/pre-market-brief-service.js +500 -42
- package/dist/services/tickflow-client.d.ts +4 -1
- package/dist/services/tickflow-client.js +32 -0
- package/dist/services/tickflow-universe-service.d.ts +26 -0
- package/dist/services/tickflow-universe-service.js +213 -0
- package/dist/services/watchlist-profile-service.d.ts +4 -1
- package/dist/services/watchlist-profile-service.js +58 -29
- package/dist/services/watchlist-service.d.ts +5 -1
- package/dist/services/watchlist-service.js +9 -4
- package/dist/storage/repositories/universe-membership-repo.d.ts +11 -0
- package/dist/storage/repositories/universe-membership-repo.js +38 -0
- package/dist/storage/repositories/universe-repo.d.ts +17 -0
- package/dist/storage/repositories/universe-repo.js +62 -0
- package/dist/storage/schemas.d.ts +2 -0
- package/dist/storage/schemas.js +13 -0
- package/dist/tools/add-stock.tool.d.ts +2 -1
- package/dist/tools/add-stock.tool.js +10 -1
- package/dist/tools/eastmoney-watchlist.tool.d.ts +31 -0
- package/dist/tools/eastmoney-watchlist.tool.js +294 -0
- package/dist/tools/mx-data.tool.d.ts +8 -0
- package/dist/tools/mx-data.tool.js +94 -0
- package/dist/tools/mx-select-stock.tool.js +6 -2
- package/dist/tools/query-database.tool.js +6 -0
- package/dist/tools/refresh-watchlist-profiles.tool.d.ts +2 -1
- package/dist/tools/refresh-watchlist-profiles.tool.js +11 -1
- package/dist/tools/screen-stock-candidates.tool.d.ts +34 -0
- package/dist/tools/screen-stock-candidates.tool.js +477 -0
- package/dist/tools/test-alert.tool.js +56 -19
- package/dist/types/mx-data.d.ts +23 -0
- package/dist/types/mx-data.js +1 -0
- package/dist/types/mx-select-stock.d.ts +1 -0
- package/dist/types/mx-self-select.d.ts +30 -0
- package/dist/types/mx-self-select.js +1 -0
- package/dist/types/tickflow.d.ts +12 -0
- package/dist/utils/tickflow-quote.d.ts +5 -0
- package/dist/utils/tickflow-quote.js +31 -0
- package/openclaw.plugin.json +83 -6
- package/package.json +6 -6
- package/skills/stock-analysis/SKILL.md +39 -20
- 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
|
-
|
|
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
|
-
|
|
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 "支撑: 昨日未设置支撑位。";
|