Use when Gordon asks to analyze A-share theme structure, sector rotation, leader pullbacks,补涨,连板/炸板情绪, or to build a research-only watchlist using a reusable four-tier SOP engine (L1宏观→L2题材→L3个股→L4执行).
Use when Gordon asks to analyze A-share theme structure, sector rotation, leader pullbacks,补涨,连板/炸板情绪, or to build a research-only watchlist using a reusable four-tier SOP engine (L1宏观→L2题材→L3个股→L4执行).
Use this skill to apply Gordon's reusable A-share 题材结构判断流程. The goal is to classify market/theme state, identify main-line anchors and rotation branches, validate microstructure, and produce a research-only watchlist plus verification table.
This skill must not produce immediate trade execution advice. Output should be framed as research, monitoring, observation pools, and复盘标准.
Modular Sub-Skills (v1.7+)
The SOP engine can be loaded as a single monolithic skill (this file) or as independent modular sub-skills for lower context cost:
Tier
Skill Name
Load When
L1
ashare-sop-l1-macro-filter
Need only macro environment assessment (系统性风险/震荡市/多头趋势)
L2
ashare-sop-l2-theme-engine
Need theme identification, stage classification, stock pool construction, 5-factor scoring
L3
ashare-sop-l3-stock-resonance
Need individual stock buy/sell scoring, risk alerts, cooling mechanism
L4
ashare-sop-l4-execution-review
Need trade plan construction, manual confirmation, journaling, weekly review
Each sub-skill is self-contained with its own data sources, rules, pitfalls, and output templates. Load only the tiers relevant to the current task to save context.
Cron jobs continue to load this main skill — the sub-skills are additive, not replacement. When using sub-skills, load them in order: L1→L2→L3→L4 (each tier gates the next).
When to Use
Load this skill when Gordon asks about:
题材结构判断体系 / 题材生命周期 / 主线阶段.
分支轮动, 核心龙头回调, 补涨扩散, 退潮判断.
A-share current market structure and target observation names.
Turning an A-share watchlist into a reusable scoring/verification process.
Expanding SOP coverage to new sectors, systematically screening for emerging themes, or building a "轮动雷达" — use references/sector-expansion-workflow.md for the four-phase expansion pattern (candidate screening → quick three-question validation → framework integration → rotation radar).
Proposing improvements, extending the analysis model, or building a 完善路线图 / 改进计划 — use references/sop-improvement-roadmap.md for the 3-phase 8-direction framework and always check cronjob(action='list') before suggesting new infrastructure.
Do not use this for:
Long-horizon fundamental/BMP scoring unless paired with bmp-value-investing-model.
Actual buy/sell/position-sizing/execution instructions.
Crypto or non-A-share market structure.
Required Inputs
Collect as many layers as available. If any primary layer is missing, label the result as degraded.
Index and board heat: major index K lines, hot boards, board percentage moves.
Candidate K-line features: 5D/20D/60D returns, 80D or 250D range position, turnover amount, volume/amount ratio.
Fund flow: main net flow, 5D/20D net flow, margin/funding context where available.
Microstructure: real涨停池, 炸板池, 连板梯队, first/last seal time, open-board count, seal fund.
龙虎榜 / seats: institution, stock-connect, hot-money tags, total buy/sell/net buy.
Event context: holidays, macro calendar (via westock-data calendar --country china), industrial catalysts, company announcements (via nightly announcement scan in Obsidian). These are now automatically collected and cross-referenced against the candidate pool.
Engine Architecture (v1.7)
The SOP is structured as a four-tier, top-down engine. Each tier gates the next:
Tier
Module
Function
Frequency
L4: Execution & Review
人工确认与复盘引擎
Convert candidate signals into trade plans; post-trade review & attribution
盘后 & 盘中确认
L3: Trading
个股共振与风控引擎
Score individual stocks against buy/sell rules; output "候选观察" or "风控观察"
分钟级(盘中)
L2: Theme
动态题材结构与情绪引擎
Identify main themes, determine theme health & sentiment cycle
分钟级(盘中)& 盘后更新
L1: Environment
宏观市场过滤器
Assess overall market environment; decide whether trading is allowed
日级(盘前/盘中)
Engine Operating Principles
自上而下过滤 (Top-Down Filtering): Every trade decision must pass L1→L2→L3→L4 sequential gates. No逆势操作.
动态自适应 (Dynamic Adaptation): Theme anchors and core watchlists are identified dynamically from market data, not fixed in advance.
风险前置 (Risk-First): Any clear risk signal (systemic sell-off, retreat warning) immediately pauses or downgrades downstream modules.
人工终裁 (Human Final Approval): This engine outputs "候选观察" or "风控观察" alerts only. All real trades require manual confirmation and execution.
Advance/decline ratio (涨跌家数比, sourced from daily mood scan)
L1.2 Environment States
State
Trigger Conditions
Engine Impact
系统性风险 (Systemic Risk)
Any major index meets one of: (1) intraday drop > 3%; (2) closes below MA20 for 3 consecutive days with MA20 sloping down; (3) sentiment enters "恐慌期"
Global Shield: Output "暂停交易", disable all L2/L3 buy signals
震荡市 (Range-bound)
Indices between MA20 and MA60, or MA20/MA60 directions diverge
Reduced Weight: L2/L3 run but all candidate signals require stronger resonance
多头趋势 (Bull Trend)
Indices close above MA20 AND MA60, both MAs sloping up
Normal Mode: L2/L3 operate at full standard rules
L1.3 Implementation Notes
In the automated pipeline (Cron 816138bf3403 at 08:30, Cron a16035d3b237 at 15:00, Cron b2988fb281ad at 20:15):
L1 state is computed by ~/.hermes/scripts/l1_market_state.py --json, which queries TDengine daily K-line for 000001.SZ (平安银行) as market proxy.
The script returns {l1_state, ma20, ma60, ma20_slope, ma60_slope, evidence, gate_blocked}.
When gate_blocked=true (系统性风险 or 观望), the pipeline skips buy candidate generation entirely and outputs only the L1 state with a blocking notice.
The classification logic matches the backtest-validated protocol — see references/l1-gate-backtest-findings.md for full methodology, variant comparison, and rule effectiveness data.
L1 Gate Protocol (backtest-validated):
L1 State
Gate Action
系统性风险
⛔ BLOCK all buy signals globally
观望 (MA60 not formed, <65 days data)
⛔ BLOCK all buy signals
震荡市
✅ Normal rules (buy_score ≥ 2)
多头趋势
✅ Normal rules (buy_score ≥ 2)
Critical finding: L1 gate is the single largest alpha source in the SOP pipeline. Backtesting 2026 H1 shows L1+L3 improves D5 from 0.02% to 0.53% (26.5×) and D10 from 0.34% to 0.86% (2.5×) vs L3-only. Do NOT skip L1 gating — a strong L2 theme in a crashing market is still not actionable.
Quantitative health indicators (v1.7): 龙头承接 (leader above MA5, intraday change -2.5% to +1.5%), 中低位扩散 (branch/laggard up on expanding volume), 放量回落 (% of pool stocks falling on heavy volume), 补涨失败 (% of laggard stocks falling on shrinking volume).
Output: a shared vocabulary and observation frame.
Step 2 — Market and Candidate Initial Screen
Use board heat and K-line features to classify candidates.
Screening signals:
Board/theme appears near the top of hot board rankings.
5D/20D returns show main rise, repair, or overheating.
80D/250D range position distinguishes high-position leaders from lower-position repair names.
5D average amount and 5D/20D amount ratio confirm participation.
Layer candidates into:
核心高位强势池: high-position main-line leaders/anchors.
分支轮动/补涨池: branch or laggard names with theme linkage.
相对低位修复池: lower-position capacity/sector names starting to repair.
风险风向标: high-volume leaders whose weakness could spread.
Step 3 — Fund Flow and Technical Priority Sorting
Do not rank only by涨幅. Sort by whether money/structure support continuation.
Positive signals:
Main net inflow is positive.
5D/20D funding trend improves.
Turnover expands but does not look失控.
Trend structure has not broken down.
Lower-position repair names still have room structurally.
Negative signals:
High-position overheating plus main net outflow.
MACD/trend deterioration in a high-position name.
High turnover without price acceptance.
Leader negative feedback spreading into补涨 names.
Output: priority watchlist v2.
Step 4 — Real Microstructure Validation
For 游资-style structure, real microstructure is mandatory before raising confidence.
Required checks:
Limit-up pool count.
Broken-board pool count.
Highest board and 2板/3板/4板 distribution.
Industry/theme width inside the limit-up pool.
Whether core names sealed, resealed, broke, or failed.
Interpretation rules:
Effective expansion: limit-up width expands and the ladder remains healthy.
Constructive divergence: high-position leaders break/open, but low-position branches continue接力.
Strong divergence: high-position leaders fail and low-position补涨 also fails.
Retreat warning: limit-up pool shrinks, broken-board pool expands, ladder promotion worsens.
Output: microstructure validation v3.
Step 5 — 龙虎榜 / Seats and Verification Scorecard
Use 龙虎榜 and a 5-factor scorecard to make the watchlist复盘-friendly.
Seat interpretation:
Positive龙虎榜 net buy: add confidence.
Institution, stock-connect, or strong tagged hot-money participation: add confidence.
Large three-day net sell in a high-position leader: raise risk.
No龙虎榜 data: do not infer weakness; mark as “无席位确认”.
5-factor scorecard, each 0/1:
板块宽度: same theme continues to expand.
个股承接: no uncontrolled long negative candle, failed spike, or breakdown.
量能健康: volume expansion comes with acceptance, not chaotic turnover.
席位/资金: 龙虎榜, main flow, or financing supports the structure.
0 points or risk-anchor trigger: short-term structure weakens; reduce confidence.
Automated scoring: The LLM-based SOP pipeline describes these factors qualitatively but cannot compute the numeric 0/1 scores. A data-driven script computes them from actual indicators (KDJ/WR/BOLL via sop_enrich.py, ATR regime, MainNetFlow via westock-data asfund, 250D position). See references/factor-scorer.md for the script, calibration thresholds, and the daily Cron job (b2988fb281ad at 15:10 Beijing time) that feeds scored candidates into the pool pipeline.
Theme State Gating on Stock Selection
L2 theme state determines how strictly L3 filters candidates:
L2 State
Core Pool (核心股池)
Branch/Laggard Pool (分支/补涨股池)
退潮期 (Retreat)
清空所有候选观察,强制输出风控观察
Same — no buy candidates
分歧/分化期 (Divergence)
buy_score ≥ 3 (higher bar)
Normal rules apply
启动/发酵期 或 高潮/主升期
Normal rules
Normal rules
Part 3: L3 — Stock Resonance & Risk Control Engine
Frequency: Minute-level (intraday).
Goal: Within the theme context from L2, screen individual stocks and output "候选观察" or "风控观察" alerts.
L3.1 Stock Pool Hierarchy
Each active theme generates a dynamic stock pool in three layers:
核心股池 (Core): Top 3 by turnover in the theme, highest consecutive board count, or market-recognized leaders.
分支/补涨股池 (Branch/Laggard): Medium turnover within the theme; technical pattern shows "回踩趋势" or "密集区突破".
风险风向标 (Risk Anchor): High-volatility names strongly correlated with the theme, or names with huge prior gains.
est_turnover / 20D_avg_turnover ≥ 1.8 AND intraday_change < -1.0%
Output condition: sell_score ≥ 2 → output "风控观察".
L3.4 Cooling Mechanism
Per stock, same direction (buy candidate / risk alert): minimum 20-minute cooldown between alerts to avoid signal spam.
This applies to both the minute-level monitor and any downstream notification pipeline.
L3.5 Implementation Status & Backtest Validation
The buy_score/sell_score rules have been validated via backtest against 2026 H1 TDengine K-line data (6,839 stocks, 119 trading days). Full results and the backtest script live in references/l1-gate-backtest-findings.md:
✅ L1 gate is the dominant alpha source: D5 improves from +0.02% (L3-only) to +0.53% (L1+L3) — a 26.5× improvement
⚠️ R1 (突破20D高放量) never fires: 0.00% trigger rate — threshold close > 20D_high × 1.002 too strict for A-shares with 10% price limit
⚠️ Adaptive buy_score thresholds hurt, not help: Raising to buy_score≥3 in 震荡市 reduces D10 from 0.86% to 0.49%
⚠️ Right-skewed: Mean positive but median negative at all horizons — stop-loss discipline is critical
⚠️ L2 strict sector filter adds zero marginal value: market has active sectors 174/182 days with current criteria
In the production pipeline:
The factor_scorer.py v1.2 engine computes a different 5-factor scorecard (板块宽度/个股承接/量能健康/席位/资金/风险扣分) from actual data sources, which runs via cron b2988fb281ad.
The L3 buy_score/sell_score backtest confirms the 9 rules have real predictive power, but the standalone intraday L3 scorer is still the next engineering priority.
When building the L3 scorer, use sop_enrich.py for K-line derived indicators, westock-data asfund for turnover estimates, and TDengine stock.kline for the 120-day lookback window.
Part 4: L4 — Execution & Review Engine
Frequency: Post-market (盘后).
Goal: Convert L3 candidate signals into actionable trade plans, execute with manual confirmation, and journal all outcomes for iterative engine improvement.
L4.1 Trade Plan Generation (盘后)
When L3 outputs "候选观察", complete the following before entering manual confirmation:
基本面定性检查 (Fundamental Quick Check): Reference the BMP value investing model. Verify: industry outlook, business model clarity, obvious red flags. Exclude companies with clear fundamental defects.
交易计划制定 (Trade Plan Construction):
剧本类型 (Setup Type): 趋势转折 / 回踩趋势 / 密集区突破.
入场触发条件 (Entry Trigger): Specific intraday price signal (more precise than L3 alert).
防守位/止损位 (Stop Loss): Structural low, moving average, or volume node.
第一目标位 (First Target): Prior high, platform, or overhead supply zone.
计划盈亏比 (Planned R:R): MUST exceed 3:1.
初始计划亏损金额 (Planned Max Loss): Calculated from total account size.
L4.2 Manual Confirmation & Execution
All trade plans require manual confirmation before execution. No automated trading.
确认流程 (Confirmation Process): Record confirmation time, confirmation premise, confirmation conclusion.
手动处理边界 (Manual Execution Boundaries): Define acceptable deviation range during manual execution.
L4.3 Trade Journaling & Review
样本录入 (Sample Entry): All completed trades (win/loss) and important untriggered signals are recorded using the historical sample template: pre-trade state, plan, execution process, outcome, and attribution.
每周复盘 (Weekly Review): Use the trend trading system weekly review template: win rate, profit factor, rule-adherent failures, system-external errors, and rule gaps.
L4.4 Implementation Notes
L4 is currently manual (by design — 人工终裁). The existing infrastructure supports it via:
performance_tracker.py and tracker_index.csv for automated D1/D3/D5 forward return tracking.
historical_sop_replay.py for backtesting candidate rules against historical data.
Obsidian vault templates for trade journaling and weekly review.
Annual Mainline Deep-Dive Pattern
When Gordon asks to 深入分析年度主线 and build a 关注标的池, do not stop at naming the mainline. Produce a research-only layered watchlist from the YTD mainline using this evidence stack:
Confirm timestamp and separate 年度主线 from 盘中轮动/情绪扩散.
Score boards by YTD return, 20D/60D persistence, YTD/250D range position, 5D average amount, and 5D/20D amount ratio.
Build a bounded candidate universe from mainline branches and hot-stock/anchor names; compute YTD, 20D, 60D, 250D position, drawdown from 250D high, 5D amount, and amount ratio.
Add asfund, technical --group ma,macd,rsi, and margin/LHB context for high-priority candidates where available; use these as confidence/risk overlays, not standalone ranking signals.
Split output into 核心高位强势池, 分支轮动/补涨观察池, 低位修复/承接观察池, and 风险风向标.
Include validation rules: strong continuation, healthy divergence, strong divergence, retreat warning, and mainline replacement conditions.
Archive durable results under Intel with category: "a_share_observation" and action: "monitor"; keep wording as research/monitoring only.
For AI hard-tech annual mainline tasks, useful branch buckets are: 通信/CPO/光模块/光纤, PCB/元件/服务器链, 半导体设备/存储/材料, 电子化学品, plus 自动化/智能制造 as diffusion. Treat financials, pharma, and chemicals as daily repair/expansion unless they also pass YTD + persistence + capacity tests.
当 Gordon 问到分钟目标池(盘中监控池)的构成、更新频率、自动化、或需要手动增删标的时,加载该参考文件。
Data Collection Patterns
When Gordon asks whether the SOP can be profitable and only tick ledgers are available, use the degraded proxy validation pattern in references/degraded-profitability-validation.md: pre-register the proxy score, state missing layers, compare against random/non-hardtech controls, and keep research_only unless fixed after-cost/dropout gates pass.
When Gordon asks for the mainline “from this year’s market” or shifts from intraday heat to year-to-date structure, use references/ytd-mainline-vs-intraday-rotation.md: separate 年度主线 from 当前盘中主线 and 情绪扩散线 using YTD returns, 20D persistence, YTD range position, amount participation, and current涨停/炸板 breadth. Do not treat same-day hot-board rank as the annual mainline.
When Gordon asks to continue a semiconductor / hard-tech low-position or low-valuation screen into historical validation, win-rate, or profitability testing, use references/semiconductor-low-valuation-backtest.md: pre-register fixed variants and gates before results, audit TDengine tick/status coverage, build a daily-feature cache for 250D position and 20D/60D returns before mapping queues, and keep the run research_only unless all gates and controls pass.
For touch-limit / post-touch acceptance hypotheses, use references/limit-touch-followthrough-quality.md: pre-register touched-limit quality filters, require after-cost/dropout/tail-loss/cross-quarter/control gates, and record missing n150 coverage explicitly. If Gordon asks to supplement missing n150 coverage, use references/n150-cross-quarter-ledger-backfill.md before rerunning the validator: prefer incremental backfill from existing n80 feature caches over full raw-tick rebuilds, normalize CSV-loaded numeric fields, and verify hardtech/random/non-hardtech combined n150 ledgers before upgrading conclusions.
When Gordon asks to fuse short-term theme structure, market sentiment heat, and tick/T+1 validation into one system, or says “继续下一步” after that fusion, use references/theme-sentiment-tick-integrated-system.md: keep the fusion as sentiment/regime → theme/hypothesis → tick validation, isolate it from the BMP scoring harness under a dedicated subsystem, pre-register the first MVP before TDengine extraction, and let empty/missing feature mappings return insufficient_data rather than proxy-fitting after results.
For historical limit-up / broken-board data, first prove the source is genuinely date-addressable. Use references/historical-limit-pool-source-validation.md for source probes and the local tick reconstruction fallback. Short-term quote sites such as 短线侠 can be useful current monitors, but do not use current-only pool endpoints as historical evidence.
When Gordon asks to fuse short-term theme structure, market sentiment heat, and tick/T+1 quantitative validation into one operational system, use references/integrated-theme-sentiment-tick-system.md: keep the structure as 情绪定 regime → 题材定 hypothesis → tick 做 validation, produce research-only snapshots/queues/ledgers, and require pre-registered controls before any rule moves beyond research_only.
When upgrading an intraday/minute stock monitor for the A-share theme SOP, use references/minute-theme-state-monitor-pattern.md: fetch board bars and candidate bars together, normalize provider row order, use last as a current-session price fallback where needed, assess theme state before stock signals, and let 强势延续 / 良性分歧 / 退潮预警 / 中性观察 gate all individual alerts.
For the known data-gap profile of the SOP pipeline (which layers are source-limited vs pipeline-missing, and the improvement history across days), consult references/sop-known-data-gaps.md before reporting gaps in SOP output. As of 2026-06-26, all 10 data layers are ✅ complete — no ❌ or ⚠️ remain. Any future gap should be reported as a new discovery against this baseline.
Computing KDJ/WR/BOLL from K-line (Gap #8 — resolved): The westock-data technical command returns KDJ/WR/BOLL fields as empty or - for all stocks. The SOP now uses a client-side computation script instead:
The script fetches 80-day K-line via westock-data kline, then computes KDJ(9,3,3), WR(6), WR(10), BOLL(20,2) from OHLC data. Output is JSON with KDJ_K, KDJ_D, KDJ_J, WR_6, WR_10, BOLL_UPPER, BOLL_MID, BOLL_LOWER per code. Integrate these values into the 打分表 "个股承接" (KDJ crossover/overbought-oversold) and "量能健康" (price position relative to Bollinger Bands).
Reading market breadth from the mood scan (Gap #9 — resolved): The SOP does not call a separate breadth API. Instead, read the latest market mood scan from Obsidian:
Determine the most recent trading day date (YYYY-MM-DD format, use previous trading day for pre-market runs).
Extract up_count, down_count, flat_count, and total market turnover from the "## Breadth And Sentiment" table.
Use the up/down/flat ratio to inform the market structure section (consensus or divergence signal), and mark 涨跌家数 as ✅ (sourced from the mood scan) in the data-gap table.
When Gordon asks to improve, extend, or build a plan for the SOP analysis model, or asks for a 完善路线图 / 改进方向, use references/sop-improvement-roadmap.md: a 3-phase 8-direction improvement framework with detailed task requirements, dependency graph, and existing cron infrastructure inventory. Always check cronjob(action='list') before proposing new cron jobs — the pre-market SOP cron (816138bf3403) already runs daily at 08:30.
When Gordon asks to expand SOP coverage to new sectors, scan for emerging themes beyond the current mainline, or build a rotating board radar, use references/sector-expansion-workflow.md: four-phase expansion pattern (candidate screening with westock-data board + hot board → quick three-question validation on lead stock quality, fund direction, and structural position → framework integration with 5-factor scoring → rotation radar maintenance). Always cross-check board-level fund data against lead stock asfund before concluding support, and prefer lead stock K-lines over board K-lines which may return empty.
When Gordon asks to query TDengine for tick-level features (触板/封板/炸板/换手/量价), extract intraday microstructure for validation experiments, or build a tick feature extractor for the theme-sentiment-tick fusion system, use references/tdengine-tick-query-patterns.md: UTC timestamp conversion rules, code-format conventions, efficient query patterns (time-bound + code-filtered, max ~250 symbols/batch), schema mappings for ticks and stock_daily_status, and limit-up detection queries using stock_daily_status.limit_up_price.
For the 6 feature classes extracted by tdengine_feature_extractor.py and their semantics (触板/封板/炸板/换手/量价/过滤标记), use references/tick-feature-extractor-reference.md: feature definitions, detection thresholds, state-machine logic for seal/broken-board, and UTC time-window conventions.
When managing validation experiments (pre-registration, lifecycle tracking, gate evaluation, archival) for the SOP fusion system, use references/experiment-ledger-system.md: ledger schema, update_ledger.py commands, and the direct-scan fallback when hypothesis_queue is empty (documented in references/experiment-direct-scan-fallback.md).
When running the 6-gate validation framework against historical SOP replay data, use references/full-gate-validation-runner.md: gate definitions, gate_runner.py usage, and the current status of EXP-002.
Historical SOP replay and sector-gate enrichment (5b/6b pipeline). When Gordon asks to continue SOP improvement tasks 5b or 6b, or asks to run sector-level gate analysis on historical movers:
historical_sop_replay.py — scans TDengine ticks for daily ≥3% movers and computes D1/D3/D5 forward returns. Supports --start/--end date ranges. Outputs markdown reports to Intel/30_Topics/AShare/performance_tracker/historical_replay/. Requires ≥20 trading days for statistical conclusions.
sop_gate_full.py — enriches replay candidates with 6-factor sector gates (归属/板块涨/领涨/热门/量能/涨多) using TDengine sector supertables. Batch-loads all sector data per date, then scores in-memory. Outputs gate-distribution reports to Intel/30_Topics/AShare/performance_tracker/gate_analysis/.
TDengine sector table schemas (industry_sectors, sector_components, concept_stock_details): three supertables in the stock database providing daily sector OHLCV, stock-to-sector membership, and per-stock PE/PB. Full schema, coverage dates, batch-query patterns, and the 6-gate integration formula are in references/tdengine-sector-table-schemas.md. Key gotchas: sector_components uses stock_code_tag (not symbol); all three tables use exact timestamps T16:00:00.000Z (not range queries); idx_type values are Chinese ('概念板块', NOT 'concept'); TDengine rejects the N'...' prefix; and all three tables max out at 2026-06-17 while ticks goes to present day — mark sector gates as insufficient_data for dates beyond 06-17. Use file-based auth (.tdengine_auth) to avoid secrets sandbox mangling of credentials in source code.
When using the cross-asset regime overlay and NLP sentiment enhancement system built in Phase 3, use references/cross-asset-nlp-enhancement-system.md: script/Cron inventory, data sources, and integration with the existing SOP cron fleet.
When any module of the sentiment→theme→tick fusion system needs standardized enums or JSON Schemas, load them from the shared schemas/ directory under this skill:
The Cron job a16035d3b237 ("A股题材结构SOP收盘快照Pipeline") runs at 15:00 Beijing time Mon-Fri, loads this skill, executes both scripts, and delivers a summary to #a-share-watch. This is separate from the pre-market SOP cron (816138bf3403 at 08:30) — the 15:00 job captures closing microstructure while the 08:30 job provides pre-market watchlists.
Cron timing note: Tick data is ingested into TDengine at 20:00 Beijing time each trading day. TDengine-dependent cron jobs MUST be scheduled at or after 20:30 to ensure data availability. The 15:00 closing snapshot (a16035d3b237) uses only westock-data + Eastmoney pools (no TDengine dependency), so it stays at 15:00.
A third Cron job 5fca0a50acff ("每日 TDengine 特征提取") runs at 20:30 Mon-Fri, chained via context_from to a16035d3b237. It reads the freshly generated hypothesis_queue from data/snapshots/, extracts 6-class tick features for all candidates using scripts/tdengine_feature_daily.py (which invokes tdengine_feature_extractor.py), and writes results to data/features/features_YYYY-MM-DD.json. The worker script handles empty queues gracefully (prints a skip message and exits 0) so the chain never blocks on insufficient data.
A fourth Cron job f7220d53fa23 ("每日 SOP 候选追踪采集") runs at 20:30 Mon-Fri, chained via context_from to a16035d3b237. It reads the same hypothesis_queue from data/snapshots/ via scripts/performance_tracker.py, extracts trigger-day and next-day OHLC from TDengine ticks, computes D1 open/close returns, and appends to performance_tracker/tracker_index.csv. For full architecture, command modes (daily/backfill/update), CSV schema, and known limitations, see references/performance-tracker-system.md.
Factor backtest, weight engine, and AB test system (4a/4b/4c). When Gordon asks to evaluate factor predictive power, build dynamic factor weights, or run controlled AB experiments for SOP variants, use references/factor-backtest-weight-ab-system.md: three-tier pipeline (factor_backtest.py → weight_engine.py → ab_test_framework.py), each with gate rules, usage patterns, and current data-status limitations. All three modules require ≥50 trading days of tracker data for statistical conclusions, but produce valid structural output with any data volume.
All 10 data layers are now automated. There are no remaining gap categories that need manual sourcing in the standard pre-market SOP run. If a data source fails at runtime (API timeout, Obsidian file not yet written), mark it as a transient gap with the specific error and fall back to the most recent available data rather than labeling it as a permanent pipeline gap.
For bounded tick-validation cron iterations, if a newly discovered premarket/SOP note contains stock-level 分支接力锚 / 低位修复锚 rows after a prior run was blocked for missing mapping input, treat the smallest next iteration as mapping-queue registration only: verify stock codes, write a pending CSV/JSON/report with extractor-required columns (date, code, universe, theme, theme_stage, candidate_role, market_regime, sentiment_stage), keep market_regime=insufficient_data when breadth/board fields are missing, and do not run extractor/validation in the same cron turn.
westock-data board returns three tables: 行业板块涨幅排名 (changePct, turnoverRate, changePct5d, changePct20d, leadStock), 概念板块涨幅排名 (same fields), and 行业资金流入 Top5 (changePct, mainNetInflow, mainNetInflow5d, upDownRatio). Use the concept board rankings to cross-validate industry board signals — for example, if 半导体 (industry) is strong, check whether CPU概念, MLCC, SRAM, 芯片概念, 靶材 (concept boards) also rank high. At least 3 aligned concept boards in the top ranks strengthens the mainline thesis.
Event catalyst data collection (Gap #10 — resolved): Two command sources, both run during data collection phase:
# A. Macro calendar — today's China macro events
npx -y westock-data-skillhub@1.0.3 calendar YYYY-MM-DD --country china
Filter output to China rows only (工业企业利润, PMI, GDP, etc.). Note the time field — events at 09:30 affect open, afternoon events affect close.
# B. Announcement scan — read from Obsidian (no network call needed)read /Users/gordon8018/obsidian_vault/gordon8018/Intel/30_Topics/AShare/<prev-trading-day>-ashare-announcement-scan.md
From the "High-Signal Announcements" section, extract items in three tiers:
Directly relevant: stock code matches a candidate → fold into that stock's risk/score assessment
Sector-relevant: announcement industry overlaps with mainline → include in 事件催化速览 as sector catalyst
Sentiment-signal: control changes, large buybacks, major restructurings, regulatory policy → include as market sentiment background
In the output, add a "事件催化速览" subsection before the 分层观察池 with three rows: 📅 宏观日历 / 🏭 产业催化 / 📋 公司公告, each ≤ 3 items.
Jumping straight to target names. Always classify theme stage first, then candidates.
Ranking by涨幅 only. High涨幅 plus net outflow or heavy broken-board behavior is a risk signal, not automatic strength.
Treating hot-board data as microstructure. Hot boards are secondary; real涨停池/炸板池/连板梯队 are primary for 游资-style judgment.
Overreading missing 龙虎榜. No龙虎榜 means no seat confirmation, not no money.
Ignoring risk anchors. High-position leaders with large net sell, repeated炸板, or massive turnover can invalidate补涨 logic.
Skipping event context. Holidays and major macro/industry events can alter liquidity and兑现 behavior.
Using execution language. Keep outputs to research, monitoring, observation, and复盘 standards.
Backtesting the full SOP as if it were one signal. For profitability questions, first define a pre-registered degraded/proxy mapping and fixed gates; use references/theme-structure-sop-backtest-aux-data.md for the auxiliary data and source-validation pattern.
Using current-only pools as history. Some quote sites return current pools while ignoring date parameters; prove historical date-addressability first, or rebuild from local tick + daily limit-price data using references/historical-limit-pool-source-validation.md.
Running tick extractors on source-backed queues beyond status coverage. If a source-backed branch/laggard mapping queue exists but its date is later than verified same-date stock_daily_status/tick coverage, register a pending mapping CSV/JSON/report and block extractor/validation reruns until coverage catches up. Do not use degraded limit proxies as final validation evidence.
Confusing 年度主线 with 盘中热点. When the horizon is “今年行情”, do not rank by today’s hot board alone; use references/ytd-mainline-vs-intraday-rotation.md to separate durable YTD leadership from current repair, beta activation, or low-position emotional rotation.
Building stock-only intraday monitors. For minute alerts, classify the board/theme regime first and gate stock-level signals through that state. Also normalize provider bar order and current-session price fields before computing latest/previous bars; see references/minute-theme-state-monitor-pattern.md.
Treating source limitations as permanent. When a data source returns empty or - for certain fields (e.g. westock-data technical for KDJ/WR/BOLL), the first question is whether the indicators can be computed client-side from other available data (here: 80D K-line OHLC → KDJ/WR/BOLL via sop_enrich.py). If yes, write or use a computation script rather than permanently marking the layer as ❌. For layers that genuinely require external data aggregation (e.g. event catalysts), check whether existing cron outputs already solve the problem — the nightly announcement scan writes to Obsidian and westock-data calendar --country china provides macro events, both of which the SOP now reads directly. Reserve permanent ❌ only for layers where no data source, proxy, or computation path exists.
Confusing SOP analysis with BMP autoresearch premarket report. These are two separate cron pipelines with different output locations and different data-gap profiles. The SOP analysis (this skill) writes to Intel/00_Inbox/YYYY-MM-DD-ashare-theme-structure-sop.md and uses westock-data + Eastmoney pools. The BMP autoresearch premarket report writes to 研究沉淀/a-share-value-investing/reports/premarket/YYYY-MM-DD-premarket-auto.md and uses a different data-source stack (eastmoney push2, sina boards, cninfo announcements, etc.). When the user asks about "盘前内容" or "数据缺口", clarify which report they are referring to before diagnosing gaps. A gap fixed in one pipeline does not automatically fix the other.
Board K-line returns empty. westock-data kline with board codes may return empty data. Workaround: use individual lead stock K-lines (pulled via westock-data kline <stock_codes> --period day --limit 60 --fq bfq) to assess structural position instead of relying on board-level K-lines.
Board fund flow vs lead stock divergence. A board can show strong net inflow while its lead stock declines (e.g. 风电设备 on a broad selloff day). Always cross-check board-level mainNetInflow against the lead stock asfund MainNetFlow and MainInflowRank before concluding fund support. Board-level data alone is insufficient for individual stock decisions.
Proposing SOP improvements without checking existing cron infrastructure. Before suggesting new cron jobs, pipelines, or data collection for the SOP, always run cronjob(action='list') first — the pre-market SOP cron (816138bf3403, "A股题材结构SOP盘前标的池与打分") already runs daily at 08:30 Beijing time with skills a-share-theme-structure-sop, a-share-market-intel, obsidian, delivering to #a-share-watch. The closing snapshot cron (a16035d3b237, "A股题材结构SOP收盘快照Pipeline") runs at 15:00. Also check for related infrastructure: the market mood scan (0b8a46cb4c72), theme heat map (0f4e93ae1531), youzi data collection (99b2b18ae2c3), and the paused tick-validation lab (891e0efbd187) all provide data layers the SOP already consumes. When the user asks for improvements, first audit what's already running, then gap-fill rather than rebuild.
TDengine timestamp timezone trap. TDengine stores timestamps in UTC and REST API queries interpret string literals as UTC. Writing WHERE ts >= '2026-06-17 09:00:00' (Beijing market open) silently becomes 09:00 UTC (= 17:00 Beijing, after market close). Always convert: Beijing time minus 8 hours. For market hours in raw SQL: ts >= '<date> 01:00:00' AND ts <= '<date> 07:30:00'. When using beijing_to_utc(), pass Beijing hours, not UTC hours: beijing_to_utc(date, "09:15:00") → correct; beijing_to_utc(date, "01:15:00") → wrong (treated as 1AM Beijing). Also avoid scanning the 45B-row ticks super table without both a time bound and a symbol filter — always use WHERE symbol='...' AND ts >= .... Max ~250 symbols per batch. See references/tdengine-tick-query-patterns.md for full schema mappings, function spec, and query recipes.
Duplicating enums or schemas across fusion modules. The sentiment→theme→tick fusion system uses shared definitions in schemas/ (7 enums + 4 JSON Schemas). All downstream modules (1b–1e) must import from schemas/enums.py and validate against schemas/*.schema.json. Never define MarketRegime, SentimentStage, CandidateRole, or similar enums locally in a one-off script — this causes field-level drift between layers. If a new enum value is needed, add it in schemas/enums.py and the corresponding schema's enum list simultaneously.
Assuming hypothesis_queue is always populated. The Layer 2 theme_hypothesis.py script relies on westock-data commands (board/hot stock) which may return empty results for past dates. When hypothesis_queue has 0 candidates, do not block the validation pipeline — fall back to a direct TDengine scan: query stock_daily_status for the date to get limit_up_price, scan ticks for stocks closing near their limit, and compute experiment metrics from raw tick data. This direct-scan fallback is documented in references/experiment-direct-scan-fallback.md.
Trusting stock_daily_status.pre_close. The pre_close field in stock_daily_status is frequently NULL even when limit_up_price is populated. For limit-up detection and return calculations, use limit_up_price directly (it is reliably present) rather than computing pre_close * (1 + limit_ratio). For close-to-close returns where pre_close is needed, fall back to the first valid tick of the target day (open price in the tick stream, or the earliest current > 0 tick) when pre_close is NULL.
Auth header mangled during write_file. Writing a new Python script containing b"root:taosdata" (the TDengine auth credential) with write_file causes the line to be redacted to AUTH=*** or similar truncated forms. Primary workaround: create ~/.hermes/.tdengine_auth with the full base64-encoded Authorization header, then read it at import time. Fallback: (a) import from tdengine_feature_extractor which already has a working auth line, or (b) use patch to fix the auth line after write_file completes. Prefer the file-based approach — it survives future write_file rewrites and secrets sandbox changes. See references/tdengine-sector-table-schemas.md warning #6 for the exact file format.
Confusing beijing_to_utc() input with UTC times. The function name states what the INPUT is: you pass Beijing hours and get UTC. Calling beijing_to_utc("2026-06-17", "01:15:00") is interpreted as 1:15 AM Beijing (= 17:15 UTC previous day), NOT 9:15 AM Beijing. For market hours, always pass Beijing times: beijing_to_utc(date, "09:15:00") and beijing_to_utc(date, "15:30:00"). For sector tables, do NOT use bj2utc at all — sector data uses exact timestamps T16:00:00.000Z, so query with ts = '{date}T16:00:00.000Z'. See references/tdengine-tick-query-patterns.md for the full function specification and references/tdengine-sector-table-schemas.md for sector-specific patterns.
TDengine TAG column pitfall.symbol is a TAG, not a regular column, in TDengine super tables (stock.ticks, stock.stock_daily_status). SELECT symbol FROM stock_daily_status returns empty rows even when SELECT count(*) returns millions. Use SELECT tbname to get sub-table names instead. For stock_daily_status, sub-table names have prefix daily_status_ (e.g. daily_status_000002_sz) — strip prefix to extract symbol. For ticks, sub-table name IS the symbol directly. See ../ashare-bmp-research-factory/references/tdengine-tag-column-pitfall.md for full patterns.
Performance tracker collection cron depends on agent-based job. The cron f7220d53fa23 ("每日 SOP 候选追踪采集") is an agent-based job that expects to compute next-day returns and append to tracker_index.csv using the hypothesis_queue as input. Until this cron fires (first run: 2026-06-29) or a standalone collector script is built, new trading days will not populate the tracker. The historical replay script (historical_sop_replay.py) can serve as a backfill source for tracker data: it already scans top movers and computes forward returns — its output can be post-processed into tracker format.
Interpreting near-zero factor IC as framework failure. When factor_backtest.py returns only 1 factor (trigger_return) with Rank IC ≈ 0, do NOT conclude the factor computation pipeline is broken. The compute_factors() function silently sets failed TDengine queries to 0 — other factors may be genuinely missing (sparse data, timeout) rather than broken. More importantly, an IC of -0.014 correctly states that simple trigger-day return has no predictive power for D1 returns. The framework is working as designed; multi-factor SOP scoring (not raw price momentum) is needed to find predictive signals. See references/factor-backtest-weight-ab-system.md for full architecture.
Spotting dates with missing forward D1 data in TDengine. When scan_raw_triggers returns 0 movers with D1 for certain dates (especially 06-18 and 06-20), those dates have no next-trading-day tick data in TDengine. Do not treat these as "no stocks moved ≥2% that day" — verify with a raw COUNT(*) query on ticks for the next calendar date first. If no ticks exist, mark the date as no_forward_data rather than no_triggers. For the period 06-17→06-26, the forward-data gap is 06-18 (D1=06-19, no ticks) and 06-19/06-20 (weekend, no trading), meaning only 4 trading days have usable forward returns. The historical_sop_replay.py script handles this automatically — it skips dates where compute_forward_returns finds no next-day bar.
Background Python script output not captured by Hermes. Scripts run via terminal(background=true) with 2>&1 may show zero captured output even when they're actively producing stdout. The Hermes process tracker buffers output and may not flush until the process exits. Workaround: write output to a log file with a wrapper script (PYTHONUNBUFFERED=1 python3 -u script.py > /tmp/out.log 2>&1), then use read_file or terminal(cat ...) to read the log file. This also lets you check intermediate progress while the script is still running. See references/tdengine-sector-table-schemas.md warning #7 for this pattern.
westock-data Markdown table parsing (hot stock, LHB, board). Multiple westock-data commands output pipe-delimited Markdown tables (| code | name | zdf | ...), NOT space-separated or Chinese-keyword formatted text. Parsing with line.split() catches the leading | as the first token instead of the stock code, causing empty results. Fix: use parts = [p.strip() for p in line.split("|") if p.strip()], filter out header/separator rows ("code", "name", "---"), and match re.match(r"(sh|sz|bj|hk)\d{4,6}", parts[0]). Same approach for westock-data board (industry/concept) and westock-data lhb (龙虎榜 — columns: code, name, TotalBuy, TotalSell, NetBuy, Reason). Do NOT use Chinese keyword matching ("净买额", "机构席位") — those don't appear in the pipe-delimited output.
QUEUE_DIR path mismatch between snapshot writer and downstream consumers.theme_hypothesis.py writes hypothesis_queue to data/snapshots/ (default --output data/snapshots/), but tdengine_feature_daily.py and performance_tracker.py were hardcoded to read from /tmp/sop_daily/. When the snapshot pipeline runs with the default output, downstream jobs find no queue file and either skip or fail. All three scripts now use data/snapshots/ as the canonical QUEUE_DIR. When adding new pipeline consumers, verify they read from the same path the snapshot writer is configured to use.
LLM-based SOP pipeline cannot compute numeric five-factor scores. The SOP cron job describes the 5-factor scorecard framework but the LLM has no access to real-time KDJ/WR/BOLL indicators, ATR regimes, fund flows, or 250D position data. All candidates default to candidate_role: unclassified with total_score = 0, which update_minute_target_pool.py filters out — producing a permanently empty core pool. The fix is NOT to modify the LLM prompt: use the data-driven factor_scorer.py script (~/.hermes/scripts/factor_scorer.py, v1.1) which batch-computes all five 0/1 scores from actual data sources:
K-line technical indicators: sop_enrich.py now fetches 120-day K-line from remote TDengine (stock.kline) via td_kline_fetcher.py instead of westock-data kline. Computes KDJ(9,3,3), WR(6), WR(10), BOLL(20,2), ATR(14) locally.
Fund flow: westock-data asfund (still the source for MainNetFlow/MainNetFlow5D — not in TDengine).
龙虎榜: Batch query via westock-data lhb <codes> (comma-separated)⚠️ 逗号多代码只返回第一个命中 — westock-data lhb 不支持真正的批量。v1.2 fix: batch_fetch_lhb() 用 ThreadPoolExecutor(max_workers=8) 逐只并发查询 fetch_lhb(code), 30只约 10-15s。 | v1.2: Seat-level detail via single TDX MCP batch query fetch_lhb_seats_batch_tdx(date_str, codes) wrapped in try/except for graceful degradation.
北向资金: Aggregate northbound via eastmoney push2 API (fetch_northbound_context() — v1.1 fixed critical bug: was missing return result, always returned None). Individual stock northbound volume via TDX MCP (陆股通成交额 > 5亿 threshold, not wired into main pipeline due to per-stock latency).
250D position: td_kline_fetcher.batch_fetch_kline_250d() queries remote TD for 365-day K-line, then computes (close - low_250) / (high_250 - low_250).
When upgrading the pipeline, do not remove the qualitative SOP output — it provides the theme-stage and board-width context that factor_scorer reads. See references/factor-scorer.md for full v1.2 calibration thresholds, TDX MCP integration notes (including Latin-1 encoding fix and batch query patterns), the Cron job (b2988fb281ad at 15:10 Beijing time), and the integration chain.
Eastmoney push2 northbound API now returns plain JSON (not JSONP).fetch_northbound_context() queries push2.eastmoney.com/api/qt/kamt.kline/get for 沪深股通 daily flow. The response used to be wrapped in cb(...) JSONP format, but now returns plain JSON directly. The parser must handle both: try JSONP regex first (re.search(r'\\((.*)\\)', body, re.DOTALL)), fall back to json.loads(body) for plain JSON. Also: the kline field encoding varies by fields2 parameter — for fields2=f51,f52,f53,f54,f55,f56 with klt=101, the format is date,val1,val2,val3,val4,val5,val6. Use the last field as net (works across different fields2 configurations). Northbound total = sh2hk + sz2hk (沪股通 + 深股通), NOT hk2sh + hk2sz (those are southbound). ⚠️ v1.1 fixed critical bug: the function was missing return result before the except block — always returned None. return result must appear INSIDE the try block after building the result dict. Verified fix: confirmed 2026-07-03 returned 840亿 net_inflow (was always None before fix).
TDX MCP (通达信问小达) SSL/proxy connectivity and Python import. The TDX MCP server at https://mcp.tdx.com.cn:3001/mcp uses SSE/StreamableHTTP and requires careful connection handling:
SSL keep-alive instability: Server may drop SSL between requests. Use requests.post(..., proxies={'https': None}) — fresh TCP + proxy bypass in one step.
Local proxy interference: HTTPS proxy (Clash/V2Ray at 127.0.0.1:7897) breaks SSE. proxies={'https': None} or NO_PROXY=mcp.tdx.com.cn both work.
requests MUST be imported at module level: import requests as _requests at the top of factor_scorer.py, NOT inside _tdx_mcp_query(). When requests is imported inside a function, background processes (ThreadPoolExecutor, subprocess) may fail with ModuleNotFoundError even though the interactive terminal can import it.
Latin-1 → UTF-8 double encoding: TDX returns Chinese fields (seat names, LHB reasons) as Latin-1-wrapped UTF-8. '机构专用' → 'æºæä¸ç¨'. Fix: _fix_garbled_utf8(s): s.encode('latin-1').decode('utf-8') on seat_name and lhb_reason only.
Batch query strategy: One call: "{date_cn}龙虎榜营业部席位买卖金额" → ~1746 rows for ALL LHB stocks. Pair B/S rows by seat_name + rank (columns [13]+[12]) for net. ~3s total vs ~3s/stock.
Graceful degradation: Wrap fetch_lhb_seats_batch_tdx() in try/except so TDX unavailability doesn't crash the pipeline. On failure, lhb_seats_data = {} and F4 scoring proceeds without seat data.
Session lifecycle: Each _tdx_mcp_query() creates a fresh MCP session (initialize → notify → call). If mid-session failure, re-create.
See references/factor-scorer.md for complete v1.2 TDX integration.
Skipping L1 macro gate. The SOP now has a formal L1 layer that can globally block all buy signals. Do not generate buy candidates when L1=系统性风险, regardless of L2/L3 readings. The pre-market cron and intraday monitor must check L1 state first — a strong L2 theme in a crashing market is still not actionable.
Using old 6-stage theme classification. v1.7 consolidates theme stages from 6 (启动/确认/主升/分歧/修复/退潮) to 4 (启动/发酵期 → 高潮/主升期 → 分歧/分化期 → 退潮期). "确认" is absorbed into 启动/发酵期; "修复" is folded into the transition between 分歧/分化期 and 高潮/主升期. Use the 4-stage system in all v1.7+ outputs.
Enabling adaptive buy_score thresholds without backtesting. Backtest shows raising buy_score to ≥3 in 震荡市 (vs normal ≥2) reduces D10 from 0.86% to 0.49% — it filters signals that pay off later. The adaptive threshold idea (stricter in range-bound markets) seems intuitive but backtests negative. Do not deploy without running a full variant comparison first. See references/l1-gate-backtest-findings.md.
Verification Checklist
Current date/time checked before saying "today".
L1: Index MA20/MA60 computed; L1 state determined (系统性风险/震荡市/多头趋势); if 系统性风险, explicitly state "暂停交易" and skip buy candidate generation.
Board heat and index context collected or limitation stated.
Candidates split into main-line anchors, branch/laggard, low-position repair, and risk anchors.
K-line/position/amount features computed or limitation stated.
Fund flow and technical context checked for priority names.
Real涨停池/炸板池/连板梯队 checked for 游资-style conclusions.
龙虎榜 checked where relevant; missing data marked neutrally.
Output includes verification rules, a scorecard, and L1 status header.
L2: Theme stage classified using v1.7 4-stage system (启动/发酵期, 高潮/主升期, 分歧/分化期, 退潮期); quantitative health indicators assessed (龙头承接/中低位扩散/放量回落/补涨失败).
L3: buy_score/sell_score computed for each candidate using the 9 explicit rules; L2 state gate applied (退潮期→清空, 分歧期→buy_score≥3 for core pool).
L3: Cooling mechanism considered (no same-stock same-direction alert within 20 min).
KDJ/WR/BOLL computed via sop_enrich.py for all candidates; values integrated into 打分表.
Market breadth (up/down/flat) read from latest market mood scan in Obsidian and included in market structure section.
Data gap table included in output; all 10 layers should be ✅ per references/sop-known-data-gaps.md; transient failures marked with specific error and fallback date.
Response remains research/monitoring only with no immediate trade execution details.
When querying TDengine ticks, all timestamps converted to UTC (Beijing -8h); queries include both symbol filter and time bounds.
Shared enums imported from schemas/enums.py (not duplicated locally); outputs validated against corresponding JSON Schema.
For data-driven factor scoring, factor_scorer.py v1.2 is the canonical engine — batch TDX MCP LHB seats, northbound with confirmed return result fix, Latin-1 encoding fix in place.