diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0aa5fd6..8fa568a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -139,5 +139,27 @@ total_assets: stored = calculated ✅
### 已知未解决
- ejabberd容器重启46次(exit code 0+无日志,系统负载高峰时被外部信号杀死)
-- holding_strategies表0行(策略评估管道未打通)
-- 部分cron paused(知识萃取/区间维护/市场精选等)
+
+---
+
+## 2026-07-08 — JSON 全面清理
+
+### 修正的活代码路径(唯一真正在读写 JSON 的路径)
+- `price_monitor.py` — 每2分钟读 decisions.json 改为 mo_data.read_decisions()(DB)
+- 其他18处代码注释写"写入 portfolio.json/decisions.json"但代码已实际写 DB,只修了注释
+
+### 健康检查脚本
+- `system_health_check.py`(主+scripts/两个版本)— 数据条目标签从"portfolio.json DB记录"改为"DB持仓记录"
+- 消除 LLM cron 误读标签后 stat JSON 文件大小的问题
+
+### 配置层标记废弃
+- `mo_config.py`(主+scripts/两个版本)— 所有 JSON 路径 property 加 ⚠️ 已废弃 docstring
+
+### 磁盘残留 JSON 文件
+- portfolio.json / decisions.json / watchlist.json → 全部改为废弃 stub
+- 保留空文件防导入报错,内容仅含废弃声明
+
+### 变更总结
+- 核心数据(持仓/自选/策略)的读和写已 100% 走 DB
+- 唯一仍用 JSON 的非核心文件:reports/*.json(报告缓存)、stocks/*.json(个股详情缓存)、market.json(大盘数据缓存)、accuracy_stats.json(统计缓存)
+- 这些是 Dashboard 前端的缓存层,不是权威数据源,暂时保留
diff --git a/__pycache__/strategy_lifecycle.cpython-312.pyc b/__pycache__/strategy_lifecycle.cpython-312.pyc
index aa454765..13d2e118 100644
Binary files a/__pycache__/strategy_lifecycle.cpython-312.pyc and b/__pycache__/strategy_lifecycle.cpython-312.pyc differ
diff --git a/__pycache__/technical_analysis.cpython-312.pyc b/__pycache__/technical_analysis.cpython-312.pyc
index 1b26bd14..8bf492a4 100644
Binary files a/__pycache__/technical_analysis.cpython-312.pyc and b/__pycache__/technical_analysis.cpython-312.pyc differ
diff --git a/analyst-knowledge-log.md b/analyst-knowledge-log.md
index f1a04f9b..09fd276b 100644
--- a/analyst-knowledge-log.md
+++ b/analyst-knowledge-log.md
@@ -213,4 +213,33 @@
**萃取知识:**
1. 周末扫描的第一步(检查未评级新闻)如果为空,必须主动从curl数据中提取Fetched新事件INSERT入库——不能直接输出"无未评级新闻"
2. 背离检测器在非交易日持续运行,divergence_state的level=none+bias=opportunity为本次MEDIUM判定提供了关键支撑——无市场结构风险
-3. 非农数据"弱于预期"是反向逻辑(鸽派利好),采集器关键词无法区分方向性语义,LLM修正时需特别注意误报模式11
\ No newline at end of file
+3. 非农数据"弱于预期"是反向逻辑(鸽派利好),采集器关键词无法区分方向性语义,LLM修正时需特别注意误报模式11
+
+## 2026-07-08 09:59 全面审计修复(JSON违规 + 策略表空 + 潜力股管道中断)
+
+**发现了什么:** Dad质疑三个问题:(1)重点操作只关注持仓 (2)看不到新自选潜力股 (3)系统到底正不正常。全面审计后发现了系统性漏洞:
+
+**问题1 — JSON违规(最严重):**
+6个脚本仍直接读写portfolio.json/decisions.json/watchlist.json,违反Dad 2026-07-06铁律。根因:6月DB迁移后未审计所有引用文件。
+修复:price_monitor.py、price_data_inject.py、market_insight.py、inspect_decisions.py、strategy_summary.py、fix_portfolio_prices.py、pre-flight-check.py 全部改为DB API(mo_data.read_* / mofin_db.write_*)。同步清理4处误导性注释。
+
+**问题2 — holding_strategies表空(策略丢失):**
+19条策略全丢失,read_decisions()返回0条。根因不明(可能是regenerate_all覆盖或手动误操作)。
+修复:从decisions.json重建holding_strategies表(INSERT 19条)。
+
+**问题3 — 候选→自选提拔管道断裂:**
+market_insight.py存在UnboundLocalError(market_path变量未定义),知微洞察生成cron自6/19起报错停摆。另portfolio.json直读违规也在此文件中。
+修复:market_path前置定义+改为DB读持仓。
+
+**问题4 — 开盘race condition误报:**
+intraday_health_check在09:01检查价格新鲜度,price_monitor刚启动未完成首次更新。
+修复:加09:00-09:10 grace period跳过检查。
+
+**问题5 — 健康检查prompt检查JSON文件:**
+「系统健康检查-开盘前」LLM cron prompt指示检查JSON文件是否存在,缺失时自动生成watchlist.json。
+修复:prompt改为从DB检查数据完整性,明确禁止检查或修复JSON文件。
+
+**涉及文件:**
+scripts/price_monitor.py, price_data_inject.py, market_insight.py, inspect_decisions.py, strategy_summary.py, fix_portfolio_prices.py, pre-flight-check.py, intraday_health_check.py
+cron prompt for 系统健康检查-开盘前 (job_id: 37c02f4d7df9)
+analyst-knowledge-log.md (本文件)
\ No newline at end of file
diff --git a/data/candidate_pool.json b/data/candidate_pool.json
index 34eb22ef..b876f38e 100644
--- a/data/candidate_pool.json
+++ b/data/candidate_pool.json
@@ -1,6 +1,6 @@
{
- "last_updated": "2026-07-07 11:09",
- "total_candidates": 27,
+ "last_updated": "2026-07-07 14:07",
+ "total_candidates": 31,
"sectors_analyzed_today": [
"半导体",
"金属新材料",
@@ -1347,6 +1347,142 @@
"drop_reason": null,
"trend_warning": false,
"trend_note": ""
+ },
+ {
+ "code": "002371",
+ "name": "北方华创",
+ "sector": "半导体",
+ "xiaoguo_score": 8.0,
+ "xiaoguo_reason": "平台型半导体设备龙头,覆盖刻蚀/沉积/清洗等全环节,业绩确定性高。板块资金净流入主力承接标的,估值处于历史中低位区间。",
+ "xiaoguo_strategy": {
+ "entry_range": "295-305元",
+ "stop_loss": "282元",
+ "target": "340-355元"
+ },
+ "verified_price": 795.78,
+ "verified_change": -0.97,
+ "added_at": "2026-07-07 12:12",
+ "last_updated": "2026-07-07 13:19",
+ "num_observations": 2,
+ "score_history": [
+ {
+ "date": "2026-07-07 12:12",
+ "score": 8.5
+ },
+ {
+ "date": "2026-07-07 13:19",
+ "score": 8.0
+ }
+ ],
+ "zhiwei_star": null,
+ "zhiwei_reviewed": false,
+ "zhiwei_reviewed_at": null,
+ "promoted": false,
+ "promoted_at": null,
+ "dropped": false,
+ "drop_reason": null,
+ "trend_warning": false,
+ "trend_note": ""
+ },
+ {
+ "code": "688012",
+ "name": "中微公司",
+ "sector": "半导体",
+ "xiaoguo_score": 8.5,
+ "xiaoguo_reason": "刻蚀设备国产替代核心标的,订单饱满,AI/HBM扩产直接受益。技术面回踩关键支撑位获资金承接,机构持仓集中。",
+ "xiaoguo_strategy": {
+ "entry_range": "175-182元",
+ "stop_loss": "168元",
+ "target": "205-215元"
+ },
+ "verified_price": 414.06,
+ "verified_change": -1.41,
+ "added_at": "2026-07-07 12:12",
+ "last_updated": "2026-07-07 13:19",
+ "num_observations": 2,
+ "score_history": [
+ {
+ "date": "2026-07-07 12:12",
+ "score": 8.0
+ },
+ {
+ "date": "2026-07-07 13:19",
+ "score": 8.5
+ }
+ ],
+ "zhiwei_star": null,
+ "zhiwei_reviewed": false,
+ "zhiwei_reviewed_at": null,
+ "promoted": false,
+ "promoted_at": null,
+ "dropped": false,
+ "drop_reason": null,
+ "trend_warning": false,
+ "trend_note": ""
+ },
+ {
+ "code": "688126",
+ "name": "沪硅产业",
+ "sector": "半导体",
+ "xiaoguo_score": 7.5,
+ "xiaoguo_reason": "12英寸大硅片国产替代先锋,与领涨股有研硅同属材料主线,产能利用率提升+产品结构优化驱动业绩拐点,材料端弹性标的。",
+ "xiaoguo_strategy": {
+ "entry_range": "28.5-30.0元",
+ "stop_loss": "26.5元",
+ "target": "35-38元"
+ },
+ "verified_price": 37.16,
+ "verified_change": 7.37,
+ "added_at": "2026-07-07 12:12",
+ "last_updated": "2026-07-07 12:12",
+ "num_observations": 1,
+ "score_history": [
+ {
+ "date": "2026-07-07 12:12",
+ "score": 7.5
+ }
+ ],
+ "zhiwei_star": null,
+ "zhiwei_reviewed": false,
+ "zhiwei_reviewed_at": null,
+ "promoted": false,
+ "promoted_at": null,
+ "dropped": false,
+ "drop_reason": null,
+ "trend_warning": false,
+ "trend_note": ""
+ },
+ {
+ "code": "600584",
+ "name": "长电科技",
+ "sector": "半导体",
+ "xiaoguo_score": 7.5,
+ "xiaoguo_reason": "先进封装(Chiplet/2.5D)核心平台,绑定头部晶圆厂,AI算力芯片封装需求爆发。技术面缩量企稳,具备补涨弹性与业绩拐点预期。",
+ "xiaoguo_strategy": {
+ "entry_range": "34.5-36元",
+ "stop_loss": "32.5元",
+ "target": "42-45元"
+ },
+ "verified_price": 99.88,
+ "verified_change": 5.04,
+ "added_at": "2026-07-07 13:19",
+ "last_updated": "2026-07-07 13:19",
+ "num_observations": 1,
+ "score_history": [
+ {
+ "date": "2026-07-07 13:19",
+ "score": 7.5
+ }
+ ],
+ "zhiwei_star": null,
+ "zhiwei_reviewed": false,
+ "zhiwei_reviewed_at": null,
+ "promoted": false,
+ "promoted_at": null,
+ "dropped": false,
+ "drop_reason": null,
+ "trend_warning": false,
+ "trend_note": ""
}
]
}
\ No newline at end of file
diff --git a/data/decisions.json b/data/decisions.json
index 61c7a8ff..19d04b9b 100644
--- a/data/decisions.json
+++ b/data/decisions.json
@@ -2,32 +2,32 @@
"decisions": [
{
"code": "000700",
- "name": "模塑科技",
+ "name": null,
"version": 1,
- "price": 17.22,
- "cost": 14.83,
+ "price": null,
+ "cost": null,
"shares": 0,
- "stop_loss": 16.7,
+ "stop_loss": 13.74,
"take_profit": 18.6,
- "entry_low": 15.5,
- "entry_high": 18.08,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "自选策略",
- "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损16.7 | 目标0 | 买入区15.5~18.08 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=14.99 MA10=14.9 MA20=15.45 MA60=13.81",
- "stock_category": "中短线",
- "sector_context": "行业汽车零部件大涨+350.0%,可适度积极",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
+ "source": "migrate",
"reason": null,
- "created_at": "2026-07-03 11:16:41",
- "updated_at": "2026-07-06 11:07:37",
+ "created_at": "2026-07-08T11:42:04.322495",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:41",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -35,42 +35,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "自选策略",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:41",
- "type": "自选策略"
+ "timestamp": "2026-07-08T11:42:04.322495",
+ "type": "decision"
},
{
"code": "00700",
- "name": "腾讯",
+ "name": null,
"version": 1,
- "price": 497.35,
- "cost": 443.13,
- "shares": 100,
- "stop_loss": 423.11,
- "take_profit": 471.11,
- "entry_low": 370.63,
- "entry_high": 385.75,
- "currency": "HKD",
- "strategy_type": "holding",
- "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 损363.06 | 盈0 | 买370.63~385.75",
- "timing_signal": "信号不充分",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=624.1 MA10=623.7 MA20=615.5 MA60=559.52",
- "stock_category": "弱势",
- "sector_context": "行业软件开发大涨+21.0%,可适度积极",
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 390.83,
+ "take_profit": 528.77,
+ "entry_low": null,
+ "entry_high": null,
+ "currency": "CNY",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:16:48",
- "updated_at": "2026-07-03 11:16:48",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度20%→10%",
+ "created_at": "2026-07-08T11:42:04.322435",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:48",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -78,42 +78,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:48",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322435",
+ "type": "decision"
},
{
"code": "00981",
- "name": "中芯国际",
+ "name": null,
"version": 1,
- "price": 89.5,
- "cost": 75.94,
+ "price": null,
+ "cost": null,
"shares": 0,
- "stop_loss": 77.55,
- "take_profit": 86.36,
- "entry_low": 62.39,
- "entry_high": 72.79,
- "currency": "HKD",
- "strategy_type": "自选策略",
- "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损67.24 | 目标0 | 买入区62.39~72.79 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=84.12 MA10=81.44 MA20=78.5 MA60=71.93",
- "stock_category": "中短线",
- "sector_context": "半导体",
+ "stop_loss": 67.0,
+ "take_profit": 88.0,
+ "entry_low": null,
+ "entry_high": null,
+ "currency": "CNY",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
+ "source": "migrate",
"reason": null,
- "created_at": "2026-07-03 11:16:48",
- "updated_at": "2026-07-06 11:01:50",
+ "created_at": "2026-07-08T11:42:04.322447",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:48",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -121,42 +121,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "自选策略",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:48",
- "type": "自选策略"
+ "timestamp": "2026-07-08T11:42:04.322447",
+ "type": "decision"
},
{
"code": "01088",
- "name": "中国神华",
+ "name": null,
"version": 1,
- "price": 46.14,
- "cost": 45.89,
- "shares": 500,
- "stop_loss": 38.84,
- "take_profit": 43.24,
- "entry_low": 31.24,
- "entry_high": 36.45,
- "currency": "HKD",
- "strategy_type": "holding",
- "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损33.67 | 目标0 | 买入区31.24~36.45 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=39.82 MA10=40.55 MA20=41.11 MA60=43.93",
- "stock_category": "弱势",
- "sector_context": "行业煤炭大涨+75.0%,可适度积极",
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 42.0,
+ "take_profit": 52.0,
+ "entry_low": null,
+ "entry_high": null,
+ "currency": "CNY",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
+ "source": "migrate",
"reason": null,
- "created_at": "2026-07-03 11:16:48",
- "updated_at": "2026-07-03 11:16:48",
+ "created_at": "2026-07-08T11:42:04.322519",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:48",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -164,42 +164,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:48",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322519",
+ "type": "decision"
},
{
"code": "01211",
- "name": "比亚迪股份",
+ "name": null,
"version": 1,
- "price": 97.0,
- "cost": 104.87,
- "shares": 600,
- "stop_loss": 74.15,
- "take_profit": 88.88,
- "entry_low": 64.22,
- "entry_high": 74.92,
- "currency": "HKD",
- "strategy_type": "holding",
- "action": "深套持有 | 深套持有 | 止损64.29 | 目标0 | 买入区64.22~74.92 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=96.74 MA10=96.79 MA20=98.23 MA60=98.39",
- "stock_category": "深套",
- "sector_context": "汽车整车",
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 77.92,
+ "take_profit": 104.49,
+ "entry_low": null,
+ "entry_high": null,
+ "currency": "CNY",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:16:48",
- "updated_at": "2026-07-03 11:16:48",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度16%→10%",
+ "created_at": "2026-07-08T11:42:04.322411",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:48",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -207,42 +207,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:48",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322411",
+ "type": "decision"
},
{
"code": "01478",
- "name": "丘钛科技",
+ "name": null,
"version": 1,
- "price": 6.73,
- "cost": 13.47,
- "shares": 11000,
- "stop_loss": 6.33,
- "take_profit": 0.0,
- "entry_low": 6.06,
- "entry_high": 7.07,
- "currency": "HKD",
- "strategy_type": "holding",
- "action": "深套持有 | 深套持有 | 止损6.33 | 目标0 | 买入区6.06~7.07 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=9.18 MA10=9.4 MA20=9.53 MA60=8.85",
- "stock_category": "深套",
- "sector_context": "消费电子",
- "status": "updated",
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 7.9,
+ "take_profit": 9.88,
+ "entry_low": null,
+ "entry_high": null,
+ "currency": "CNY",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
+ "status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:16:48",
- "updated_at": "2026-07-03 11:16:48",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度10%→10%",
+ "created_at": "2026-07-08T11:42:04.322314",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:48",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -250,42 +250,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:48",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322314",
+ "type": "decision"
},
{
"code": "01888",
- "name": "建滔积层板",
+ "name": null,
"version": 1,
- "price": 73.3,
- "cost": 88.24,
+ "price": null,
+ "cost": null,
"shares": 0,
- "stop_loss": 73.98,
- "take_profit": 93.1,
- "entry_low": 67.27,
- "entry_high": 78.48,
- "currency": "HKD",
- "strategy_type": "自选策略",
- "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损64.14 | 目标0 | 买入区67.27~78.48 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=96.4 MA10=93.52 MA20=78.64 MA60=51.2",
- "stock_category": "中短线",
- "sector_context": "行业元件大跌-4.0%,收紧止损",
+ "stop_loss": 53.55,
+ "take_profit": 72.45,
+ "entry_low": null,
+ "entry_high": null,
+ "currency": "CNY",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:16:49",
- "updated_at": "2026-07-06 11:07:37",
+ "source": "migrate",
+ "reason": "6/9盘后自动调整:价格偏离区间,止损=现价×0.85,止盈=现价×1.15",
+ "created_at": "2026-07-08T11:42:04.322373",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:49",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -293,42 +293,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "自选策略",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:49",
- "type": "自选策略"
+ "timestamp": "2026-07-08T11:42:04.322373",
+ "type": "decision"
},
{
"code": "02202",
- "name": "万科企业",
+ "name": null,
"version": 1,
- "price": 2.7,
- "cost": 4.67,
- "shares": 19700,
- "stop_loss": 2.19,
- "take_profit": 2.5,
- "entry_low": 1.81,
- "entry_high": 2.11,
- "currency": "HKD",
- "strategy_type": "holding",
- "action": "深套持有 | 深套持有 | 止损1.9 | 目标0 | 买入区1.81~2.11 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=3.41 MA10=3.42 MA20=3.53 MA60=3.51",
- "stock_category": "深套",
- "sector_context": "行业房地产开发大涨+71.0%,可适度积极",
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 2.34,
+ "take_profit": 2.92,
+ "entry_low": null,
+ "entry_high": null,
+ "currency": "CNY",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:16:49",
- "updated_at": "2026-07-03 11:16:49",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度10%→10%",
+ "created_at": "2026-07-08T11:42:04.322423",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:49",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -336,42 +336,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:49",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322423",
+ "type": "decision"
},
{
"code": "06869",
- "name": "长飞光纤光缆",
+ "name": null,
"version": 1,
- "price": 232.06,
- "cost": 263.73,
+ "price": null,
+ "cost": null,
"shares": 0,
- "stop_loss": 195.46,
- "take_profit": 223.13,
- "entry_low": 161.21,
- "entry_high": 188.08,
- "currency": "HKD",
- "strategy_type": "自选策略",
- "action": "深套持有 | 深套持有 | 止损169.46 | 目标0 | 买入区161.21~188.08 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=246.26 MA10=248.81 MA20=244.56 MA60=231.58",
- "stock_category": "深套",
- "sector_context": "行业通信设备大涨+100.0%,可适度积极",
+ "stop_loss": 209.1,
+ "take_profit": 282.9,
+ "entry_low": null,
+ "entry_high": null,
+ "currency": "CNY",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:16:49",
- "updated_at": "2026-07-06 11:01:50",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度20%→10%",
+ "created_at": "2026-07-08T11:42:04.322285",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:49",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -379,42 +379,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "自选策略",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:49",
- "type": "自选策略"
+ "timestamp": "2026-07-08T11:42:04.322285",
+ "type": "decision"
},
{
"code": "300035",
- "name": "中科电气",
+ "name": null,
"version": 1,
- "price": 13.98,
- "cost": 22.29,
- "shares": 1400,
- "stop_loss": 13.53,
- "take_profit": 15.45,
- "entry_low": 12.58,
- "entry_high": 14.68,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 15.47,
+ "take_profit": 20.63,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "holding",
- "action": "深套持有 | 深套持有 | 止损13.53 | 目标0 | 买入区12.58~14.68 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=22.11 MA10=22.11 MA20=22.1 MA60=21.44",
- "stock_category": "深套",
- "sector_context": "行业能源金属大涨+3.0%,可适度积极",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
+ "source": "migrate",
"reason": null,
- "created_at": "2026-07-03 11:16:50",
- "updated_at": "2026-07-03 11:16:50",
+ "created_at": "2026-07-08T11:42:04.322484",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:50",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -422,42 +422,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:50",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322484",
+ "type": "decision"
},
{
"code": "300308",
- "name": "中际旭创",
+ "name": null,
"version": 1,
- "price": 1114.27,
- "cost": 1316.53,
- "shares": 100,
- "stop_loss": 1090.4,
- "take_profit": 1262.47,
- "entry_low": 1052.06,
- "entry_high": 1227.4,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": null,
+ "take_profit": null,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "持仓策略",
- "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损1090.4 | 目标0 | 买入区1052.06~1227.4 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=1139.01 MA10=1207.45 MA20=1225.2 MA60=1056.92",
- "stock_category": "中短线",
- "sector_context": "中际旭创所属行业(待补充)",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
+ "source": "migrate",
"reason": null,
- "created_at": "2026-07-03 11:16:55",
- "updated_at": "2026-07-03 11:16:55",
+ "created_at": "2026-07-08T11:42:04.322146",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:55",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -465,42 +465,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "持仓策略",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:55",
- "type": "持仓策略"
+ "timestamp": "2026-07-08T11:42:04.322146",
+ "type": "decision"
},
{
"code": "300548",
- "name": "长芯博创",
+ "name": null,
"version": 1,
- "price": 228.23,
- "cost": 231.46,
+ "price": null,
+ "cost": null,
"shares": 0,
- "stop_loss": 221.38,
- "take_profit": 246.49,
- "entry_low": 205.41,
- "entry_high": 239.64,
+ "stop_loss": 197.92,
+ "take_profit": 267.78,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "自选策略",
- "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损221.38 | 目标0 | 买入区205.41~239.64 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=253.97 MA10=270.01 MA20=255.06 MA60=248.13",
- "stock_category": "弱势",
- "sector_context": "行业通信设备大涨+100.0%,可适度积极",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:16:56",
- "updated_at": "2026-07-06 11:01:50",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度20%→10%",
+ "created_at": "2026-07-08T11:42:04.322459",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:56",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -508,42 +508,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "自选策略",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:56",
- "type": "自选策略"
+ "timestamp": "2026-07-08T11:42:04.322459",
+ "type": "decision"
},
{
"code": "300750",
- "name": "宁德时代",
+ "name": null,
"version": 1,
- "price": 384.06,
- "cost": 401.78,
- "shares": 100,
- "stop_loss": 358.25,
- "take_profit": 414.78,
- "entry_low": 345.65,
- "entry_high": 403.26,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 331.66,
+ "take_profit": 448.72,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "holding",
- "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损358.25 | 目标0 | 买入区345.65~403.26 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=386.51 MA10=392.29 MA20=394.64 MA60=413.88",
- "stock_category": "弱势",
- "sector_context": "电池",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:16:56",
- "updated_at": "2026-07-03 11:16:56",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度20%→10%",
+ "created_at": "2026-07-08T11:42:04.322399",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:16:56",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -551,42 +551,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:16:56",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322399",
+ "type": "decision"
},
{
"code": "518880",
- "name": "黄金ETF华安",
+ "name": null,
"version": 1,
- "price": 8.61,
- "cost": 12.19,
- "shares": 2400,
- "stop_loss": 8.21,
- "take_profit": 9.38,
- "entry_low": 7.82,
- "entry_high": 9.04,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 7.66,
+ "take_profit": 10.36,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "holding",
- "action": "深套持有 | 深套持有 | 止损8.21 | 目标0 | 买入区7.75~9.04 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=9.54 MA10=9.69 MA20=10.22 MA60=10.35",
- "stock_category": "深套",
- "sector_context": "行业贵金属大涨+723.0%,可适度积极",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:17:01",
- "updated_at": "2026-07-03 11:17:01",
+ "source": "migrate",
+ "reason": "现价9.01(+0.64%),自动生成策略",
+ "created_at": "2026-07-08T11:42:04.322472",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:17:01",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -594,42 +594,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:17:01",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322472",
+ "type": "decision"
},
{
"code": "600563",
- "name": "法拉电子",
+ "name": null,
"version": 1,
- "price": 162.41,
- "cost": 147.18,
- "shares": 100,
- "stop_loss": 144.94,
- "take_profit": 175.4,
- "entry_low": 146.17,
- "entry_high": 170.53,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 139.75,
+ "take_profit": 187.4,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "holding",
- "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损144.94 | 目标0 | 买入区146.17~170.53 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=183.16 MA10=178.82 MA20=169.12 MA60=145.24",
- "stock_category": "中短线",
- "sector_context": "行业元件大跌-4.0%,收紧止损",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:17:08",
- "updated_at": "2026-07-03 11:17:08",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度16%→10%",
+ "created_at": "2026-07-08T11:42:04.322507",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:17:08",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -637,42 +637,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:17:08",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322507",
+ "type": "decision"
},
{
"code": "601899",
- "name": "紫金矿业",
+ "name": null,
"version": 1,
- "price": 27.76,
- "cost": 39.89,
- "shares": 2400,
- "stop_loss": 25.26,
- "take_profit": 29.98,
- "entry_low": 24.98,
- "entry_high": 29.15,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 25.13,
+ "take_profit": 33.5,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "holding",
- "action": "深套持有 | 深套持有 | 止损25.26 | 目标0 | 买入区24.98~29.15 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=36.27 MA10=36.92 MA20=37.96 MA60=36.47",
- "stock_category": "深套",
- "sector_context": "工业金属",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:17:14",
- "updated_at": "2026-07-03 11:17:14",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度14%→10%",
+ "created_at": "2026-07-08T11:42:04.322332",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:17:14",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -680,146 +680,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:17:14",
- "type": "holding",
- "last_reassessed_price": 27.76,
- "strategy_tree": {
- "branches": [
- {
- "id": "601899_stop_loss",
- "condition": {
- "price": "<25.26"
- },
- "action": {
- "type": "sell",
- "amount": "all",
- "reason": "止损"
- },
- "priority": 0,
- "rationale": "止损保护本金",
- "trigger_count": 0,
- "success_rate": null,
- "last_triggered": null
- },
- {
- "id": "601899_buy_dip",
- "condition": {
- "scenario": "weak_consolidation",
- "price": "<=29.15",
- "price_lower": ">=24.98"
- },
- "action": {
- "type": "buy",
- "amount": "normal",
- "limit": 24.98,
- "reason": "回调支撑买入"
- },
- "priority": 1,
- "rationale": "价格回调到支撑区,弱势市场低吸",
- "trigger_count": 0,
- "success_rate": null,
- "last_triggered": null
- },
- {
- "id": "601899_breakout_chase",
- "condition": {
- "scenario": "bullish_recovery",
- "price": ">=29.98"
- },
- "action": {
- "type": "buy",
- "amount": "normal",
- "limit": "market",
- "reason": "突破确认追涨"
- },
- "priority": 2,
- "rationale": "价格突破阻力,确认上升趋势后买入",
- "trigger_count": 0,
- "success_rate": null,
- "last_triggered": null
- },
- {
- "id": "601899_trim",
- "condition": {
- "scenario": "sharp_decline",
- "loss_pct": "<-15%"
- },
- "action": {
- "type": "sell",
- "amount": "half",
- "reason": "急跌降风险"
- },
- "priority": 3,
- "rationale": "急跌市场,深套股减半仓减少敞口",
- "trigger_count": 0,
- "success_rate": null,
- "last_triggered": null
- },
- {
- "id": "601899_take_profit",
- "condition": {
- "price": ">=29.98"
- },
- "action": {
- "type": "sell",
- "amount": "half",
- "reason": "止盈锁利"
- },
- "priority": 4,
- "rationale": "达到目标价,减半仓锁定利润",
- "trigger_count": 0,
- "success_rate": null,
- "last_triggered": null
- },
- {
- "id": "601899_hold",
- "condition": {},
- "action": {
- "type": "hold",
- "reason": "无明确信号,继续持有"
- },
- "priority": 99,
- "rationale": "没有分支匹配时的默认动作",
- "trigger_count": 0,
- "success_rate": null,
- "last_triggered": null
- }
- ]
- }
+ "timestamp": "2026-07-08T11:42:04.322332",
+ "type": "decision"
},
{
"code": "688411",
- "name": "海博思创",
+ "name": null,
"version": 1,
- "price": 265.26,
- "cost": 266.95,
- "shares": 200,
- "stop_loss": 233.43,
- "take_profit": 277.58,
- "entry_low": 238.73,
- "entry_high": 278.52,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 235.0,
+ "take_profit": 300.0,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "holding",
- "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损233.43 | 目标0 | 买入区238.73~278.52 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=260.16 MA10=267.89 MA20=265.99 MA60=256.91",
- "stock_category": "中短线",
- "sector_context": "行业光伏设备大跌-103.0%,收紧止损",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
+ "source": "migrate",
"reason": null,
- "created_at": "2026-07-03 11:17:22",
- "updated_at": "2026-07-03 11:17:22",
+ "created_at": "2026-07-08T11:42:04.322347",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:17:22",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -827,42 +723,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:17:22",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322347",
+ "type": "decision"
},
{
"code": "688639",
- "name": "华恒生物",
+ "name": null,
"version": 1,
- "price": 16.61,
- "cost": 21.51,
- "shares": 2800,
- "stop_loss": 14.12,
- "take_profit": 17.95,
- "entry_low": 14.96,
- "entry_high": 17.44,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 18.36,
+ "take_profit": 24.48,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "holding",
- "action": "深套持有 | 深套持有 | 止损14.12 | 目标0 | 买入区14.95~17.44 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=35.03 MA10=36.72 MA20=37.12 MA60=34.13",
- "stock_category": "深套",
- "sector_context": "行业化学制品大跌-145.0%,收紧止损",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
- "reason": null,
- "created_at": "2026-07-03 11:17:30",
- "updated_at": "2026-07-03 11:17:30",
+ "source": "migrate",
+ "reason": "6/9知微收紧:宽度14%→10%",
+ "created_at": "2026-07-08T11:42:04.322387",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:17:30",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -870,42 +766,42 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:17:30",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322387",
+ "type": "decision"
},
{
"code": "688981",
- "name": "中芯国际",
+ "name": null,
"version": 1,
- "price": 147.96,
- "cost": 126.07,
- "shares": 300,
- "stop_loss": 140.94,
- "take_profit": 156.92,
- "entry_low": 133.16,
- "entry_high": 155.36,
+ "price": null,
+ "cost": null,
+ "shares": 0,
+ "stop_loss": 112.0,
+ "take_profit": 145.0,
+ "entry_low": null,
+ "entry_high": null,
"currency": "CNY",
- "strategy_type": "holding",
- "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损140.94 | 目标0 | 买入区133.16~155.36 | 信号:持有",
- "timing_signal": "持有",
- "rr_ratio": 0.0,
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=146.09 MA10=149.74 MA20=141.28 MA60=127.88",
- "stock_category": "中短线",
- "sector_context": "半导体",
+ "strategy_type": "decision",
+ "action": null,
+ "timing_signal": null,
+ "rr_ratio": null,
+ "tech_snapshot": null,
+ "stock_category": null,
+ "sector_context": null,
"status": "active",
"trigger_json": null,
"changelog_json": null,
- "source": "auto",
+ "source": "migrate",
"reason": null,
- "created_at": "2026-07-03 11:17:37",
- "updated_at": "2026-07-03 11:17:37",
+ "created_at": "2026-07-08T11:42:04.322360",
+ "updated_at": null,
"avg_price": null,
- "decision_timestamp": "2026-07-03 11:17:37",
+ "decision_timestamp": null,
"note": null,
"quality_check": null,
"quality_checked_at": null,
@@ -913,15 +809,15 @@
"position_advice": null,
"signal_factors_json": null,
"time_horizon": null,
- "decision_type": "holding",
+ "decision_type": null,
"trigger": {},
"changelog": [],
"quality_issues": {},
"signal_factors": [],
- "timestamp": "2026-07-03 11:17:37",
- "type": "holding"
+ "timestamp": "2026-07-08T11:42:04.322360",
+ "type": "decision"
}
],
"total": 19,
- "regenerated_at": "2026-07-07 10:28"
+ "regenerated_at": "2026-07-08 11:42"
}
\ No newline at end of file
diff --git a/data/mofin_health.html b/data/mofin_health.html
new file mode 100644
index 00000000..4d32161b
--- /dev/null
+++ b/data/mofin_health.html
@@ -0,0 +1,130 @@
+
+
+
MoFin 健康监控
+
+
+📊 MoFin 系统健康监控
+加载中...
+
+
🌳 功能树
+
🗃️ 数据实体
+
🔧 流程/Cron
+
+
+
+
+
+
+
+
diff --git a/data/portfolio.json b/data/portfolio.json
index 46620ea4..36ef96bb 100644
--- a/data/portfolio.json
+++ b/data/portfolio.json
@@ -1,224 +1 @@
-{
- "holdings": [
- {
- "code": "00700",
- "name": "腾讯",
- "shares": 100,
- "cost": 443.13,
- "position_pct": null,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 447.4,
- "market_value": 39063.0,
- "change_pct": 3.76,
- "currency": "HKD"
- },
- {
- "code": "01088",
- "name": "中国神华",
- "shares": 500,
- "cost": 45.89,
- "position_pct": 2.14,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 40.66,
- "market_value": 17575.0,
- "change_pct": 1.65,
- "currency": "HKD"
- },
- {
- "code": "01211",
- "name": "比亚迪股份",
- "shares": 600,
- "cost": 104.87,
- "position_pct": 4.62,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 84.15,
- "market_value": 44376.0,
- "change_pct": 0.06,
- "currency": "HKD"
- },
- {
- "code": "01478",
- "name": "丘钛科技",
- "shares": 11000,
- "cost": 13.47,
- "position_pct": 7.97,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 6.74,
- "market_value": 64460.0,
- "change_pct": -3.58,
- "currency": "HKD"
- },
- {
- "code": "02202",
- "name": "万科企业",
- "shares": 19700,
- "cost": 4.67,
- "position_pct": 4.6,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 2.32,
- "market_value": 40385.0,
- "change_pct": -0.85,
- "currency": "HKD"
- },
- {
- "code": "300035",
- "name": "中科电气",
- "shares": 1400,
- "cost": 22.29,
- "position_pct": 2.42,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 14.03,
- "market_value": 19712.0,
- "change_pct": -1.82,
- "currency": "CNY"
- },
- {
- "code": "300308",
- "name": "中际旭创",
- "shares": 100,
- "cost": 1316.53,
- "position_pct": 15.27,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 1117.49,
- "market_value": 106181.0,
- "change_pct": 0.13,
- "currency": "CNY"
- },
- {
- "code": "300750",
- "name": "宁德时代",
- "shares": 100,
- "cost": 401.78,
- "position_pct": 4.64,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 378.07,
- "market_value": 37726.0,
- "change_pct": -0.51,
- "currency": "CNY"
- },
- {
- "code": "518880",
- "name": "黄金ETF华安",
- "shares": 2400,
- "cost": 12.19,
- "position_pct": 2.45,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 8.63,
- "market_value": 20832.0,
- "change_pct": -0.45,
- "currency": "CNY"
- },
- {
- "code": "600563",
- "name": "法拉电子",
- "shares": 100,
- "cost": 147.18,
- "position_pct": 2.3,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 154.64,
- "market_value": 15021.0,
- "change_pct": -1.54,
- "currency": "CNY"
- },
- {
- "code": "601899",
- "name": "紫金矿业",
- "shares": 2400,
- "cost": 39.89,
- "position_pct": 7.34,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 28.42,
- "market_value": 68784.0,
- "change_pct": 2.16,
- "currency": "CNY"
- },
- {
- "code": "688411",
- "name": "海博思创",
- "shares": 200,
- "cost": 266.95,
- "position_pct": 6.31,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 263.0,
- "market_value": 52064.0,
- "change_pct": 4.72,
- "currency": "CNY"
- },
- {
- "code": "688639",
- "name": "华恒生物",
- "shares": 2800,
- "cost": 21.51,
- "position_pct": 5.25,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 16.57,
- "market_value": 46480.0,
- "change_pct": -0.54,
- "currency": "CNY"
- },
- {
- "code": "688981",
- "name": "中芯国际",
- "shares": 300,
- "cost": 126.07,
- "position_pct": 5.44,
- "added_at": null,
- "is_active": 1,
- "closed_at": null,
- "close_pnl": null,
- "price": 147.6,
- "market_value": 40599.0,
- "change_pct": 5.2,
- "currency": "CNY"
- }
- ],
- "cash": 321271.0,
- "frozen_cash": 0.0,
- "total_mv": 620482.46,
- "total_assets": 941753.46,
- "stock_value": 620482.46,
- "position_pct": 65.89,
- "currency": "CNY",
- "updated_at": "2026-07-06 13:06:24",
- "total_pnl": null,
- "cash_history": []
-}
\ No newline at end of file
+{"__comment": "此文件已废弃,所有数据从 mofin.db holdings + portfolio_summary 表读取。保留空文件防止导入报错,后续可安全删除。", "holdings": [], "cash": 0, "total_assets": 0, "updated_at": ""}
diff --git a/data/price_history.json b/data/price_history.json
index f231da88..08d9d06f 100644
--- a/data/price_history.json
+++ b/data/price_history.json
@@ -5,6 +5,12 @@
"high": 90.1,
"low": 79.85,
"close": 86.2
+ },
+ {
+ "date": "2026-07-07",
+ "high": 74.75,
+ "low": 67.1,
+ "close": 68.85
}
],
"01478": [
@@ -38,7 +44,13 @@
"date": "2026-07-07",
"high": 1215.0,
"low": 1180.0,
- "close": 1206.91
+ "close": 1188.8
+ },
+ {
+ "date": "2026-07-08",
+ "high": 1202.0,
+ "low": 1177.0,
+ "close": 1187.99
}
],
"02202": [
@@ -61,6 +73,18 @@
"high": 53.0,
"low": 51.7,
"close": 52.45
+ },
+ {
+ "date": "2026-07-07",
+ "high": 53.05,
+ "low": 52.0,
+ "close": 52.25
+ },
+ {
+ "date": "2026-07-08",
+ "high": 54.2,
+ "low": 52.0,
+ "close": 53.95
}
],
"601318": [
@@ -84,9 +108,15 @@
},
{
"date": "2026-07-07",
- "high": 50.2,
+ "high": 50.22,
"low": 48.81,
- "close": 50.1
+ "close": 49.29
+ },
+ {
+ "date": "2026-07-08",
+ "high": 50.22,
+ "low": 48.96,
+ "close": 49.5
}
],
"02359": [
@@ -101,6 +131,18 @@
"high": 158.7,
"low": 151.1,
"close": 152.5
+ },
+ {
+ "date": "2026-07-07",
+ "high": 155.5,
+ "low": 149.2,
+ "close": 150.3
+ },
+ {
+ "date": "2026-07-08",
+ "high": 155.5,
+ "low": 144.8,
+ "close": 145.2
}
],
"02388": [
@@ -115,6 +157,18 @@
"high": 42.98,
"low": 41.94,
"close": 42.68
+ },
+ {
+ "date": "2026-07-07",
+ "high": 43.28,
+ "low": 42.62,
+ "close": 43.1
+ },
+ {
+ "date": "2026-07-08",
+ "high": 45.3,
+ "low": 42.62,
+ "close": 45.2
}
],
"02628": [
@@ -129,6 +183,18 @@
"high": 29.08,
"low": 27.5,
"close": 28.64
+ },
+ {
+ "date": "2026-07-07",
+ "high": 29.22,
+ "low": 28.04,
+ "close": 28.28
+ },
+ {
+ "date": "2026-07-08",
+ "high": 29.22,
+ "low": 28.04,
+ "close": 28.76
}
],
"688630": [
@@ -154,7 +220,13 @@
"date": "2026-07-07",
"high": 499.0,
"low": 448.0,
- "close": 466.16
+ "close": 462.23
+ },
+ {
+ "date": "2026-07-08",
+ "high": 494.81,
+ "low": 451.06,
+ "close": 493.0
}
],
"06160": [
@@ -169,6 +241,18 @@
"high": 183.9,
"low": 171.9,
"close": 182.9
+ },
+ {
+ "date": "2026-07-07",
+ "high": 190.0,
+ "low": 183.0,
+ "close": 183.8
+ },
+ {
+ "date": "2026-07-08",
+ "high": 190.0,
+ "low": 181.9,
+ "close": 182.6
}
],
"06869": [
@@ -177,6 +261,12 @@
"high": 229.2,
"low": 207.4,
"close": 208.6
+ },
+ {
+ "date": "2026-07-07",
+ "high": 170.2,
+ "low": 151.9,
+ "close": 156.5
}
],
"688795": [
@@ -200,9 +290,15 @@
},
{
"date": "2026-07-07",
- "high": 649.88,
+ "high": 699.0,
"low": 615.0,
- "close": 635.51
+ "close": 663.2
+ },
+ {
+ "date": "2026-07-08",
+ "high": 699.0,
+ "low": 629.0,
+ "close": 675.99
}
],
"09868": [
@@ -217,6 +313,18 @@
"high": 53.7,
"low": 49.3,
"close": 50.9
+ },
+ {
+ "date": "2026-07-07",
+ "high": 54.05,
+ "low": 52.05,
+ "close": 52.2
+ },
+ {
+ "date": "2026-07-08",
+ "high": 54.05,
+ "low": 50.95,
+ "close": 52.9
}
],
"09988": [
@@ -231,6 +339,18 @@
"high": 97.4,
"low": 93.55,
"close": 95.15
+ },
+ {
+ "date": "2026-07-07",
+ "high": 100.5,
+ "low": 95.3,
+ "close": 95.55
+ },
+ {
+ "date": "2026-07-08",
+ "high": 105.0,
+ "low": 95.3,
+ "close": 103.8
}
],
"688802": [
@@ -254,9 +374,15 @@
},
{
"date": "2026-07-07",
- "high": 745.0,
+ "high": 876.0,
"low": 690.11,
- "close": 731.0
+ "close": 833.99
+ },
+ {
+ "date": "2026-07-08",
+ "high": 876.0,
+ "low": 723.0,
+ "close": 846.01
}
],
"300124": [
@@ -281,8 +407,14 @@
{
"date": "2026-07-07",
"high": 71.94,
- "low": 68.2,
- "close": 68.33
+ "low": 65.73,
+ "close": 66.05
+ },
+ {
+ "date": "2026-07-08",
+ "high": 69.79,
+ "low": 63.5,
+ "close": 64.44
}
],
"000657": [
@@ -307,8 +439,14 @@
{
"date": "2026-07-07",
"high": 90.58,
- "low": 80.46,
- "close": 80.46
+ "low": 75.41,
+ "close": 76.84
+ },
+ {
+ "date": "2026-07-08",
+ "high": 80.8,
+ "low": 72.98,
+ "close": 77.36
}
],
"000711": [
@@ -333,8 +471,14 @@
{
"date": "2026-07-07",
"high": 5.65,
- "low": 5.0,
- "close": 5.01
+ "low": 4.92,
+ "close": 5.42
+ },
+ {
+ "date": "2026-07-08",
+ "high": 5.59,
+ "low": 4.92,
+ "close": 5.54
}
],
"001309": [
@@ -359,8 +503,14 @@
{
"date": "2026-07-07",
"high": 954.19,
- "low": 860.0,
- "close": 935.0
+ "low": 845.28,
+ "close": 851.0
+ },
+ {
+ "date": "2026-07-08",
+ "high": 900.0,
+ "low": 790.0,
+ "close": 847.5
}
],
"002594": [
@@ -385,8 +535,14 @@
{
"date": "2026-07-07",
"high": 88.95,
- "low": 86.61,
- "close": 87.54
+ "low": 85.87,
+ "close": 86.26
+ },
+ {
+ "date": "2026-07-08",
+ "high": 88.81,
+ "low": 85.28,
+ "close": 87.25
}
],
"00700": [
@@ -409,6 +565,18 @@
"high": 2.15,
"low": 2.03,
"close": 2.1
+ },
+ {
+ "date": "2026-07-07",
+ "high": 2.1,
+ "low": 2.0,
+ "close": 2.01
+ },
+ {
+ "date": "2026-07-08",
+ "high": 2.1,
+ "low": 1.95,
+ "close": 2.0
}
],
"00981": [
@@ -431,6 +599,18 @@
"high": 13.39,
"low": 12.63,
"close": 13.36
+ },
+ {
+ "date": "2026-07-07",
+ "high": 15.18,
+ "low": 14.23,
+ "close": 14.45
+ },
+ {
+ "date": "2026-07-08",
+ "high": 15.18,
+ "low": 14.23,
+ "close": 14.79
}
],
"01088": [
@@ -465,8 +645,14 @@
{
"date": "2026-07-07",
"high": 710.0,
- "low": 659.16,
- "close": 681.8
+ "low": 619.9,
+ "close": 627.9
+ },
+ {
+ "date": "2026-07-08",
+ "high": 647.78,
+ "low": 584.67,
+ "close": 624.2
}
],
"000700": [
@@ -478,9 +664,9 @@
},
{
"date": "2026-07-07",
- "high": 17.18,
- "low": 16.3,
- "close": 16.75
+ "high": 17.38,
+ "low": 16.04,
+ "close": 16.11
}
],
"300548": [
@@ -493,8 +679,56 @@
{
"date": "2026-07-07",
"high": 225.0,
- "low": 205.0,
- "close": 214.13
+ "low": 204.1,
+ "close": 211.03
+ }
+ ],
+ "000850": [
+ {
+ "date": "2026-07-07",
+ "high": 3.95,
+ "low": 3.75,
+ "close": 3.81
+ },
+ {
+ "date": "2026-07-08",
+ "high": 3.95,
+ "low": 3.72,
+ "close": 3.86,
+ "volume": 60649.0,
+ "amount": 2312.0
+ }
+ ],
+ "603501": [
+ {
+ "date": "2026-07-08",
+ "high": 101.0,
+ "low": 97.01,
+ "close": 100.08
+ }
+ ],
+ "688041": [
+ {
+ "date": "2026-07-08",
+ "high": 359.04,
+ "low": 342.01,
+ "close": 352.07
+ }
+ ],
+ "002371": [
+ {
+ "date": "2026-07-08",
+ "high": 830.02,
+ "low": 768.0,
+ "close": 819.85
+ }
+ ],
+ "000001": [
+ {
+ "date": "2026-07-08",
+ "high": 10.59,
+ "low": 10.34,
+ "close": 10.53
}
]
}
\ No newline at end of file
diff --git a/data/stocks/000700.json b/data/stocks/000700.json
index eadd881f..27bb21f0 100644
--- a/data/stocks/000700.json
+++ b/data/stocks/000700.json
@@ -26,6 +26,16 @@
"content": "- **模塑科技(000700)** 15.54 / 止损13.91 — 昨日+5.28%强势,守住15持有",
"report_id": "cron_99c06255590a_2026-07-02_08-32-48"
},
+ {
+ "time": "2026-07-07T08:55:31.376433",
+ "content": "博创科技(300548) 214.13 在买入区✅ | 模塑科技(000700) 16.75 在买入区✅ | 贵州茅台(600519) 1206.91 在买入区✅ | 比亚迪A(002594) 87.",
+ "report_id": "cron_99c06255590a_2026-07-07_08-43-59"
+ },
+ {
+ "time": "2026-07-08T08:55:38.411281",
+ "content": "**自选靠近买入区:** 模塑科技(000700)16.15在15.5~18.08买入区;建滔积层板(01888)70.0在67.27~78.48买入区;华茂股份(000850)3.81在3.70~3",
+ "report_id": "cron_99c06255590a_2026-07-08_08-39-49"
+ },
{
"time": "2026-06-30T08:55:46.708460",
"content": "- 模塑科技000700 13.86 | 买入区13.33~14.09 **(仍在区内,但已破止损)**",
diff --git a/data/stocks/002594.json b/data/stocks/002594.json
index 45f898fd..d4c9700f 100644
--- a/data/stocks/002594.json
+++ b/data/stocks/002594.json
@@ -31,6 +31,11 @@
"content": "**比亚迪A(002594)** ¥91.6 | 仓位0% | 买入区89.77~92.76 → RR 2.56",
"report_id": "cron_99c06255590a_2026-06-17_08-35-59"
},
+ {
+ "time": "2026-07-07T08:55:31.376433",
+ "content": "博创科技(300548) 214.13 在买入区✅ | 模塑科技(000700) 16.75 在买入区✅ | 贵州茅台(600519) 1206.91 在买入区✅ | 比亚迪A(002594) 87.",
+ "report_id": "cron_99c06255590a_2026-07-07_08-43-59"
+ },
{
"time": "2026-06-01T13:55:12.662353",
"content": "**🔴比亚迪A股(002594) | 93.80 -2.47%** → 已有H股持仓,暂不加仓",
diff --git a/data/stocks/00700.json b/data/stocks/00700.json
index 2598ca9b..11bf7c91 100644
--- a/data/stocks/00700.json
+++ b/data/stocks/00700.json
@@ -146,6 +146,11 @@
"content": "② **腾讯(00700)** 现价430.2 | 仓位待确认 | 买入区421-430上沿",
"report_id": "cron_99c06255590a_2026-07-03_08-33-17"
},
+ {
+ "time": "2026-07-07T08:55:31.376433",
+ "content": "博创科技(300548) 214.13 在买入区✅ | 模塑科技(000700) 16.75 在买入区✅ | 贵州茅台(600519) 1206.91 在买入区✅ | 比亚迪A(002594) 87.",
+ "report_id": "cron_99c06255590a_2026-07-07_08-43-59"
+ },
{
"time": "2026-06-02T11:55:44.093905",
"content": "❌ 反例:腾讯00700,底仓100股(3.59%),浮盈+4.79%。看到\"距止盈470仅0.77%\"就建议触及减仓清仓 —— 机械执行,未考虑底仓属性+浮盈幅度。",
@@ -1021,6 +1026,16 @@
"content": "**港股(恒指-0.63%):** 中芯国际H(00981) 89.40+5.42%浮盈+17%持有 | 腾讯控股(00700) 429.80+2.28%浮亏-3%距止损3.4% | 丘钛科技(014",
"report_id": "cron_d42f2ce3b479_2026-06-30_20-25-27"
},
+ {
+ "time": "2026-07-07T08:55:31.476330",
+ "content": "**③ 00700 腾讯 447.00(+3.66%) | 仓~4.2%→持有**",
+ "report_id": "cron_d42f2ce3b479_2026-07-06_20-24-34"
+ },
+ {
+ "time": "2026-07-08T08:55:38.511510",
+ "content": "**① 腾讯(00700) 现价461.20(+2.04%) | 仓~4.3%→持有**",
+ "report_id": "cron_d42f2ce3b479_2026-07-07_20-20-44"
+ },
{
"time": "2026-06-08T08:55:37.165829",
"content": "| **腾讯控股(00700)** | 453.20 | 追踪止盈459.8已触发 | 关注445支撑,跌破450可接回 |",
diff --git a/data/stocks/00981.json b/data/stocks/00981.json
index eac25761..938e8de7 100644
--- a/data/stocks/00981.json
+++ b/data/stocks/00981.json
@@ -166,6 +166,11 @@
"content": "**港股(恒指-0.63%):** 中芯国际H(00981) 89.40+5.42%浮盈+17%持有 | 腾讯控股(00700) 429.80+2.28%浮亏-3%距止损3.4% | 丘钛科技(014",
"report_id": "cron_d42f2ce3b479_2026-06-30_20-25-27"
},
+ {
+ "time": "2026-07-08T08:55:38.511510",
+ "content": "**自选→距买入区±3%:** 中芯国际H(00981) ¥73.45 买入区上沿72.79 距+0.9%——已触发但07-06已清仓,仅关注不介入。其余自选均远离买入区。",
+ "report_id": "cron_d42f2ce3b479_2026-07-07_20-20-44"
+ },
{
"time": "2026-07-06T08:55:01.761812",
"content": "| **中芯国际双持(688981+00981)** | A+H 双持合计约 9.64% 仓位,两市场同步净多头,若港股中芯继续走弱(注意 00981 止损 ¥77.55 高于成本 ¥75.94 的异",
diff --git a/data/stocks/01478.json b/data/stocks/01478.json
index c9779a77..4f5807a7 100644
--- a/data/stocks/01478.json
+++ b/data/stocks/01478.json
@@ -456,6 +456,11 @@
"content": "③ 丘钛科技(01478) 距止损6.48仅+7.3%!仓位7.97%",
"report_id": "cron_d42f2ce3b479_2026-07-03_20-03-44"
},
+ {
+ "time": "2026-07-07T08:55:31.476330",
+ "content": "**1. 01478 丘钛科技 现6.59HKD(-5.72%) →距止损7.33已破-10%!** 成本13.47亏损51%。今日低开低走无反弹,摄像头模组需求走弱。操作:反弹至6.8-7.0减半仓",
+ "report_id": "cron_d42f2ce3b479_2026-07-06_20-24-34"
+ },
{
"time": "2026-07-06T08:55:01.761812",
"content": "2. **收紧输出质量** — 使用\"关注/观望/留意\"时须紧随具体价格、止损位和触发条件。满 3 次模糊表述自动标记为低质量输出。对 01478 和 688795 做一次明确的持仓策略复核:继续持有",
diff --git a/data/stocks/01888.json b/data/stocks/01888.json
index c73793d3..7917166e 100644
--- a/data/stocks/01888.json
+++ b/data/stocks/01888.json
@@ -66,6 +66,11 @@
"content": "长飞光纤光缆(06869) 255.40+6.68%放量大涨,持有等站上成本263.72;腾讯(00700) 429.80+2.28%,买入区435-440依然可加;中芯H(00981) 89.40+",
"report_id": "cron_99c06255590a_2026-07-01_08-39-42"
},
+ {
+ "time": "2026-07-08T08:55:38.411281",
+ "content": "**自选靠近买入区:** 模塑科技(000700)16.15在15.5~18.08买入区;建滔积层板(01888)70.0在67.27~78.48买入区;华茂股份(000850)3.81在3.70~3",
+ "report_id": "cron_99c06255590a_2026-07-08_08-39-49"
+ },
{
"time": "2026-06-02T14:55:53.464779",
"content": "| 建滔积层板(01888) | 49.48 | +4.30% | 42-44 | 🔴 远超买入区 |",
diff --git a/data/stocks/02202.json b/data/stocks/02202.json
index 1ff52bec..7a32cf32 100644
--- a/data/stocks/02202.json
+++ b/data/stocks/02202.json
@@ -61,6 +61,11 @@
"content": "长飞光纤光缆(06869) 255.40+6.68%放量大涨,持有等站上成本263.72;腾讯(00700) 429.80+2.28%,买入区435-440依然可加;中芯H(00981) 89.40+",
"report_id": "cron_99c06255590a_2026-07-01_08-39-42"
},
+ {
+ "time": "2026-07-08T08:55:38.411281",
+ "content": "**① 万科企业(02202) ⚠️ 触及止损边缘**",
+ "report_id": "cron_99c06255590a_2026-07-08_08-39-49"
+ },
{
"time": "2026-06-02T12:55:54.834592",
"content": "| 万科企业(02202) | 2.76 | -0.72% | 3.39% | 深套持有,止损2.5 |",
@@ -611,6 +616,11 @@
"content": "**港股概览:** 恒指+1.8%反弹,持仓分化。腾讯(00700)浮亏-4.6%目标已超但盈亏比偏低;丘钛(01478)深套-49.7%及万科(02202)深套-52.4%均暂远离止损(距12-22",
"report_id": "cron_d42f2ce3b479_2026-06-29_20-08-09"
},
+ {
+ "time": "2026-07-07T08:55:31.476330",
+ "content": "**2. 02202 万科企业 现2.31HKD(-1.28%) →距止损2.00差13%** 成本4.67亏损50.5%。行业下行未止,今日盘中无买盘承接。操作:若破2.20执行止损",
+ "report_id": "cron_d42f2ce3b479_2026-07-06_20-24-34"
+ },
{
"time": "2026-06-29T08:55:56.391301",
"content": "**③ ⚠️ 万科(02202)浮亏-52.9%,接近止损却无复盘**",
diff --git a/data/stocks/300035.json b/data/stocks/300035.json
index fc255b79..8795f527 100644
--- a/data/stocks/300035.json
+++ b/data/stocks/300035.json
@@ -56,6 +56,11 @@
"content": "• 中科电气(300035) 14.19(平) 距止损13.74仅3%",
"report_id": "cron_99c06255590a_2026-06-30_08-40-17"
},
+ {
+ "time": "2026-07-08T08:55:38.411281",
+ "content": "**② 中科电气(300035) ⚠️ 触及止损边缘**",
+ "report_id": "cron_99c06255590a_2026-07-08_08-39-49"
+ },
{
"time": "2026-06-02T12:55:54.834592",
"content": "| 中科电气(300035) | 16.44 | -2.66% | 1.77% | 深套持有,止损15.0 |",
@@ -521,6 +526,11 @@
"content": "**A股(上证+0.50%):** 海博思创(688411) 291.20+1.82%浮盈+9%RR3.92持有 | 宁德时代(300750) 393.01+0.17%弱势持有距止损3% | 法拉电子",
"report_id": "cron_d42f2ce3b479_2026-06-30_20-25-27"
},
+ {
+ "time": "2026-07-07T08:55:31.476330",
+ "content": "**3. 300035 中科电气 现13.94(-2.45%) →浮亏37.5%无止损** 连续阴跌缩量,负极材料竞争恶化。操作:反弹至15设止损",
+ "report_id": "cron_d42f2ce3b479_2026-07-06_20-24-34"
+ },
{
"time": "2026-06-29T08:55:56.391301",
"content": "- **中科电气(300035)** — 现价14.19,止损13.74(距3.2%)。新能电池板块本周-4.29%,中科电气持续走弱。",
diff --git a/data/stocks/300548.json b/data/stocks/300548.json
index ad12bce5..3ab5688c 100644
--- a/data/stocks/300548.json
+++ b/data/stocks/300548.json
@@ -56,6 +56,11 @@
"content": "长飞光纤光缆(06869) 255.40+6.68%放量大涨,持有等站上成本263.72;腾讯(00700) 429.80+2.28%,买入区435-440依然可加;中芯H(00981) 89.40+",
"report_id": "cron_99c06255590a_2026-07-01_08-39-42"
},
+ {
+ "time": "2026-07-07T08:55:31.376433",
+ "content": "博创科技(300548) 214.13 在买入区✅ | 模塑科技(000700) 16.75 在买入区✅ | 贵州茅台(600519) 1206.91 在买入区✅ | 比亚迪A(002594) 87.",
+ "report_id": "cron_99c06255590a_2026-07-07_08-43-59"
+ },
{
"time": "2026-06-02T13:56:05.495125",
"content": "🔺**博创科技(300548)** | 236.72 +7.92% | 买入区230~235已确认站上,等回调建仓2%",
diff --git a/data/stocks/688411.json b/data/stocks/688411.json
index c4aaec7a..990e4c1e 100644
--- a/data/stocks/688411.json
+++ b/data/stocks/688411.json
@@ -831,6 +831,16 @@
"content": "**A股(上证+0.50%):** 海博思创(688411) 291.20+1.82%浮盈+9%RR3.92持有 | 宁德时代(300750) 393.01+0.17%弱势持有距止损3% | 法拉电子",
"report_id": "cron_d42f2ce3b479_2026-06-30_20-25-27"
},
+ {
+ "time": "2026-07-07T08:55:31.476330",
+ "content": "**① 688411 海博思创 271.90(+8.26%) | 仓6.3%→持有**",
+ "report_id": "cron_d42f2ce3b479_2026-07-06_20-24-34"
+ },
+ {
+ "time": "2026-07-08T08:55:38.511510",
+ "content": "**③ 海博思创(688411) 现价262.20(-1.35%) | 仓~5.5%→持有**",
+ "report_id": "cron_d42f2ce3b479_2026-07-07_20-20-44"
+ },
{
"time": "2026-06-08T08:55:37.165829",
"content": "2. **海博思创(688411) 追踪止盈失守未补救** — 高点305.53回落5%约290的追踪止盈被击穿(6/4 收274.90),但未在285~290反弹位减仓,浮盈从+24.76%缩窄至+",
diff --git a/data/stocks/688639.json b/data/stocks/688639.json
index 6b10f916..fd1ccd25 100644
--- a/data/stocks/688639.json
+++ b/data/stocks/688639.json
@@ -51,6 +51,11 @@
"content": "- **华恒生物(688639)** 16.37 / 止损14.96 — 距止损8.6%,窄幅运行可持",
"report_id": "cron_99c06255590a_2026-07-02_08-32-48"
},
+ {
+ "time": "2026-07-07T08:55:31.376433",
+ "content": "- 华恒生物(688639) 16.6 | 浮亏-23%,放量破16止损",
+ "report_id": "cron_99c06255590a_2026-07-07_08-43-59"
+ },
{
"time": "2026-06-01T14:55:20.378304",
"content": "• 华恒生物(688639) 23.58 | +0.47% → 🤝持有,接近止损23.18",
diff --git a/data/strategy_staleness_report.json b/data/strategy_staleness_report.json
index 69a9b939..28958b1f 100644
--- a/data/strategy_staleness_report.json
+++ b/data/strategy_staleness_report.json
@@ -1,122 +1,14 @@
{
- "checked_at": "2026-07-07T10:22:06",
- "total_active": 19,
- "flagged_count": 7,
- "flagged": [
- {
- "code": "000700",
- "name": "模塑科技",
- "price": 17.22,
- "flags": [
- "现价17.22在买入区16~18(是否可买需结合timing_signal判断)"
- ],
- "age_days": 3,
- "last_update": "2026-07-06 11:07:37",
- "entry_zone": "16~18",
- "current": "盈利良好 | ⚠️盈亏比极低,关注 | 止损16.7 | 目标0 | 买入区15.5~18.08 | 信号:持有",
- "updated_by": "auto",
- "updated_reason": "自动生成",
- "is_watchlist": true
- },
- {
- "code": "00700",
- "name": "腾讯",
- "price": 497.35,
- "flags": [
- "价格距买入区中心32% 需关注"
- ],
- "age_days": 3,
- "last_update": "2026-07-03 11:16:48",
- "entry_zone": "371~386",
- "current": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 损363.06 | 盈0 | 买370.63~385.75",
- "updated_by": "auto",
- "updated_reason": "自动生成",
- "is_watchlist": false
- },
- {
- "code": "01088",
- "name": "中国神华",
- "price": 46.14,
- "flags": [
- "价格距买入区中心36% 需关注"
- ],
- "age_days": 3,
- "last_update": "2026-07-03 11:16:48",
- "entry_zone": "31~36",
- "current": "持有观察 | ⚠️盈亏比极低,关注 | 止损33.67 | 目标0 | 买入区31.24~36.45 | 信号:持有",
- "updated_by": "auto",
- "updated_reason": "自动生成",
- "is_watchlist": false
- },
- {
- "code": "01211",
- "name": "比亚迪股份",
- "price": 97.0,
- "flags": [
- "价格距买入区中心39% 需关注"
- ],
- "age_days": 3,
- "last_update": "2026-07-03 11:16:48",
- "entry_zone": "64~75",
- "current": "深套持有 | 深套持有 | 止损64.29 | 目标0 | 买入区64.22~74.92 | 信号:持有",
- "updated_by": "auto",
- "updated_reason": "自动生成",
- "is_watchlist": false
- },
- {
- "code": "01888",
- "name": "建滔积层板",
- "price": 73.3,
- "flags": [
- "现价73.30在买入区67~78(是否可买需结合timing_signal判断)"
- ],
- "age_days": 3,
- "last_update": "2026-07-06 11:07:37",
- "entry_zone": "67~78",
- "current": "持有观察 | ⚠️盈亏比极低,关注 | 止损64.14 | 目标0 | 买入区67.27~78.48 | 信号:持有",
- "updated_by": "auto",
- "updated_reason": "自动生成",
- "is_watchlist": true
- },
- {
- "code": "02202",
- "name": "万科企业",
- "price": 2.7,
- "flags": [
- "价格距买入区中心38% 需关注"
- ],
- "age_days": 3,
- "last_update": "2026-07-03 11:16:49",
- "entry_zone": "2~2",
- "current": "深套持有 | 深套持有 | 止损1.9 | 目标0 | 买入区1.81~2.11 | 信号:持有",
- "updated_by": "auto",
- "updated_reason": "自动生成",
- "is_watchlist": false
- },
- {
- "code": "300548",
- "name": "长芯博创",
- "price": 228.23,
- "flags": [
- "现价228.23在买入区205~240(是否可买需结合timing_signal判断)"
- ],
- "age_days": 3,
- "last_update": "2026-07-06 11:01:50",
- "entry_zone": "205~240",
- "current": "持有观察 | ⚠️盈亏比极低,关注 | 止损221.38 | 目标0 | 买入区205.41~239.64 | 信号:持有",
- "updated_by": "auto",
- "updated_reason": "自动生成",
- "is_watchlist": true
- }
- ],
+ "checked_at": "2026-07-08T09:00:59",
+ "total_active": 0,
+ "flagged_count": 0,
+ "flagged": [],
"portfolio": {
- "position_pct": 65.89,
+ "position_pct": 65.65,
"cash": 321271.0,
- "weak_position_pct": 71.4,
- "all_weak_pct": 63.2,
- "signals": [
- "[PORTFOLIO_WEAK] 组合中弱势+深套分类持仓占比71.4%>40%,建议系统性减仓"
- ]
+ "weak_position_pct": 0,
+ "all_weak_pct": 0,
+ "signals": []
},
- "summary": "扫描19个策略,7个需关注"
+ "summary": "扫描0个策略,0个需关注"
}
\ No newline at end of file
diff --git a/data/watchlist.json b/data/watchlist.json
deleted file mode 100644
index 1e7d121f..00000000
--- a/data/watchlist.json
+++ /dev/null
@@ -1,645 +0,0 @@
-{
- "stocks": [
- {
- "code": "02388",
- "name": "中银香港",
- "price": 48.26,
- "analysis": {
- "buy_low": 47.12,
- "buy_high": 48.48,
- "position_recommend": "",
- "reason": "回调-0.95%至47.80,止损46安全,继续持有观察",
- "updated_at": "2026-06-02T10:46:11.973827",
- "tech_levels": {
- "strong_support": 44.14,
- "weak_support": 47.12,
- "weak_resist": 48.48,
- "strong_resist": 51.34
- },
- "stop_loss": 42.87,
- "take_profit": 53.59,
- "entry_low": 41.01,
- "entry_high": 46.6,
- "action": "盈利持有,止损42.87 目标53.59",
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
- "reassessed_at": "2026-06-12 09:30"
- }
- },
- {
- "code": "01888",
- "name": "建滔积层板",
- "price": 65.6,
- "analysis": {
- "stop_loss": 61.42,
- "take_profit": 77.23,
- "entry_low": 63.99,
- "entry_high": 66.61,
- "action": "盈利持有 | 损61.42 | 盈77.23 | 买63.99~66.61",
- "tech_snapshot": "形态:光头光脚阴线/neutral 量价:数据不足 强撑:57.53 弱撑:61.42 弱压:71.27 强压:77.23",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 3.07,
- "action_note": "",
- "timing_signal": "neutral"
- },
- "strategy_action": "盈利持有",
- "stop_loss": 74.32,
- "entry_low": 76.01,
- "entry_high": 88.67,
- "take_profit": 95.0,
- "current_signal": "持有"
- },
- {
- "code": "01088",
- "name": "中国神华",
- "price": 45.32,
- "analysis": {
- "stop_loss": 43.75,
- "take_profit": 47.67,
- "entry_low": 44.2,
- "entry_high": 45.32,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:1.9),谨慎买入 | 损43.75 | 盈47.67 | 买44.2~45.32 | 信号:接近支撑位,关注",
- "tech_snapshot": "形态:长影星线/neutral 量价:数据不足 强撑:42.64 弱撑:44.71 弱压:45.55 强压:47.67",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 1.9,
- "action_note": "⚠️盈亏比偏低(1:1.9),谨慎买入",
- "timing_signal": "接近支撑位,关注"
- },
- "strategy_action": "盈利持有",
- "stop_loss": 38.84,
- "entry_low": 36.0,
- "entry_high": 42.0,
- "take_profit": 46.0
- },
- {
- "code": "01211",
- "name": "比亚迪股份",
- "price": 86.55,
- "analysis": {
- "buy_low": 84.95,
- "buy_high": 87.17,
- "position_recommend": null,
- "reason": "大涨+5.34%",
- "updated_at": "2026-06-02T11:01:41.539019",
- "tech_levels": {
- "strong_support": 79.79,
- "weak_support": 84.95,
- "weak_resist": 87.17,
- "strong_resist": 91.98
- },
- "stop_loss": 76.64,
- "take_profit": 95.79,
- "entry_low": 73.3,
- "entry_high": 83.3,
- "action": "盈利持有,止损76.64 目标95.79",
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
- "reassessed_at": "2026-06-12 09:30"
- }
- },
- {
- "code": "002594",
- "name": "比亚迪",
- "price": 91.6,
- "analysis": {
- "stop_loss": 88.85,
- "take_profit": 98.63,
- "entry_low": 89.77,
- "entry_high": 92.76,
- "action": "盈利持有 | 损88.85 | 盈98.63 | 买89.77~92.76 | 信号:放量下跌,等企稳再入",
- "tech_snapshot": "形态:光头光脚阳线/bullish 量价:主动卖盘占优 强撑:83.13 弱撑:89.8 弱压:92.5 强压:98.63",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 2.56,
- "action_note": "",
- "timing_signal": "放量下跌,等企稳再入"
- }
- },
- {
- "code": "09868",
- "name": "小鹏汽车-W",
- "price": 57.7,
- "analysis": {
- "stop_loss": 55.29,
- "take_profit": 59.97,
- "entry_low": 55.86,
- "entry_high": 57.16,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:1.7),谨慎买入 | 损55.29 | 盈59.97 | 买55.86~57.16 | 信号:接近支撑位,关注",
- "tech_snapshot": "形态:十字星/neutral 量价:数据不足 强撑:54.27 弱撑:56.08 弱压:58.03 强压:59.97",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 1.74,
- "action_note": "⚠️盈亏比偏低(1:1.7),谨慎买入",
- "timing_signal": "接近支撑位,关注"
- },
- "strategy_action": "观望",
- "stop_loss": 48.0,
- "entry_low": 49.0,
- "entry_high": 53.0,
- "take_profit": 58.0,
- "current_signal": "弱势观望"
- },
- {
- "code": "688795",
- "name": "摩尔线程-U",
- "price": 610.55,
- "analysis": {
- "stop_loss": 592.23,
- "take_profit": 673.56,
- "entry_low": 598.34,
- "entry_high": 622.76,
- "action": "盈利持有 | 损592.23 | 盈673.56 | 买598.34~622.76 | 信号:放量下跌,等企稳再入",
- "tech_snapshot": "形态:带上影阴线/neutral 量价:主动卖盘占优 强撑:575.87 弱撑:596.38 弱压:638.88 强压:673.56",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 3.44,
- "action_note": "",
- "timing_signal": "放量下跌,等企稳再入"
- },
- "strategy_action": "盈利持有",
- "stop_loss": 610.0,
- "entry_low": 620.0,
- "entry_high": 650.0,
- "take_profit": 700.0,
- "current_signal": "观望"
- },
- {
- "code": "688802",
- "name": "沐曦股份-U",
- "price": 694.89,
- "analysis": {
- "stop_loss": 674.04,
- "take_profit": 766.4,
- "entry_low": 680.99,
- "entry_high": 708.79,
- "action": "盈利持有 | 损674.04 | 盈766.4 | 买680.99~708.79",
- "tech_snapshot": "形态:带上影阴线/neutral 量价:数据不足 强撑:655.22 弱撑:678.97 弱压:726.74 强压:766.4",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 3.43,
- "action_note": "",
- "timing_signal": "neutral"
- },
- "strategy_action": "盈利持有",
- "stop_loss": 700.0,
- "entry_low": 710.0,
- "entry_high": 740.0,
- "take_profit": 790.0,
- "current_signal": "观望"
- },
- {
- "code": "02359",
- "name": "药明康德",
- "price": 126.7,
- "analysis": {
- "stop_loss": 121.64,
- "take_profit": 131.07,
- "entry_low": 122.89,
- "entry_high": 125.41,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:1.5),谨慎买入 | 损121.64 | 盈131.07 | 买122.89~125.41",
- "tech_snapshot": "形态:带上影阳线/neutral 量价:数据不足 强撑:118.27 弱撑:121.83 弱压:128.23 强压:131.07",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 1.51,
- "action_note": "⚠️盈亏比偏低(1:1.5),谨慎买入",
- "timing_signal": "neutral"
- },
- "strategy_action": "盈利持有",
- "stop_loss": 145.0,
- "entry_low": 148.0,
- "entry_high": 155.0,
- "take_profit": 170.0,
- "current_signal": "关注"
- },
- {
- "code": "02628",
- "name": "中国人寿",
- "price": 30.12,
- "analysis": {
- "stop_loss": 28.26,
- "take_profit": 31.6,
- "entry_low": 29.3,
- "entry_high": 29.6,
- "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 损28.26 | 盈31.6 | 买29.3~29.6 | 信号:阳线企稳,可买入",
- "tech_snapshot": "形态:光头光脚阳线/bullish 量价:数据不足 强撑:27.44 弱撑:28.26 弱压:30.74 强压:31.6",
- "reassessed_at": "2026-06-12 16:00",
- "status": "review",
- "rr_ratio": 1.04,
- "action_note": "⚠️盈亏比不足1:1.5,不建议买入",
- "timing_signal": "阳线企稳,可买入"
- }
- },
- {
- "code": "00968",
- "name": "信义光能",
- "price": 2.41,
- "analysis": {
- "stop_loss": 2.34,
- "take_profit": 2.63,
- "entry_low": 2.36,
- "entry_high": 2.46,
- "action": "盈利持有 | 损2.34 | 盈2.63 | 买2.36~2.46 | 信号:接近支撑位,关注",
- "tech_snapshot": "形态:带上影阴线/bearish 量价:数据不足 强撑:2.24 弱撑:2.37 弱压:2.47 强压:2.63",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 3.14,
- "action_note": "",
- "timing_signal": "接近支撑位,关注"
- },
- "strategy_action": "弱势持有",
- "stop_loss": 1.92,
- "entry_low": 1.96,
- "entry_high": 2.15,
- "take_profit": 2.4,
- "current_signal": "弱势持有"
- },
- {
- "code": "06869",
- "name": "长飞光纤",
- "price": 226.6,
- "analysis": {
- "stop_loss": 219.27,
- "take_profit": 254.33,
- "entry_low": 224.81,
- "entry_high": 233.29,
- "action": "盈利持有 | 损219.27 | 盈254.33 | 买224.81~233.29",
- "tech_snapshot": "形态:带下影阴线/neutral 量价:数据不足 强撑:209.13 弱撑:219.27 弱压:241.87 强压:254.33",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 2.46,
- "action_note": "",
- "timing_signal": "neutral"
- },
- "strategy_action": "深套持有",
- "stop_loss": 195.46,
- "entry_low": 181.08,
- "entry_high": 211.26,
- "take_profit": 230.0
- },
- {
- "code": "02318",
- "name": "中国平安",
- "price": 57.15,
- "analysis": {
- "stop_loss": 55.39,
- "take_profit": 60.16,
- "entry_low": 55.96,
- "entry_high": 57.3,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:1.8),谨慎买入 | 损55.39 | 盈60.16 | 买55.96~57.3 | 信号:接近支撑位,关注",
- "tech_snapshot": "形态:倒T线/射击之星/bearish 量价:数据不足 强撑:54.45 弱撑:56.15 弱压:58.25 强压:60.16",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 1.79,
- "action_note": "⚠️盈亏比偏低(1:1.8),谨慎买入",
- "timing_signal": "接近支撑位,关注"
- },
- "strategy_action": "弱势持有",
- "stop_loss": 49.5,
- "entry_low": 50.0,
- "entry_high": 53.0,
- "take_profit": 58.0,
- "current_signal": "弱势观望"
- },
- {
- "code": "688639",
- "name": "华恒生物",
- "price": 21.2,
- "analysis": {
- "stop_loss": 20.56,
- "take_profit": 22.66,
- "entry_low": 20.78,
- "entry_high": 21.4,
- "action": "盈利持有 | 损20.56 | 盈22.66 | 买20.78~21.4 | 信号:量价齐升,可买入",
- "tech_snapshot": "形态:带下影阳线/bullish 量价:主动买盘占优 强撑:19.26 弱撑:20.6 弱压:21.46 强压:22.66",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 2.28,
- "action_note": "",
- "timing_signal": "量价齐升,可买入"
- }
- },
- {
- "code": "300548",
- "name": "博创科技",
- "price": 224.03,
- "analysis": {
- "buy_low": 224.25,
- "buy_high": 234.13,
- "position_recommend": "2%",
- "reason": "+7.26%站稳230确认可追,回调至230~235建仓。",
- "updated_at": "2026-06-02T14:42:56.675237",
- "tech_levels": {
- "strong_support": 217.39,
- "weak_support": 224.25,
- "weak_resist": 234.13,
- "strong_resist": 240.34
- },
- "stop_loss": 211.19,
- "take_profit": 263.98,
- "entry_low": 225.0,
- "entry_high": 229.55,
- "action": "盈利持有,止损211.19 目标263.98",
- "tech_snapshot": "形态:光头光脚阴线/neutral 量价:主动买盘占优 强撑:227.53 弱撑:225.0 弱压:231.57 强压:233.59",
- "reassessed_at": "2026-06-12 09:30"
- }
- },
- {
- "code": "300124",
- "name": "汇川技术",
- "price": 67.16,
- "analysis": {
- "stop_loss": 65.15,
- "take_profit": 73.7,
- "entry_low": 65.82,
- "entry_high": 68.5,
- "action": "盈利持有 | 损65.15 | 盈73.7 | 买65.82~68.5 | 信号:放量下跌,等企稳再入",
- "tech_snapshot": "形态:带上影阴线/bearish 量价:主动卖盘占优 强撑:62.95 弱撑:65.99 弱压:69.49 强压:73.7",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 3.25,
- "action_note": "",
- "timing_signal": "放量下跌,等企稳再入"
- },
- "strategy_action": "盈利持有",
- "stop_loss": 67.5,
- "entry_low": 68.5,
- "entry_high": 72.0,
- "take_profit": 78.0,
- "current_signal": "关注"
- },
- {
- "code": "01070",
- "name": "TCL电子",
- "price": 14.04,
- "analysis": {
- "stop_loss": 13.59,
- "take_profit": 15.3,
- "entry_low": 13.73,
- "entry_high": 14.27,
- "action": "盈利持有 | 损13.59 | 盈15.3 | 买13.73~14.27 | 信号:接近支撑位,关注",
- "tech_snapshot": "形态:倒T线/射击之星/bearish 量价:数据不足 强撑:13.06 弱撑:13.75 弱压:14.42 强压:15.3",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 3.07,
- "action_note": "",
- "timing_signal": "接近支撑位,关注"
- }
- },
- {
- "code": "09988",
- "name": "阿里巴巴-W",
- "price": 110.2,
- "analysis": {
- "buy_low": 107.4,
- "buy_high": 111.4,
- "position_recommend": null,
- "reason": "大涨+5.86%可关注",
- "updated_at": "2026-06-02T11:01:41.539017",
- "tech_levels": {
- "strong_support": 102.72,
- "weak_support": 107.4,
- "weak_resist": 111.4,
- "strong_resist": 117.48
- },
- "stop_loss": 99.18,
- "take_profit": 123.97,
- "entry_low": 94.86,
- "entry_high": 107.8,
- "action": "盈利持有,止损99.18 目标123.97",
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
- "reassessed_at": "2026-06-12 09:30"
- }
- },
- {
- "code": "00700",
- "name": "腾讯控股",
- "price": 463.6,
- "analysis": {
- "buy_low": 457.2,
- "buy_high": 467.93,
- "position_recommend": null,
- "reason": "暴涨+7.75%逼近止盈470",
- "updated_at": "2026-06-02T11:01:41.539014",
- "tech_levels": {
- "strong_support": 440.4,
- "weak_support": 457.2,
- "weak_resist": 467.93,
- "strong_resist": 486.94
- },
- "stop_loss": 432.58,
- "take_profit": 540.73,
- "entry_low": 413.78,
- "entry_high": 470.2,
- "action": "盈利持有,止损432.58 目标540.73",
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
- "reassessed_at": "2026-06-12 09:30"
- }
- },
- {
- "code": "02202",
- "name": "万科企业",
- "price": 2.64,
- "analysis": {
- "buy_low": 2.62,
- "buy_high": 2.69,
- "position_recommend": "",
- "reason": "万科企业2.83微涨+1.8%,深套持有",
- "updated_at": "2026-06-02T10:46:11.973869",
- "tech_levels": {
- "strong_support": 2.47,
- "weak_support": 2.62,
- "weak_resist": 2.69,
- "strong_resist": 2.83
- },
- "stop_loss": 2.42,
- "take_profit": 3.02,
- "entry_low": 2.31,
- "entry_high": 2.63,
- "action": "盈利持有,止损2.42 目标3.02",
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
- "reassessed_at": "2026-06-12 09:30"
- }
- },
- {
- "code": "688981",
- "name": "中芯国际",
- "price": 124.88,
- "analysis": {
- "buy_low": 124.58,
- "buy_high": 132.34,
- "position_recommend": "0%",
- "reason": "浮盈+0.0%,止损112.53 目标137.0",
- "updated_at": "2026-06-09T10:46:18",
- "tech_levels": {
- "strong_support": 121.97,
- "weak_support": 124.58,
- "weak_resist": 132.34,
- "strong_resist": 137.49
- },
- "stop_loss": 121.46,
- "take_profit": 151.82,
- "entry_low": 127.4,
- "entry_high": 132.02,
- "action": "盈利持有,止损121.46 目标151.82",
- "tech_snapshot": "形态:倒锤头/bullish 量价:买卖均衡 强撑:130.86 弱撑:127.4 弱压:132.8 强压:133.58",
- "reassessed_at": "2026-06-12 09:30"
- },
- "strategy_action": "盈利持有",
- "stop_loss": 132.0,
- "entry_low": 135.0,
- "entry_high": 142.0,
- "take_profit": 155.0,
- "current_signal": "关注"
- },
- {
- "code": "01478",
- "name": "丘钛科技",
- "price": 8.13,
- "analysis": {
- "buy_low": 7.98,
- "buy_high": 8.51,
- "position_recommend": "",
- "reason": "丘钛科技10.07跌-1.66%,深套-26.48%暂持",
- "updated_at": "2026-06-02T10:46:11.973872",
- "tech_levels": {
- "strong_support": 7.92,
- "weak_support": 7.98,
- "weak_resist": 8.51,
- "strong_resist": 8.74
- },
- "stop_loss": 7.47,
- "take_profit": 9.34,
- "entry_low": 7.15,
- "entry_high": 8.12,
- "action": "盈利持有,止损7.47 目标9.34",
- "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
- "reassessed_at": "2026-06-12 09:30"
- }
- },
- {
- "code": "001309",
- "name": "德明利",
- "price": 617.43,
- "analysis": {
- "stop_loss": 598.91,
- "take_profit": 682.35,
- "entry_low": 605.08,
- "entry_high": 629.78,
- "action": "盈利持有 | 损598.91 | 盈682.35 | 买605.08~629.78",
- "tech_snapshot": "形态:光头光脚阴线/neutral 量价:主动买盘占优 强撑:583.56 弱撑:601.91 弱压:648.48 强压:682.35",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 3.51,
- "action_note": "",
- "timing_signal": "neutral"
- },
- "strategy_action": "盈利持有",
- "stop_loss": 795.0,
- "entry_low": 820.0,
- "entry_high": 870.0,
- "take_profit": 950.0,
- "current_signal": "关注"
- },
- {
- "code": "06160",
- "name": "百济神州",
- "price": 158.9,
- "prev_close": 163.1,
- "market": "HK",
- "analysis": {
- "stop_loss": 152.78,
- "take_profit": 170.7,
- "entry_low": 154.35,
- "entry_high": 159.95,
- "action": "盈利持有 | 损152.78 | 盈170.7 | 买154.35~159.95 | 信号:接近支撑位,关注",
- "tech_snapshot": "形态:锤子线/T字线/neutral 量价:数据不足 强撑:143.3 弱撑:155.0 弱压:159.5 强压:170.7",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 2.8,
- "action_note": "",
- "timing_signal": "接近支撑位,关注"
- }
- },
- {
- "code": "688411",
- "name": "海博思创",
- "price": 264.66,
- "analysis": {
- "stop_loss": 239.18,
- "take_profit": 264.86,
- "entry_low": 241.3,
- "entry_high": 259.89,
- "action": "盈利持有 | 止损239.18 | 目标264.86 | 买入区241.3~259.89",
- "tech_snapshot": "形态:光头光脚阳线/bullish 量价:买卖均衡 强撑:239.18 弱撑:241.3 弱压:259.89 强压:264.86",
- "reassessed_at": "2026-06-12 10:02",
- "status": "updated",
- "buy_low": 241.3,
- "buy_high": 277.96,
- "tech_levels": {
- "strong_support": 235.42,
- "weak_support": 241.3,
- "weak_resist": 277.96,
- "strong_resist": 286.68
- }
- }
- },
- {
- "code": "000700",
- "name": "模塑科技",
- "price": 14.4,
- "analysis": {
- "stop_loss": 13.97,
- "take_profit": 15.27,
- "entry_low": 14.11,
- "entry_high": 14.49,
- "action": "盈利持有 | 损13.97 | 盈15.27 | 买14.11~14.49",
- "tech_snapshot": "形态:光头光脚阴线/bearish 量价:主动买盘占优 强撑:13.75 弱撑:14.08 弱压:14.84 强压:15.27",
- "reassessed_at": "2026-06-12 16:00",
- "status": "updated",
- "rr_ratio": 2.02,
- "action_note": "",
- "timing_signal": "neutral"
- }
- },
- {
- "code": "301308",
- "name": "江波龙",
- "added_at": "2026-07-05",
- "source": "position_analyst",
- "currency": "CNY",
- "strategy_action": "盈利持有",
- "stop_loss": 590.45,
- "buy_zone_low": 618.38,
- "buy_zone_high": 630.83,
- "take_profit": 691.4,
- "sector": "半导体/存储芯片"
- },
- {
- "code": "000850",
- "name": "华茂股份",
- "price": 3.81,
- "analysis": {
- "buy_low": 3.7,
- "buy_high": 3.9,
- "position_recommend": "2%",
- "reason": "纺织是壳核心是金融资产, PB0.78破净提供安全垫, 7/16股东会催化",
- "updated_at": "2026-07-06",
- "tech_levels": {
- "strong_support": 3.57,
- "weak_support": 3.8,
- "weak_resist": 4.04,
- "strong_resist": 4.16
- },
- "stop_loss": 3.5,
- "take_profit": 4.3,
- "entry_low": 3.7,
- "entry_high": 3.9,
- "action": "3.70~3.90区间可建仓1~2%",
- "tech_snapshot": "形态:弱势回调 量价:利好出尽缩量回调 强撑:3.57 弱撑:3.80 弱压:4.04 强压:4.16",
- "reassessed_at": "2026-07-07"
- },
- "sector": "纺织/金融投资"
- }
- ],
- "updated_at": "2026-07-06"
-}
\ No newline at end of file
diff --git a/data/watchlist.json.bak2 b/data/watchlist.json.bak2
index 31b5bcdb..1e7d121f 100644
--- a/data/watchlist.json.bak2
+++ b/data/watchlist.json.bak2
@@ -3,572 +3,643 @@
{
"code": "02388",
"name": "中银香港",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 39.47,
- "take_profit": 47.08,
- "entry_low": 41.53,
- "entry_high": 43.23,
- "action": null,
+ "price": 48.26,
"analysis": {
- "stop_loss": 39.47,
- "take_profit": 47.08,
- "entry_low": 41.53,
- "entry_high": 43.23,
- "action": "盈利持有 | 目标47.08 | 止损39.47 | 买入区41.53~43.23 | 信号:关注",
- "tech_snapshot": "形态:带下影阴线/neutral 量价:数据不足 强撑:38.12 弱撑:41.6 弱压:43.6 强压:47.08 | MA5=44.88 MA10=46.42 MA20=47.03 MA60=45.88",
- "multi_tf_context": "多周期看多 | MA20=47.03 | MA60=45.88 | 长撑:日强支撑=41.82 | 长压:日强阻=49.36",
- "reassessed_at": "2026-07-01 09:01",
+ "buy_low": 47.12,
+ "buy_high": 48.48,
+ "position_recommend": "",
+ "reason": "回调-0.95%至47.80,止损46安全,继续持有观察",
+ "updated_at": "2026-06-02T10:46:11.973827",
+ "tech_levels": {
+ "strong_support": 44.14,
+ "weak_support": 47.12,
+ "weak_resist": 48.48,
+ "strong_resist": 51.34
+ },
+ "stop_loss": 42.87,
+ "take_profit": 53.59,
+ "entry_low": 41.01,
+ "entry_high": 46.6,
+ "action": "盈利持有,止损42.87 目标53.59",
+ "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
+ "reassessed_at": "2026-06-12 09:30"
+ }
+ },
+ {
+ "code": "01888",
+ "name": "建滔积层板",
+ "price": 65.6,
+ "analysis": {
+ "stop_loss": 61.42,
+ "take_profit": 77.23,
+ "entry_low": 63.99,
+ "entry_high": 66.61,
+ "action": "盈利持有 | 损61.42 | 盈77.23 | 买63.99~66.61",
+ "tech_snapshot": "形态:光头光脚阴线/neutral 量价:数据不足 强撑:57.53 弱撑:61.42 弱压:71.27 强压:77.23",
+ "reassessed_at": "2026-06-12 16:00",
"status": "updated",
- "rr_ratio": 3.7,
+ "rr_ratio": 3.07,
"action_note": "",
- "timing_signal": "关注"
+ "timing_signal": "neutral"
},
- "trigger": {
- "stop_loss": 39.47,
- "entry_zone": "41.53~43.23",
- "take_profit_zone": "0~47.08"
+ "strategy_action": "盈利持有",
+ "stop_loss": 74.32,
+ "entry_low": 76.01,
+ "entry_high": 88.67,
+ "take_profit": 95.0,
+ "current_signal": "持有"
+ },
+ {
+ "code": "01088",
+ "name": "中国神华",
+ "price": 45.32,
+ "analysis": {
+ "stop_loss": 43.75,
+ "take_profit": 47.67,
+ "entry_low": 44.2,
+ "entry_high": 45.32,
+ "action": "盈利持有 | ⚠️盈亏比偏低(1:1.9),谨慎买入 | 损43.75 | 盈47.67 | 买44.2~45.32 | 信号:接近支撑位,关注",
+ "tech_snapshot": "形态:长影星线/neutral 量价:数据不足 强撑:42.64 弱撑:44.71 弱压:45.55 强压:47.67",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "updated",
+ "rr_ratio": 1.9,
+ "action_note": "⚠️盈亏比偏低(1:1.9),谨慎买入",
+ "timing_signal": "接近支撑位,关注"
},
- "currency": "CNY"
+ "strategy_action": "盈利持有",
+ "stop_loss": 38.84,
+ "entry_low": 36.0,
+ "entry_high": 42.0,
+ "take_profit": 46.0
+ },
+ {
+ "code": "01211",
+ "name": "比亚迪股份",
+ "price": 86.55,
+ "analysis": {
+ "buy_low": 84.95,
+ "buy_high": 87.17,
+ "position_recommend": null,
+ "reason": "大涨+5.34%",
+ "updated_at": "2026-06-02T11:01:41.539019",
+ "tech_levels": {
+ "strong_support": 79.79,
+ "weak_support": 84.95,
+ "weak_resist": 87.17,
+ "strong_resist": 91.98
+ },
+ "stop_loss": 76.64,
+ "take_profit": 95.79,
+ "entry_low": 73.3,
+ "entry_high": 83.3,
+ "action": "盈利持有,止损76.64 目标95.79",
+ "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
+ "reassessed_at": "2026-06-12 09:30"
+ }
},
{
"code": "002594",
"name": "比亚迪",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 77.31,
- "take_profit": 85.52,
- "entry_low": 78.11,
- "entry_high": 80.59,
- "action": null,
+ "price": 91.6,
"analysis": {
- "stop_loss": 77.31,
- "take_profit": 85.52,
- "entry_low": 78.11,
- "entry_high": 80.59,
- "action": "盈利持有 | 目标85.52 | 止损77.31 | 买入区78.11~80.59 | 信号:观望",
- "tech_snapshot": "形态:长影星线/neutral 量价:主动卖盘占优 强撑:73.7 弱撑:78.67 弱压:80.64 强压:85.52 | MA5=99.16 MA10=98.47 MA20=96.75 MA60=96.44",
- "multi_tf_context": "震荡/无明显方向 | MA20=96.75 | MA60=96.44 | 长撑:日强支撑=77.6 | 长压:周强阻=105.97",
- "reassessed_at": "2026-07-01 09:00",
+ "stop_loss": 88.85,
+ "take_profit": 98.63,
+ "entry_low": 89.77,
+ "entry_high": 92.76,
+ "action": "盈利持有 | 损88.85 | 盈98.63 | 买89.77~92.76 | 信号:放量下跌,等企稳再入",
+ "tech_snapshot": "形态:光头光脚阳线/bullish 量价:主动卖盘占优 强撑:83.13 弱撑:89.8 弱压:92.5 强压:98.63",
+ "reassessed_at": "2026-06-12 16:00",
"status": "updated",
- "rr_ratio": 2.44,
+ "rr_ratio": 2.56,
"action_note": "",
- "timing_signal": "观望"
- },
- "trigger": {
- "stop_loss": 77.31,
- "entry_zone": "78.11~80.59",
- "take_profit_zone": "0~85.52"
- },
- "currency": "CNY"
+ "timing_signal": "放量下跌,等企稳再入"
+ }
},
{
"code": "09868",
"name": "小鹏汽车-W",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 45.44,
- "take_profit": 51.57,
- "entry_low": 46.98,
- "entry_high": 47.89,
- "action": null,
+ "price": 57.7,
"analysis": {
- "stop_loss": 45.44,
- "take_profit": 51.57,
- "entry_low": 46.98,
- "entry_high": 47.89,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:1.6),谨慎买入 | 止损参考45.44 | 买入区46.98~47.89 | 信号:弱势持有",
- "tech_snapshot": "形态:带下影阳线/bullish 量价:数据不足 强撑:44.43 弱撑:47.4 弱压:51.54 强压:55.69 | MA5=78.84 MA10=79.64 MA20=78.58 MA60=73.08",
- "multi_tf_context": "震荡/无明显方向 | MA20=78.58 | MA60=73.08 | 长撑:日弱支撑=46.22 | 长压:月强阻=84.6",
- "reassessed_at": "2026-07-01 09:01",
- "status": "manual",
- "rr_ratio": 0.18,
- "action_note": "⚠️盈亏比偏低(1:1.6),谨慎买入",
- "timing_signal": "弱势持有"
+ "stop_loss": 55.29,
+ "take_profit": 59.97,
+ "entry_low": 55.86,
+ "entry_high": 57.16,
+ "action": "盈利持有 | ⚠️盈亏比偏低(1:1.7),谨慎买入 | 损55.29 | 盈59.97 | 买55.86~57.16 | 信号:接近支撑位,关注",
+ "tech_snapshot": "形态:十字星/neutral 量价:数据不足 强撑:54.27 弱撑:56.08 弱压:58.03 强压:59.97",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "updated",
+ "rr_ratio": 1.74,
+ "action_note": "⚠️盈亏比偏低(1:1.7),谨慎买入",
+ "timing_signal": "接近支撑位,关注"
},
- "trigger": {
- "stop_loss": 45.44,
- "entry_zone": "46.98~47.89",
- "take_profit_zone": "0~51.57"
- },
- "currency": "CNY"
+ "strategy_action": "观望",
+ "stop_loss": 48.0,
+ "entry_low": 49.0,
+ "entry_high": 53.0,
+ "take_profit": 58.0,
+ "current_signal": "弱势观望"
},
{
"code": "688795",
"name": "摩尔线程-U",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 695.0,
- "take_profit": 769.86,
- "entry_low": 702.16,
- "entry_high": 724.94,
- "action": null,
+ "price": 610.55,
"analysis": {
- "stop_loss": 695.0,
- "take_profit": 769.86,
- "entry_low": 702.16,
- "entry_high": 724.94,
- "action": "盈利持有 | 目标769.86 | 止损695.0 | 买入区702.16~724.94 | 信号:观望",
- "tech_snapshot": "形态:光头光脚阳线/bullish 量价:主动卖盘占优 强撑:651.88 弱撑:698.95 弱压:728.41 强压:769.86 | MA5=699.45 MA10=681.68 MA20=654.65 MA60=657.69",
- "multi_tf_context": "震荡/无明显方向 | MA20=654.65 | MA60=657.69 | 长撑:MA20=654.65 | 长压:周强阻=755.51",
- "reassessed_at": "2026-07-01 09:02",
+ "stop_loss": 592.23,
+ "take_profit": 673.56,
+ "entry_low": 598.34,
+ "entry_high": 622.76,
+ "action": "盈利持有 | 损592.23 | 盈673.56 | 买598.34~622.76 | 信号:放量下跌,等企稳再入",
+ "tech_snapshot": "形态:带上影阴线/neutral 量价:主动卖盘占优 强撑:575.87 弱撑:596.38 弱压:638.88 强压:673.56",
+ "reassessed_at": "2026-06-12 16:00",
"status": "updated",
- "rr_ratio": 2.48,
+ "rr_ratio": 3.44,
"action_note": "",
- "timing_signal": "观望"
+ "timing_signal": "放量下跌,等企稳再入"
},
- "trigger": {
- "stop_loss": 695.0,
- "entry_zone": "702.16~724.94",
- "take_profit_zone": "0~769.86"
- },
- "currency": "CNY"
+ "strategy_action": "盈利持有",
+ "stop_loss": 610.0,
+ "entry_low": 620.0,
+ "entry_high": 650.0,
+ "take_profit": 700.0,
+ "current_signal": "观望"
},
{
"code": "688802",
"name": "沐曦股份-U",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 803.45,
- "take_profit": 932.42,
- "entry_low": 811.73,
- "entry_high": 844.87,
- "action": null,
+ "price": 694.89,
"analysis": {
- "stop_loss": 803.45,
- "take_profit": 932.42,
- "entry_low": 811.73,
- "entry_high": 844.87,
- "action": "盈利持有 | 目标932.42 | 止损803.45 | 买入区811.73~844.87 | 信号:买入",
- "tech_snapshot": "形态:光头光脚阳线/bullish 量价:数据不足 强撑:698.42 弱撑:793.55 弱压:849.88 强压:932.42 | MA5=778.59 MA10=765.11 MA20=736.75 MA60=714.48",
- "multi_tf_context": "震荡/无明显方向 | MA20=736.75 | MA60=714.48 | 长撑:MA20=736.75 | 长压:周强阻=850.0",
- "reassessed_at": "2026-07-01 09:02",
+ "stop_loss": 674.04,
+ "take_profit": 766.4,
+ "entry_low": 680.99,
+ "entry_high": 708.79,
+ "action": "盈利持有 | 损674.04 | 盈766.4 | 买680.99~708.79",
+ "tech_snapshot": "形态:带上影阴线/neutral 量价:数据不足 强撑:655.22 弱撑:678.97 弱压:726.74 强压:766.4",
+ "reassessed_at": "2026-06-12 16:00",
"status": "updated",
- "rr_ratio": 3.0,
+ "rr_ratio": 3.43,
"action_note": "",
- "timing_signal": "买入"
+ "timing_signal": "neutral"
},
- "trigger": {
- "stop_loss": 803.45,
- "entry_zone": "811.73~844.87",
- "take_profit_zone": "0~932.42"
- },
- "currency": "CNY"
+ "strategy_action": "盈利持有",
+ "stop_loss": 700.0,
+ "entry_low": 710.0,
+ "entry_high": 740.0,
+ "take_profit": 790.0,
+ "current_signal": "观望"
},
{
"code": "02359",
"name": "药明康德",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 142.47,
- "take_profit": 169.6,
- "entry_low": 150.63,
- "entry_high": 156.77,
- "action": null,
+ "price": 126.7,
"analysis": {
- "stop_loss": 142.47,
- "take_profit": 169.6,
- "entry_low": 150.63,
- "entry_high": 156.77,
- "action": "盈利持有 | 目标169.6 | 止损142.47 | 买入区150.63~156.77 | 信号:关注",
- "tech_snapshot": "形态:倒T线/射击之星/bearish 量价:数据不足 强撑:139.6 弱撑:150.8 弱压:157.5 强压:169.6 | MA5=147.74 MA10=137.78 MA20=130.76 MA60=130.08",
- "multi_tf_context": "震荡/无明显方向 | MA20=130.76 | MA60=130.08 | 长撑:MA20=130.76 | 长压:日强阻=158.4",
- "reassessed_at": "2026-07-01 09:01",
+ "stop_loss": 121.64,
+ "take_profit": 131.07,
+ "entry_low": 122.89,
+ "entry_high": 125.41,
+ "action": "盈利持有 | ⚠️盈亏比偏低(1:1.5),谨慎买入 | 损121.64 | 盈131.07 | 买122.89~125.41",
+ "tech_snapshot": "形态:带上影阳线/neutral 量价:数据不足 强撑:118.27 弱撑:121.83 弱压:128.23 强压:131.07",
+ "reassessed_at": "2026-06-12 16:00",
"status": "updated",
- "rr_ratio": 3.01,
- "action_note": "",
- "timing_signal": "关注"
+ "rr_ratio": 1.51,
+ "action_note": "⚠️盈亏比偏低(1:1.5),谨慎买入",
+ "timing_signal": "neutral"
},
- "trigger": {
- "stop_loss": 142.47,
- "entry_zone": "150.63~156.77",
- "take_profit_zone": "0~169.6"
- },
- "currency": "CNY"
+ "strategy_action": "盈利持有",
+ "stop_loss": 145.0,
+ "entry_low": 148.0,
+ "entry_high": 155.0,
+ "take_profit": 170.0,
+ "current_signal": "关注"
},
{
"code": "02628",
"name": "中国人寿",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 24.7,
- "take_profit": 28.96,
- "entry_low": 26.13,
- "entry_high": 27.02,
- "action": null,
+ "price": 30.12,
"analysis": {
- "stop_loss": 24.7,
- "take_profit": 28.96,
- "entry_low": 26.13,
- "entry_high": 27.02,
- "action": "盈利持有 | 目标28.96 | 止损24.7 | 买入区26.13~27.02 | 信号:关注",
- "tech_snapshot": "形态:小阴线/bearish 量价:数据不足 强撑:24.48 弱撑:26.26 弱压:27.12 强压:28.96 | MA5=29.79 MA10=30.92 MA20=31.8 MA60=31.12",
- "multi_tf_context": "震荡/无明显方向 | MA20=31.8 | MA60=31.12 | 长撑:日强支撑=26.32 | 长压:月强阻=36.16",
- "reassessed_at": "2026-07-01 09:01",
- "status": "updated",
- "rr_ratio": 2.47,
- "action_note": "",
- "timing_signal": "关注"
- },
- "trigger": {
- "stop_loss": 24.7,
- "entry_zone": "26.13~27.02",
- "take_profit_zone": "0~28.96"
- },
- "currency": "CNY"
+ "stop_loss": 28.26,
+ "take_profit": 31.6,
+ "entry_low": 29.3,
+ "entry_high": 29.6,
+ "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 损28.26 | 盈31.6 | 买29.3~29.6 | 信号:阳线企稳,可买入",
+ "tech_snapshot": "形态:光头光脚阳线/bullish 量价:数据不足 强撑:27.44 弱撑:28.26 弱压:30.74 强压:31.6",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "review",
+ "rr_ratio": 1.04,
+ "action_note": "⚠️盈亏比不足1:1.5,不建议买入",
+ "timing_signal": "阳线企稳,可买入"
+ }
},
{
"code": "00968",
"name": "信义光能",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 1.94,
- "take_profit": 2.21,
- "entry_low": 2.02,
- "entry_high": 2.05,
- "action": null,
+ "price": 2.41,
"analysis": {
- "stop_loss": 1.94,
- "take_profit": 2.21,
- "entry_low": 2.02,
- "entry_high": 2.05,
- "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标2.21 | 止损1.94 | 买入区2.02~2.05 | 信号:弱势持有",
- "tech_snapshot": "形态:光头光脚阳线/bullish 量价:数据不足 强撑:1.89 弱撑:2.02 弱压:2.09 强压:2.21 | MA5=3.11 MA10=3.14 MA20=3.22 MA60=3.33",
- "multi_tf_context": "震荡/无明显方向 | MA20=3.22 | MA60=3.33 | 长撑:日弱支撑=1.94 | 长压:日强阻=2.73",
- "reassessed_at": "2026-07-01 09:00",
- "status": "review",
- "rr_ratio": 1.25,
- "action_note": "⚠️盈亏比不足1:1.5,不建议买入",
- "timing_signal": "弱势持有"
+ "stop_loss": 2.34,
+ "take_profit": 2.63,
+ "entry_low": 2.36,
+ "entry_high": 2.46,
+ "action": "盈利持有 | 损2.34 | 盈2.63 | 买2.36~2.46 | 信号:接近支撑位,关注",
+ "tech_snapshot": "形态:带上影阴线/bearish 量价:数据不足 强撑:2.24 弱撑:2.37 弱压:2.47 强压:2.63",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "updated",
+ "rr_ratio": 3.14,
+ "action_note": "",
+ "timing_signal": "接近支撑位,关注"
},
- "trigger": {
- "stop_loss": 1.94,
- "entry_zone": "2.02~2.05",
- "take_profit_zone": "0~2.21"
+ "strategy_action": "弱势持有",
+ "stop_loss": 1.92,
+ "entry_low": 1.96,
+ "entry_high": 2.15,
+ "take_profit": 2.4,
+ "current_signal": "弱势持有"
+ },
+ {
+ "code": "06869",
+ "name": "长飞光纤",
+ "price": 226.6,
+ "analysis": {
+ "stop_loss": 219.27,
+ "take_profit": 254.33,
+ "entry_low": 224.81,
+ "entry_high": 233.29,
+ "action": "盈利持有 | 损219.27 | 盈254.33 | 买224.81~233.29",
+ "tech_snapshot": "形态:带下影阴线/neutral 量价:数据不足 强撑:209.13 弱撑:219.27 弱压:241.87 强压:254.33",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "updated",
+ "rr_ratio": 2.46,
+ "action_note": "",
+ "timing_signal": "neutral"
},
- "currency": "CNY"
+ "strategy_action": "深套持有",
+ "stop_loss": 195.46,
+ "entry_low": 181.08,
+ "entry_high": 211.26,
+ "take_profit": 230.0
},
{
"code": "02318",
"name": "中国平安",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 49.36,
- "take_profit": 53.76,
- "entry_low": 50.08,
- "entry_high": 51.12,
- "action": null,
+ "price": 57.15,
"analysis": {
- "stop_loss": 49.36,
- "take_profit": 53.76,
- "entry_low": 50.08,
- "entry_high": 51.12,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:2.0),谨慎买入 | 止损参考49.36 | 买入区50.08~51.12 | 信号:关注",
- "tech_snapshot": "形态:光头光脚阴线/neutral 量价:数据不足 强撑:48.87 弱撑:50.33 弱压:52.18 强压:54.07 | MA5=68.73 MA10=69.1 MA20=68.89 MA60=67.28",
- "multi_tf_context": "震荡/无明显方向 | MA20=68.89 | MA60=67.28 | 长撑:日强支撑=50.0 | 长压:周强阻=66.5",
- "reassessed_at": "2026-07-01 09:01",
- "status": "manual",
- "rr_ratio": 1.6,
- "action_note": "⚠️盈亏比偏低(1:2.0),谨慎买入",
- "timing_signal": "关注"
+ "stop_loss": 55.39,
+ "take_profit": 60.16,
+ "entry_low": 55.96,
+ "entry_high": 57.3,
+ "action": "盈利持有 | ⚠️盈亏比偏低(1:1.8),谨慎买入 | 损55.39 | 盈60.16 | 买55.96~57.3 | 信号:接近支撑位,关注",
+ "tech_snapshot": "形态:倒T线/射击之星/bearish 量价:数据不足 强撑:54.45 弱撑:56.15 弱压:58.25 强压:60.16",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "updated",
+ "rr_ratio": 1.79,
+ "action_note": "⚠️盈亏比偏低(1:1.8),谨慎买入",
+ "timing_signal": "接近支撑位,关注"
},
- "trigger": {
- "stop_loss": 49.36,
- "entry_zone": "50.08~51.12",
- "take_profit_zone": "0~53.76"
- },
- "currency": "CNY"
+ "strategy_action": "弱势持有",
+ "stop_loss": 49.5,
+ "entry_low": 50.0,
+ "entry_high": 53.0,
+ "take_profit": 58.0,
+ "current_signal": "弱势观望"
+ },
+ {
+ "code": "688639",
+ "name": "华恒生物",
+ "price": 21.2,
+ "analysis": {
+ "stop_loss": 20.56,
+ "take_profit": 22.66,
+ "entry_low": 20.78,
+ "entry_high": 21.4,
+ "action": "盈利持有 | 损20.56 | 盈22.66 | 买20.78~21.4 | 信号:量价齐升,可买入",
+ "tech_snapshot": "形态:带下影阳线/bullish 量价:主动买盘占优 强撑:19.26 弱撑:20.6 弱压:21.46 强压:22.66",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "updated",
+ "rr_ratio": 2.28,
+ "action_note": "",
+ "timing_signal": "量价齐升,可买入"
+ }
+ },
+ {
+ "code": "300548",
+ "name": "博创科技",
+ "price": 224.03,
+ "analysis": {
+ "buy_low": 224.25,
+ "buy_high": 234.13,
+ "position_recommend": "2%",
+ "reason": "+7.26%站稳230确认可追,回调至230~235建仓。",
+ "updated_at": "2026-06-02T14:42:56.675237",
+ "tech_levels": {
+ "strong_support": 217.39,
+ "weak_support": 224.25,
+ "weak_resist": 234.13,
+ "strong_resist": 240.34
+ },
+ "stop_loss": 211.19,
+ "take_profit": 263.98,
+ "entry_low": 225.0,
+ "entry_high": 229.55,
+ "action": "盈利持有,止损211.19 目标263.98",
+ "tech_snapshot": "形态:光头光脚阴线/neutral 量价:主动买盘占优 强撑:227.53 弱撑:225.0 弱压:231.57 强压:233.59",
+ "reassessed_at": "2026-06-12 09:30"
+ }
},
{
"code": "300124",
"name": "汇川技术",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 64.34,
- "take_profit": 71.31,
- "entry_low": 65.0,
- "entry_high": 67.13,
- "action": null,
+ "price": 67.16,
"analysis": {
- "stop_loss": 64.34,
- "take_profit": 71.31,
- "entry_low": 65.0,
- "entry_high": 67.13,
- "action": "盈利持有 | 目标71.31 | 止损64.34 | 买入区65.0~67.13 | 信号:观望",
- "tech_snapshot": "形态:锤子线/T字线/neutral 量价:主动卖盘占优 强撑:60.69 弱撑:64.72 弱压:67.61 强压:71.31 | MA5=74.88 MA10=76.52 MA20=77.68 MA60=73.65",
- "multi_tf_context": "震荡/无明显方向 | MA20=77.68 | MA60=73.65 | 长撑:日弱支撑=63.13 | 长压:周强阻=83.55",
- "reassessed_at": "2026-07-01 09:01",
+ "stop_loss": 65.15,
+ "take_profit": 73.7,
+ "entry_low": 65.82,
+ "entry_high": 68.5,
+ "action": "盈利持有 | 损65.15 | 盈73.7 | 买65.82~68.5 | 信号:放量下跌,等企稳再入",
+ "tech_snapshot": "形态:带上影阴线/bearish 量价:主动卖盘占优 强撑:62.95 弱撑:65.99 弱压:69.49 强压:73.7",
+ "reassessed_at": "2026-06-12 16:00",
"status": "updated",
- "rr_ratio": 2.5,
+ "rr_ratio": 3.25,
"action_note": "",
- "timing_signal": "观望"
+ "timing_signal": "放量下跌,等企稳再入"
},
- "trigger": {
- "stop_loss": 64.34,
- "entry_zone": "65.0~67.13",
- "take_profit_zone": "0~71.31"
- },
- "currency": "CNY"
+ "strategy_action": "盈利持有",
+ "stop_loss": 67.5,
+ "entry_low": 68.5,
+ "entry_high": 72.0,
+ "take_profit": 78.0,
+ "current_signal": "关注"
},
{
"code": "01070",
"name": "TCL电子",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 12.5,
- "take_profit": 13.72,
- "entry_low": 12.63,
- "entry_high": 12.77,
- "action": null,
+ "price": 14.04,
"analysis": {
- "stop_loss": 12.5,
- "take_profit": 13.72,
- "entry_low": 12.63,
- "entry_high": 12.77,
- "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标13.72 | 止损12.5 | 买入区12.63~12.77",
- "tech_snapshot": "形态:长影星线/neutral 量价:数据不足 强撑:12.08 弱撑:12.58 弱压:13.21 强压:13.72 | MA5=12.84 MA10=13.3 MA20=13.67 MA60=14.33",
- "multi_tf_context": "多周期分化 | MA20=13.67 | MA60=14.33 | 长撑:日弱支撑=12.41 | 长压:周强阻=16.19",
- "reassessed_at": "2026-07-01 09:00",
- "status": "review",
- "rr_ratio": 1.09,
- "action_note": "⚠️盈亏比不足1:1.5,不建议买入",
- "timing_signal": "信号不充分"
- },
- "trigger": {
- "stop_loss": 12.5,
- "entry_zone": "12.63~12.77",
- "take_profit_zone": "0~13.72"
- },
- "currency": "CNY"
+ "stop_loss": 13.59,
+ "take_profit": 15.3,
+ "entry_low": 13.73,
+ "entry_high": 14.27,
+ "action": "盈利持有 | 损13.59 | 盈15.3 | 买13.73~14.27 | 信号:接近支撑位,关注",
+ "tech_snapshot": "形态:倒T线/射击之星/bearish 量价:数据不足 强撑:13.06 弱撑:13.75 弱压:14.42 强压:15.3",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "updated",
+ "rr_ratio": 3.07,
+ "action_note": "",
+ "timing_signal": "接近支撑位,关注"
+ }
},
{
"code": "09988",
"name": "阿里巴巴-W",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 89.74,
- "take_profit": 100.64,
- "entry_low": 90.99,
- "entry_high": 93.04,
- "action": null,
+ "price": 110.2,
"analysis": {
- "stop_loss": 89.74,
- "take_profit": 100.64,
- "entry_low": 90.99,
- "entry_high": 93.04,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:1.6),谨慎买入 | 目标100.64 | 止损89.74 | 买入区90.99~93.04 | 信号:弱势持有",
- "tech_snapshot": "形态:锤子线/T字线/bullish 量价:数据不足 强撑:84.76 弱撑:91.45 弱压:94.1 强压:100.64 | MA5=148.26 MA10=151.36 MA20=158.48 MA60=147.61",
- "multi_tf_context": "震荡/无明显方向 | MA20=158.48 | MA60=147.61 | 长撑:日强支撑=88.65 | 长压:月强阻=174.2",
- "reassessed_at": "2026-07-01 09:01",
- "status": "updated",
- "rr_ratio": 1.6,
- "action_note": "⚠️盈亏比偏低(1:1.6),谨慎买入",
- "timing_signal": "弱势持有"
+ "buy_low": 107.4,
+ "buy_high": 111.4,
+ "position_recommend": null,
+ "reason": "大涨+5.86%可关注",
+ "updated_at": "2026-06-02T11:01:41.539017",
+ "tech_levels": {
+ "strong_support": 102.72,
+ "weak_support": 107.4,
+ "weak_resist": 111.4,
+ "strong_resist": 117.48
+ },
+ "stop_loss": 99.18,
+ "take_profit": 123.97,
+ "entry_low": 94.86,
+ "entry_high": 107.8,
+ "action": "盈利持有,止损99.18 目标123.97",
+ "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
+ "reassessed_at": "2026-06-12 09:30"
+ }
+ },
+ {
+ "code": "00700",
+ "name": "腾讯控股",
+ "price": 463.6,
+ "analysis": {
+ "buy_low": 457.2,
+ "buy_high": 467.93,
+ "position_recommend": null,
+ "reason": "暴涨+7.75%逼近止盈470",
+ "updated_at": "2026-06-02T11:01:41.539014",
+ "tech_levels": {
+ "strong_support": 440.4,
+ "weak_support": 457.2,
+ "weak_resist": 467.93,
+ "strong_resist": 486.94
+ },
+ "stop_loss": 432.58,
+ "take_profit": 540.73,
+ "entry_low": 413.78,
+ "entry_high": 470.2,
+ "action": "盈利持有,止损432.58 目标540.73",
+ "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
+ "reassessed_at": "2026-06-12 09:30"
+ }
+ },
+ {
+ "code": "02202",
+ "name": "万科企业",
+ "price": 2.64,
+ "analysis": {
+ "buy_low": 2.62,
+ "buy_high": 2.69,
+ "position_recommend": "",
+ "reason": "万科企业2.83微涨+1.8%,深套持有",
+ "updated_at": "2026-06-02T10:46:11.973869",
+ "tech_levels": {
+ "strong_support": 2.47,
+ "weak_support": 2.62,
+ "weak_resist": 2.69,
+ "strong_resist": 2.83
+ },
+ "stop_loss": 2.42,
+ "take_profit": 3.02,
+ "entry_low": 2.31,
+ "entry_high": 2.63,
+ "action": "盈利持有,止损2.42 目标3.02",
+ "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
+ "reassessed_at": "2026-06-12 09:30"
+ }
+ },
+ {
+ "code": "688981",
+ "name": "中芯国际",
+ "price": 124.88,
+ "analysis": {
+ "buy_low": 124.58,
+ "buy_high": 132.34,
+ "position_recommend": "0%",
+ "reason": "浮盈+0.0%,止损112.53 目标137.0",
+ "updated_at": "2026-06-09T10:46:18",
+ "tech_levels": {
+ "strong_support": 121.97,
+ "weak_support": 124.58,
+ "weak_resist": 132.34,
+ "strong_resist": 137.49
+ },
+ "stop_loss": 121.46,
+ "take_profit": 151.82,
+ "entry_low": 127.4,
+ "entry_high": 132.02,
+ "action": "盈利持有,止损121.46 目标151.82",
+ "tech_snapshot": "形态:倒锤头/bullish 量价:买卖均衡 强撑:130.86 弱撑:127.4 弱压:132.8 强压:133.58",
+ "reassessed_at": "2026-06-12 09:30"
},
- "trigger": {
- "stop_loss": 89.74,
- "entry_zone": "90.99~93.04",
- "take_profit_zone": "0~100.64"
- },
- "currency": "CNY"
+ "strategy_action": "盈利持有",
+ "stop_loss": 132.0,
+ "entry_low": 135.0,
+ "entry_high": 142.0,
+ "take_profit": 155.0,
+ "current_signal": "关注"
+ },
+ {
+ "code": "01478",
+ "name": "丘钛科技",
+ "price": 8.13,
+ "analysis": {
+ "buy_low": 7.98,
+ "buy_high": 8.51,
+ "position_recommend": "",
+ "reason": "丘钛科技10.07跌-1.66%,深套-26.48%暂持",
+ "updated_at": "2026-06-02T10:46:11.973872",
+ "tech_levels": {
+ "strong_support": 7.92,
+ "weak_support": 7.98,
+ "weak_resist": 8.51,
+ "strong_resist": 8.74
+ },
+ "stop_loss": 7.47,
+ "take_profit": 9.34,
+ "entry_low": 7.15,
+ "entry_high": 8.12,
+ "action": "盈利持有,止损7.47 目标9.34",
+ "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None",
+ "reassessed_at": "2026-06-12 09:30"
+ }
},
{
"code": "001309",
"name": "德明利",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 810.0,
- "take_profit": 1153.26,
- "entry_low": 873.18,
- "entry_high": 908.82,
- "action": null,
+ "price": 617.43,
"analysis": {
- "stop_loss": 810.0,
- "take_profit": 1153.26,
- "entry_low": 873.18,
- "entry_high": 908.82,
- "action": "盈利持有 | 止损参考810.0 | 买入区873.18~908.82",
- "tech_snapshot": "形态:锤子线/T字线/neutral 量价:主动买盘占优 强撑:873.0 弱撑:907.94 弱压:970.0 强压:1040.65 | MA5=912.98 MA10=813.43 MA20=720.19 MA60=612.25",
- "multi_tf_context": "多周期看多 | MA20=720.19 | MA60=612.25 | 长撑:MA20=720.19 | 长压:日强阻=980.0",
- "reassessed_at": "2026-07-01 09:00",
- "status": "manual",
- "rr_ratio": 1.58,
+ "stop_loss": 598.91,
+ "take_profit": 682.35,
+ "entry_low": 605.08,
+ "entry_high": 629.78,
+ "action": "盈利持有 | 损598.91 | 盈682.35 | 买605.08~629.78",
+ "tech_snapshot": "形态:光头光脚阴线/neutral 量价:主动买盘占优 强撑:583.56 弱撑:601.91 弱压:648.48 强压:682.35",
+ "reassessed_at": "2026-06-12 16:00",
+ "status": "updated",
+ "rr_ratio": 3.51,
"action_note": "",
"timing_signal": "neutral"
},
- "trigger": {
- "stop_loss": 810.0,
- "entry_zone": "873.18~908.82",
- "take_profit_zone": "0~1153.26"
- },
- "currency": "CNY"
+ "strategy_action": "盈利持有",
+ "stop_loss": 795.0,
+ "entry_low": 820.0,
+ "entry_high": 870.0,
+ "take_profit": 950.0,
+ "current_signal": "关注"
},
{
"code": "06160",
"name": "百济神州",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 164.16,
- "take_profit": 183.83,
- "entry_low": 166.99,
- "entry_high": 172.03,
- "action": null,
+ "price": 158.9,
+ "prev_close": 163.1,
+ "market": "HK",
"analysis": {
- "stop_loss": 164.16,
- "take_profit": 183.83,
- "entry_low": 166.99,
- "entry_high": 172.03,
- "action": "盈利持有 | 目标183.83 | 止损164.16 | 买入区166.99~172.03",
- "tech_snapshot": "形态:带上影阴线/bearish 量价:数据不足 强撑:159.23 弱撑:166.87 弱压:175.07 强压:183.83 | MA5=189.84 MA10=195.82 MA20=199.25 MA60=193.25",
- "multi_tf_context": "震荡/无明显方向 | MA20=199.25 | MA60=193.25 | 长撑:日弱支撑=154.5 | 长压:月强阻=222.0",
- "reassessed_at": "2026-07-01 09:01",
+ "stop_loss": 152.78,
+ "take_profit": 170.7,
+ "entry_low": 154.35,
+ "entry_high": 159.95,
+ "action": "盈利持有 | 损152.78 | 盈170.7 | 买154.35~159.95 | 信号:接近支撑位,关注",
+ "tech_snapshot": "形态:锤子线/T字线/neutral 量价:数据不足 强撑:143.3 弱撑:155.0 弱压:159.5 强压:170.7",
+ "reassessed_at": "2026-06-12 16:00",
"status": "updated",
- "rr_ratio": 2.15,
+ "rr_ratio": 2.8,
"action_note": "",
- "timing_signal": "信号不充分"
- },
- "trigger": {
- "stop_loss": 164.16,
- "entry_zone": "166.99~172.03",
- "take_profit_zone": "0~183.83"
- },
- "currency": "CNY"
+ "timing_signal": "接近支撑位,关注"
+ }
},
{
- "code": "000711",
- "name": "ST京蓝",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 4.25,
- "take_profit": 4.99,
- "entry_low": 4.45,
- "entry_high": 4.55,
- "action": null,
+ "code": "688411",
+ "name": "海博思创",
+ "price": 264.66,
"analysis": {
- "stop_loss": 4.25,
- "take_profit": 4.99,
- "entry_low": 4.45,
- "entry_high": 4.55,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:1.6),谨慎买入 | 目标4.99 | 止损4.25 | 买入区4.45~4.55 | 信号:观望",
- "tech_snapshot": "形态:光头光脚阳线/neutral 量价:主动卖盘占优 强撑:4.1 弱撑:4.25 弱压:4.69 强压:4.99 | MA5=4.65 MA10=5.0 MA20=5.38 MA60=5.11",
- "multi_tf_context": "震荡/无明显方向 | MA20=5.38 | MA60=5.11 | 长撑:日弱支撑=4.32 | 长压:日弱阻=6.28",
- "reassessed_at": "2026-07-01 09:00",
+ "stop_loss": 239.18,
+ "take_profit": 264.86,
+ "entry_low": 241.3,
+ "entry_high": 259.89,
+ "action": "盈利持有 | 止损239.18 | 目标264.86 | 买入区241.3~259.89",
+ "tech_snapshot": "形态:光头光脚阳线/bullish 量价:买卖均衡 强撑:239.18 弱撑:241.3 弱压:259.89 强压:264.86",
+ "reassessed_at": "2026-06-12 10:02",
"status": "updated",
- "rr_ratio": 1.55,
- "action_note": "⚠️盈亏比偏低(1:1.6),谨慎买入",
- "timing_signal": "观望"
- },
- "trigger": {
- "stop_loss": 4.25,
- "entry_zone": "4.45~4.55",
- "take_profit_zone": "0~4.99"
- },
- "currency": "CNY"
+ "buy_low": 241.3,
+ "buy_high": 277.96,
+ "tech_levels": {
+ "strong_support": 235.42,
+ "weak_support": 241.3,
+ "weak_resist": 277.96,
+ "strong_resist": 286.68
+ }
+ }
},
{
- "code": "688630",
- "name": "芯碁微装",
- "added_at": "2026-06-21 02:51:30",
- "stop_loss": 532.91,
- "take_profit": 595.43,
- "entry_low": 538.4,
- "entry_high": 554.48,
- "action": null,
+ "code": "000700",
+ "name": "模塑科技",
+ "price": 14.4,
"analysis": {
- "stop_loss": 532.91,
- "take_profit": 595.43,
- "entry_low": 538.4,
- "entry_high": 554.48,
- "action": "盈利持有 | 目标595.43 | 止损532.91 | 买入区538.4~554.48",
- "tech_snapshot": "形态:锤子线/T字线/neutral 量价:主动买盘占优 强撑:486.75 弱撑:527.18 弱压:563.3 强压:595.43 | MA5=538.96 MA10=508.73 MA20=449.83 MA60=323.87",
- "multi_tf_context": "多周期看多 | MA20=449.83 | MA60=323.87 | 长撑:MA20=449.83 | 长压:日强阻=563.89",
- "reassessed_at": "2026-07-01 09:01",
+ "stop_loss": 13.97,
+ "take_profit": 15.27,
+ "entry_low": 14.11,
+ "entry_high": 14.49,
+ "action": "盈利持有 | 损13.97 | 盈15.27 | 买14.11~14.49",
+ "tech_snapshot": "形态:光头光脚阴线/bearish 量价:主动买盘占优 强撑:13.75 弱撑:14.08 弱压:14.84 强压:15.27",
+ "reassessed_at": "2026-06-12 16:00",
"status": "updated",
- "rr_ratio": 2.07,
+ "rr_ratio": 2.02,
"action_note": "",
- "timing_signal": "信号不充分"
- },
- "trigger": {
- "stop_loss": 532.91,
- "entry_zone": "538.4~554.48",
- "take_profit_zone": "0~595.43"
- },
- "currency": "CNY"
+ "timing_signal": "neutral"
+ }
},
{
- "code": "000657",
- "name": "中钨高新",
- "added_at": "2026-06-20T15:44:00",
- "stop_loss": 93.06,
- "take_profit": 105.13,
- "entry_low": 93.81,
- "entry_high": 97.63,
- "action": null,
- "analysis": {
- "stop_loss": 93.06,
- "take_profit": 105.13,
- "entry_low": 93.81,
- "entry_high": 97.63,
- "action": "盈利持有 | 目标105.13 | 止损93.06 | 买入区93.81~97.63",
- "tech_snapshot": "形态:带上影阳线/neutral 量价:主动买盘占优 强撑:86.01 弱撑:93.06 弱压:97.84 强压:105.13 | MA5=99.31 MA10=98.79 MA20=86.3 MA60=66.5",
- "multi_tf_context": "多周期看多 | MA20=86.3 | MA60=66.5 | 长撑:MA20=86.3 | 长压:日强阻=113.99",
- "reassessed_at": "2026-07-01 09:00",
- "status": "updated",
- "rr_ratio": 3.54,
- "action_note": "",
- "timing_signal": "信号不充分"
- },
- "trigger": {
- "stop_loss": 93.06,
- "entry_zone": "93.81~97.63",
- "take_profit_zone": "0~105.13"
- },
- "currency": "CNY"
+ "code": "301308",
+ "name": "江波龙",
+ "added_at": "2026-07-05",
+ "source": "position_analyst",
+ "currency": "CNY",
+ "strategy_action": "盈利持有",
+ "stop_loss": 590.45,
+ "buy_zone_low": 618.38,
+ "buy_zone_high": 630.83,
+ "take_profit": 691.4,
+ "sector": "半导体/存储芯片"
},
{
- "code": "600519",
- "name": "贵州茅台",
- "added_at": "2026-06-22 09:22:26",
- "stop_loss": 1149.93,
- "take_profit": 1249.71,
- "entry_low": 1161.78,
- "entry_high": 1189.84,
- "action": null,
+ "code": "000850",
+ "name": "华茂股份",
+ "price": 3.81,
"analysis": {
- "stop_loss": 1149.93,
- "take_profit": 1249.71,
- "entry_low": 1161.78,
- "entry_high": 1189.84,
- "action": "盈利持有 | ⚠️盈亏比偏低(1:1.8),谨慎买入 | 目标1249.71 | 止损1149.93 | 买入区1161.78~1189.84 | 信号:关注",
- "tech_snapshot": "形态:长影星线/neutral 量价:主动买盘占优 强撑:1121.73 弱撑:1175.77 弱压:1195.44 强压:1249.71 | MA5=1373.78 MA10=1383.48 MA20=1362.71 MA60=1396.98",
- "multi_tf_context": "多周期看多 | MA20=1362.71 | MA60=1396.98 | 长撑:日强支撑=1151.01 | 长压:月强阻=1539.98",
- "reassessed_at": "2026-07-01 09:01",
- "status": "updated",
- "rr_ratio": 1.81,
- "action_note": "⚠️盈亏比偏低(1:1.8),谨慎买入",
- "timing_signal": "关注"
+ "buy_low": 3.7,
+ "buy_high": 3.9,
+ "position_recommend": "2%",
+ "reason": "纺织是壳核心是金融资产, PB0.78破净提供安全垫, 7/16股东会催化",
+ "updated_at": "2026-07-06",
+ "tech_levels": {
+ "strong_support": 3.57,
+ "weak_support": 3.8,
+ "weak_resist": 4.04,
+ "strong_resist": 4.16
+ },
+ "stop_loss": 3.5,
+ "take_profit": 4.3,
+ "entry_low": 3.7,
+ "entry_high": 3.9,
+ "action": "3.70~3.90区间可建仓1~2%",
+ "tech_snapshot": "形态:弱势回调 量价:利好出尽缩量回调 强撑:3.57 弱撑:3.80 弱压:4.04 强压:4.16",
+ "reassessed_at": "2026-07-07"
},
- "trigger": {
- "stop_loss": 1149.93,
- "entry_zone": "1161.78~1189.84",
- "take_profit_zone": "0~1249.71"
- },
- "currency": "CNY"
- },
- {
- "code": "601318",
- "name": "中国平安",
- "added_at": "2026-06-22 11:48:54",
- "stop_loss": 46.31,
- "take_profit": 50.6,
- "entry_low": 46.79,
- "entry_high": 48.03,
- "action": null,
- "analysis": {
- "stop_loss": 46.31,
- "take_profit": 50.6,
- "entry_low": 46.79,
- "entry_high": 48.03,
- "action": "盈利持有 | 目标50.6 | 止损46.31 | 买入区46.79~48.03 | 信号:观望",
- "tech_snapshot": "形态:带上影阴线/bearish 量价:主动卖盘占优 强撑:45.42 弱撑:47.08 弱压:48.67 强压:50.6 | MA5=68.37 MA10=68.78 MA20=66.54 MA60=62.97",
- "multi_tf_context": "多周期看多 | MA20=66.54 | MA60=62.97 | 长撑:日弱支撑=47.2 | 长压:周强阻=59.36",
- "reassessed_at": "2026-07-01 09:01",
- "status": "updated",
- "rr_ratio": 2.0,
- "action_note": "",
- "timing_signal": "观望"
- },
- "trigger": {
- "stop_loss": 46.31,
- "entry_zone": "46.79~48.03",
- "take_profit_zone": "0~50.6"
- },
- "currency": "CNY"
+ "sector": "纺织/金融投资"
}
- ]
+ ],
+ "updated_at": "2026-07-06"
}
\ No newline at end of file
diff --git a/data/xiaoguo_insights.json b/data/xiaoguo_insights.json
index fcfa633f..0211ee4b 100644
--- a/data/xiaoguo_insights.json
+++ b/data/xiaoguo_insights.json
@@ -1,62 +1,54 @@
{
- "timestamp": "2026-07-06 16:10",
+ "timestamp": "2026-07-07 16:14",
"source": "xiaoguo_fallback",
"stocks": [
{
"code": "300308",
"name": "中际旭创",
"sentiment": "positive",
- "confidence": 0.70,
- "reason": "公司今日在互动易澄清两大市场传闻:康宁玻璃桥技术非光模块替代方案、上游炫光片封锁传言不实。主动辟谣有助于缓解FUD,稳定股价",
- "priority": "距止损2.49%"
- },
- {
- "code": "300035",
- "name": "中科电气",
- "sentiment": "neutral",
- "confidence": 0.50,
- "reason": "今日无公司重大新闻,仅有主力资金流向统计提及。消息面平淡,股价-1.82%正常波动",
- "priority": "距止损3.70%"
- },
- {
- "code": "01088",
- "name": "中国神华",
- "sentiment": "positive",
- "confidence": 0.65,
- "reason": "7月2日公告定州三期、沧东三期5号机组正式投运,产能扩张利好。南向资金持续大比例持有。今日+1.65%,走势稳健",
- "priority": "距止损4.69%"
+ "confidence": 0.72,
+ "reason": "今日成交额超300亿元,市场交投活跃。公司主动辟谣物料封锁传言,澄清康宁玻璃桥技术非光模块替代方案。利好消除不确定性,尾盘+2.09%走强",
+ "priority": "距止损2.8%"
},
{
"code": "688981",
"name": "中芯国际",
"sentiment": "neutral",
- "confidence": 0.55,
- "reason": "今日无公司层面新消息(最新公开新闻为6月底)。但当日涨幅+5.2%创阶段新高,半导体板块情绪偏暖。机构持仓稳定",
- "priority": "距止损4.72%"
+ "confidence": 0.58,
+ "reason": "消息面多空交织:牵头半导体企业获超10亿元股权融资(利好),但中芯国际概念板块近期下跌5.66%。今日股价+0.99%企稳,距止损仅3.1%需关注",
+ "priority": "距止损3.1%"
},
{
"code": "000700",
"name": "模塑科技",
"sentiment": "negative",
- "confidence": 0.60,
- "reason": "7月2日公司公告明确机器人外覆盖件订单占比不足0.1%,主动降温机器人炒作预期。前期涨幅过大,当前买入区需警惕回调风险",
- "priority": "买入区15.5~18.08"
+ "confidence": 0.65,
+ "reason": "公司公告机器人外覆盖件订单金额占比不足0.1%,主动降温概念炒作预期。股价已跌破止损16.70元至16.15元,距止损3.4%需警惕进一步下行",
+ "priority": "距止损3.4%"
+ },
+ {
+ "code": "300548",
+ "name": "长芯博创",
+ "sentiment": "positive",
+ "confidence": 0.68,
+ "reason": "发布2025年度权益分派方案(每10股派2.90元),股息回报利好。此前收购子公司少数股权完成,基本面改善。股价-1.52%属正常回调,距止损5.0%",
+ "priority": "距止损5.0%"
},
{
"code": "01888",
"name": "建滔积层板",
"sentiment": "negative",
- "confidence": 0.85,
- "reason": "今日港股PCB概念大幅波动,建滔积层板从早盘+5.39%急转暴跌至-12.37%,日振幅超17%。南向资金此前持续加仓,但今日出现恐慌性抛售",
- "priority": "买入区67.27~78.48"
+ "confidence": 0.70,
+ "reason": "今日PCB概念反弹+公司再发涨价函,但昨日暴跌超10%后今日仍跌-5.48%。南向资金此前持续加仓但近期板块恐慌性抛售明显,短期压力未释放完毕",
+ "priority": "涨跌幅-5.48%"
},
{
- "code": "300548",
- "name": "博创科技",
- "sentiment": "neutral",
- "confidence": 0.50,
- "reason": "今日无公司重大新闻。通信行业整体偏弱(行业-7.36%),F5G概念下跌1.86%,板块环境不佳但公司层面无利空",
- "priority": "买入区205.41~239.64"
+ "code": "06869",
+ "name": "长飞光纤光缆",
+ "sentiment": "negative",
+ "confidence": 0.75,
+ "reason": "港股算力硬件股持续暴跌,今日再度走低跌超9%。公司已连续多日大幅下挫(昨日-15%,今日-7.70%),距止损195.46已远跌至157.8,属系统性杀跌需警惕",
+ "priority": "涨跌幅-7.70%"
}
]
}
diff --git a/docs/analyst-knowledge-log.md b/docs/analyst-knowledge-log.md
index 468fd8c8..e3bc33d7 100644
--- a/docs/analyst-knowledge-log.md
+++ b/docs/analyst-knowledge-log.md
@@ -1,21 +1,9 @@
+# analyst-knowledge-log.md
-## 2026-07-06 DB锁+市值错误修复
+## [2026-07-08 10:40] 自愈执行器升级处理:KOSDAQ-4.1% HIGH信号
-### 发现的问题
-1. **市值错误(586,191 vs 实际619,880+)** — `mo_data.read_portfolio()` 从 portfolio_summary 表读取 total_mv,该字段可能未及时更新(holdings变化后summary未同步),导致报告输出过时市值。
-
-2. **DB并发锁** — mo_data.py 的 _get_db() 和多个脚本使用 bare sqlite3.connect(),无 timeout/WAL/busy_timeout。多个 cron 并发写入时触发 "database is locked" 错误。
-
-### 修改内容
-
-**mo_data.py:**
-- _get_db(): sqlite3.connect 加 timeout=15, PRAGMA journal_mode=WAL, PRAGMA busy_timeout=15000
-- read_portfolio(): 从 holdings 实时计算 total_mv (shares×price,港股×汇率),不信任 summary 存储值
-
-**stale_push_wlin.py (2处), multi_timeframe.py (2处), strategy_lifecycle.py (3处), strategy_tree.py (1处):**
-- 替换 bare sqlite3.connect() 为 mofin_db.get_conn()(已有 WAL+timeout+退避重试)
-
-### 效果预期
-- 市值永远从 holdings 实时计算,不再出现 stale summary 导致的错报
-- DB "database is locked" 错误消除(WAL + timeout=15 + busy_timeout=15000)
-- 所有高频脚本统一走 get_conn(),连接参数一致
+- **发现**: 自愈执行器上报盘中自检发现宏观风险HIGH(KOSDAQ-4.1% + 中信证券研报)
+- **判断**: 系统无故障,已知市场条件延续。KOSDAQ-4.1%是昨日KOSPI暴跌的延续,非新事件。中信证券研报是采集器误报(正常行业研报)。
+- **数据验证**: 中国各指数全面脱钩改善(上证-1.26%→+0.02%,恒生-0.51%→+1.69%,科创50+0.28%→+0.62%)。持仓止损距均>3%。
+- **操作**: 标记已知状态,无需新推送。08:30 LLM深度分析已覆盖并修正。
+- **涉及文件**: macro_risk_state.json(已由consumer更新为processed)
diff --git a/mo_alphasift_bridge.py b/mo_alphasift_bridge.py
index 75c321b5..fe4989bf 100644
--- a/mo_alphasift_bridge.py
+++ b/mo_alphasift_bridge.py
@@ -18,8 +18,8 @@ from pathlib import Path
DSA_API = "http://127.0.0.1:8001"
MOFIN_DATA = Path("/home/hmo/web-dashboard/data")
-WATCHLIST_PATH = MOFIN_DATA / "watchlist.json"
-PORTFOLIO_PATH = MOFIN_DATA / "portfolio.json"
+# 已迁移到 DB — watchlist.json / portfolio.json 不再使用
+# 保留路径仅用于兼容 import,实际数据从 mofin.db 读取
DEFAULT_STRATEGIES = "balanced_alpha,dual_low,quality_value"
DEFAULT_MARKET = "cn"
@@ -54,14 +54,18 @@ def api(endpoint, method="GET", body=None):
return None
def get_existing_codes():
+ """从 DB 读取已有持仓+自选。JSON 已废弃。"""
codes = set()
- for path in [WATCHLIST_PATH, PORTFOLIO_PATH]:
- data = load_json(path)
- if not data: continue
- key = "stocks" if "watchlist" in str(path) else "holdings"
- for item in data.get(key, []):
- c = str(item.get("code", "")).strip()
- if c: codes.add(c)
+ try:
+ import sqlite3
+ db = sqlite3.connect(str(MOFIN_DATA / "mofin.db"))
+ for row in db.execute("SELECT code FROM watchlist_stocks WHERE is_active=1"):
+ codes.add(str(row[0]).strip())
+ for row in db.execute("SELECT code FROM holdings"):
+ codes.add(str(row[0]).strip())
+ db.close()
+ except Exception as e:
+ print(f"WARN: DB读取失败: {e}")
return codes
@@ -205,12 +209,19 @@ def run_all(strategies_str, market, max_results, dry_run=False):
print("\n[DRY RUN] 未写入")
return
- # 写入
- wl = load_json(WATCHLIST_PATH) or {"stocks": []}
- wl["stocks"].extend(new_stocks)
- wl["updated_at"] = time_str
- save_json(WATCHLIST_PATH, wl)
- print(f"\n已写入 {WATCHLIST_PATH}")
+ # 写入 DB
+ try:
+ sys.path.insert(0, str(MOFIN_DATA.parent))
+ from mofin_db import get_conn, write_watchlist_stock
+ conn = get_conn()
+ for s in new_stocks:
+ s.setdefault('currency', 'CNY')
+ write_watchlist_stock(conn, s)
+ conn.close()
+ print(f"\n已写入 {len(new_stocks)} 只到 DB watchlist_stocks")
+ except Exception as e:
+ print(f"WARN: DB写入失败: {e}")
+ return
# 策略生成
print("\n调用 regenerate_all()...")
diff --git a/mo_config.py b/mo_config.py
index 1199092c..ca825d21 100644
--- a/mo_config.py
+++ b/mo_config.py
@@ -10,7 +10,7 @@ mo_config.py — MoFin 统一配置管理(单例模式)
用法:
from mo_config import config
- portfolio_path = config.data_dir / "portfolio.json"
+ config.data_dir / "somedata.json"
"""
import os
@@ -28,7 +28,7 @@ class MoConfig:
# 项目根目录
project_dir: Path = field(default_factory=lambda: Path(__file__).parent.resolve())
- # 数据目录(portfolio.json, decisions.json 等)
+ # 数据目录(SQLite 为主)
data_dir: Path = field(default_factory=lambda: Path(
os.environ.get("MOFIN_DATA_DIR", "/home/hmo/web-dashboard/data")
))
@@ -42,22 +42,26 @@ class MoConfig:
# Hermes 状态目录
hermes_dir: Path = field(default_factory=lambda: Path.home() / ".hermes")
- # ── 关键数据文件路径 ──────────────────────────────────────────
-
+ # ── 关键数据文件路径(已废弃,仅保留为检查逻辑。新代码勿用) ──────
+
@property
def portfolio_path(self) -> Path:
+ """⚠️ 已废弃!数据在 mofin.db holdings + portfolio_summary 表。"""
return self.data_dir / "portfolio.json"
-
+
@property
def decisions_path(self) -> Path:
+ """⚠️ 已废弃!数据在 mofin.db holding_strategies 表。"""
return self.data_dir / "decisions.json"
-
+
@property
def watchlist_path(self) -> Path:
+ """⚠️ 已废弃!数据在 mofin.db watchlist_stocks 表。"""
return self.data_dir / "watchlist.json"
-
+
@property
def price_events_path(self) -> Path:
+ """⚠️ 已废弃!数据在 mofin.db price_events 表。"""
return self.data_dir / "price_events.json"
@property
@@ -144,11 +148,9 @@ class MoConfig:
if not self.data_dir.exists():
issues.append(f"数据目录不存在: {self.data_dir}")
- if not self.portfolio_path.exists():
- issues.append(f"portfolio.json 不存在: {self.portfolio_path}")
-
- if not self.decisions_path.exists():
- issues.append(f"decisions.json 不存在: {self.decisions_path}")
+ # 检查 DB 文件(数据源)
+ if not self._get_db_path().exists():
+ issues.append(f"mofin.db 数据库不存在: {self._get_db_path()}")
return issues
diff --git a/mofin_db.py b/mofin_db.py
index ca765a4c..9fd53cc9 100644
--- a/mofin_db.py
+++ b/mofin_db.py
@@ -1097,7 +1097,7 @@ def write_holding_strategy(conn, code: str, name: str, data: dict) -> tuple[bool
signal_factors_json, time_horizon, decision_type)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
datetime('now','localtime'),
- ?,?,?,?,?,?,?,?,?)
+ ?,?,?,?,?,?,?,?,?,?)
""", (
code, name,
data.get('version', 1), data.get('price'), data.get('cost'),
@@ -1187,7 +1187,7 @@ def write_portfolio_summary(conn, data: dict) -> tuple[bool, str]:
def write_watchlist_stock(conn, stock: dict) -> tuple[bool, str]:
- """写入自选股(替代 watchlist.json)"""
+ """写入自选股(写入 watchlist_stocks 表)"""
try:
conn.execute("""
INSERT INTO watchlist_stocks (code, name, price, entry_low, entry_high,
diff --git a/price_monitor.py b/price_monitor.py
index 017a4e15..bd5eb615 100644
--- a/price_monitor.py
+++ b/price_monitor.py
@@ -11,6 +11,8 @@ import time
import sqlite3
from datetime import datetime
+# ⚠️ 以下常量已废弃:数据在 mofin.db 的 holding_strategies / holdings / watchlist_stocks 表
+# 保留仅防止 import 报错,新代码勿用
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
WATCHLIST_PATH = "/home/hmo/web-dashboard/data/watchlist.json"
@@ -23,6 +25,7 @@ sys.path.insert(0, "/home/hmo/MoFin")
try:
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_live_prices
from mo_models import calc_total_mv, calc_total_assets
+ from mo_data import read_decisions
HAS_DB = True
except ImportError:
HAS_DB = False
@@ -307,12 +310,11 @@ def run_once(round_label=""):
# === 第一步:一次性刷新所有价格 ===
refreshed = refresh_data_prices()
- # === 第二步:检查触发条件 ===
+ # === 第二步:检查触发条件(纯DB,不读JSON) ===
try:
- with open(DECISIONS_PATH) as f:
- dec = json.load(f)
- except:
- print(f"❌{label} 无法读取decisions.json", file=sys.stderr)
+ dec = read_decisions()
+ except Exception as e:
+ print(f"❌{label} 无法从DB读取决策数据: {e}", file=sys.stderr)
return
active = [d for d in dec.get("decisions", []) if d.get("status") == "active"]
@@ -393,7 +395,7 @@ def run_once(round_label=""):
if price == 0:
continue
- # 从 decisions.json 中读取 analysis 的买入区
+ # 从 DB holding_strategies 读取买入区
entry_low = d.get("entry_low", 0)
entry_high = d.get("entry_high", 0)
if not entry_low or not entry_high:
diff --git a/scripts/clean_watchlist.py b/scripts/clean_watchlist.py
index be8d04e9..8d344cf6 100644
--- a/scripts/clean_watchlist.py
+++ b/scripts/clean_watchlist.py
@@ -9,8 +9,8 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mo_data import read_portfolio, read_decisions, read_watchlist
from mofin_db import get_conn, write_watchlist_stock, write_holding_strategy
-WL = "/home/hmo/web-dashboard/data/watchlist.json"
-DEC = "/home/hmo/web-dashboard/data/decisions.json"
+WL = "/home/hmo/web-dashboard/data/watchlist.json" # 路径保留用于历史备份兼容,数据实际走DB
+DEC = "/home/hmo/web-dashboard/data/decisions.json" # 同上
holding_codes = set()
pf = read_portfolio()
@@ -33,8 +33,7 @@ removed = [s for s in stocks if s.get("code") in holding_codes]
after = len(new_stocks)
wl["stocks"] = new_stocks
-# Backup
-os.rename(WL, WL + ".bak2")
+# Backup — DB 版,不再碰JSON文件
# DB 写入
conn = get_conn()
for s in wl.get("stocks", []):
@@ -61,8 +60,7 @@ for d in dec.get("decisions", []):
dec_changed += 1
if dec_changed:
- os.rename(DEC, DEC + ".bak3")
- # DB 写入
+ # DB 写入(不再碰JSON文件)
conn = get_conn()
for d in dec.get("decisions", []):
write_holding_strategy(conn, d.get("code", ""), d.get("name", ""), d)
diff --git a/scripts/data/format_error_library.json b/scripts/data/format_error_library.json
new file mode 100644
index 00000000..5ce275e1
--- /dev/null
+++ b/scripts/data/format_error_library.json
@@ -0,0 +1,35 @@
+{
+ "library_version": "1.0",
+ "total_records": 2,
+ "last_updated": "2026-07-08T01:14:29.611447",
+ "records": [
+ {
+ "task_id": "test-task",
+ "timestamp": "2026-07-08T01:14:29.582665",
+ "attempt": 1,
+ "score": 85,
+ "errors": [
+ "小问题"
+ ],
+ "is_failure": false,
+ "context": {
+ "code": "000001",
+ "name": "测试"
+ }
+ },
+ {
+ "task_id": "test-task",
+ "timestamp": "2026-07-08T01:14:29.611356",
+ "attempt": 2,
+ "score": 60,
+ "errors": [
+ "缺少字段: stop_loss",
+ "JSON语法错误"
+ ],
+ "is_failure": true,
+ "context": {
+ "code": "000001"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/scripts/data/mofin.db b/scripts/data/mofin.db
deleted file mode 120000
index a9de2003..00000000
--- a/scripts/data/mofin.db
+++ /dev/null
@@ -1 +0,0 @@
-/home/hmo/MoFin/data/mofin.db
\ No newline at end of file
diff --git a/scripts/fix_portfolio_prices.py b/scripts/fix_portfolio_prices.py
index 5c3e2c17..e175866f 100644
--- a/scripts/fix_portfolio_prices.py
+++ b/scripts/fix_portfolio_prices.py
@@ -1,84 +1,78 @@
#!/usr/bin/env python3
"""
-fix_portfolio_prices.py — 一次性修复脚本:从 decisions.json 读取 price 字段
-更新两个 canonical portfolio.json,并重算 total_assets/position_pct。
+fix_portfolio_prices.py — 一次性修复脚本:从 decisions(DB) 读取 price 字段
+更新 holdings 表,并重算 total_assets/position_pct。
背景:strategy_lifecycle.regenerate_all() 在旧版本中会
从 DB query_holdings()(不含 price/change_pct)覆盖写入
portfolio.json,导致 price_monitor 维护的实时价丢失。
该 bug 已在 strategy_lifecycle.py:1790 修复(保留 price 字段),
-此脚本用于修复已损坏的 portfolio.json。
+此脚本用于修复已损坏的 portfolio 数据。
用法:
- python3 fix_portfolio_prices.py # 修复两个 canonical 文件
+ python3 fix_portfolio_prices.py # 修复 DB
python3 fix_portfolio_prices.py --check # 只检查,不修改
"""
-import json
import sys
-from pathlib import Path
-# Canonical paths
-MOFIN_PORTFOLIO = Path("/home/hmo/MoFin/data/portfolio.json")
-DASHBOARD_PORTFOLIO = Path("/home/hmo/web-dashboard/data/portfolio.json")
-DECISIONS_PATH = Path("/home/hmo/web-dashboard/data/decisions.json")
+from mo_data import read_decisions, read_portfolio
+from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary
-def load_decisions():
- """从 decisions.json 读取 {code: {price, ...}} 映射"""
+def build_price_map():
+ """从 decisions(DB) 读取 {code: {price, ...}} 映射"""
try:
- with open(DECISIONS_PATH) as f:
- raw = json.load(f)
+ dec = read_decisions()
except Exception as e:
- print(f"❌ 无法读取 {DECISIONS_PATH}: {e}", file=sys.stderr)
+ print(f"❌ 无法读取 decisions(DB): {e}", file=sys.stderr)
return {}
-
+
price_map = {}
- for d in raw.get("decisions", []):
+ for d in dec.get("decisions", []):
code = d.get("code", "")
price = d.get("price", 0) or d.get("current_price", 0)
if code and price:
price_map[code] = float(price)
- print(f" decisions.json: {len(price_map)} 个股票的 price 已加载")
+ print(f" decisions(DB): {len(price_map)} 个股票的 price 已加载")
return price_map
-def fix_portfolio(path: Path, price_map: dict, check_only: bool):
- """修复一个 portfolio.json 文件"""
- if not path.exists():
- print(f"❌ {path} 不存在,跳过")
+def fix_portfolio(check_only: bool) -> bool:
+ """从 DB 读持仓,修复 price,写回 DB"""
+ try:
+ pf = read_portfolio()
+ except Exception as e:
+ print(f"❌ 无法读取 portfolio(DB): {e}", file=sys.stderr)
return False
- try:
- with open(path) as f:
- pf = json.load(f)
- except Exception as e:
- print(f"❌ 无法读取 {path}: {e}", file=sys.stderr)
+ holdings = pf.get("holdings", [])
+ price_map = build_price_map()
+ if not price_map:
+ print("❌ decisions 无有效 price 数据,退出")
return False
changes = 0
errors = 0
- holdings = pf.get("holdings", [])
-
+
for h in holdings:
code = h.get("code", "")
if not code:
continue
-
+
old_price = h.get("price", 0)
decision_price = price_map.get(code)
-
+
if decision_price and (not old_price or old_price == 0):
h["price"] = decision_price
changes += 1
- print(f" ✅ {code} {h.get('name','')}: price {old_price} → {decision_price}")
+ print(f" ✅ {code} {h.get('name','')}: price {old_price} \u2192 {decision_price}")
elif decision_price and old_price and abs(decision_price - old_price) / max(abs(old_price), 1) > 0.05:
- # Price differs >5% from decision - flag it but don't overwrite (price_monitor is authoritative)
print(f" ⚠️ {code} {h.get('name','')}: 当前价 {old_price} vs decisions {decision_price} (偏离>{5:.0f}%),保留当前价")
elif not decision_price:
errors += 1
if old_price == 0 or not old_price:
- print(f" ❌ {code} {h.get('name','')}: decisions.json 无此股 price 数据,当前 price={old_price}")
+ print(f" ❌ {code} {h.get('name','')}: decisions 无此股 price 数据,当前 price={old_price}")
# 重算 total_assets / position_pct
total_mv = 0
@@ -86,38 +80,56 @@ def fix_portfolio(path: Path, price_map: dict, check_only: bool):
price = h.get("price", 0) or 0
shares = h.get("shares", 0) or 0
total_mv += price * shares
-
+
cash = pf.get("cash", 0) or 0
- new_total_assets = round(total_mv + cash, 2)
+ frozen = pf.get("frozen_cash", 0) or 0
+ new_total_assets = round(total_mv + cash + frozen, 2)
new_position_pct = round(total_mv / new_total_assets * 100, 2) if new_total_assets > 0 else 0
-
+
old_total_assets = pf.get("total_assets", 0)
old_position_pct = pf.get("position_pct", 0)
-
+
if abs(new_total_assets - old_total_assets) > 100:
- print(f" 📊 total_assets: {old_total_assets} → {new_total_assets} (变动 {new_total_assets-old_total_assets:.0f})")
- pf["total_assets"] = new_total_assets
+ print(f" 📊 total_assets: {old_total_assets} \u2192 {new_total_assets} (变动 {new_total_assets-old_total_assets:.0f})")
changes += 1
-
+
if abs(new_position_pct - old_position_pct) > 0.5:
- print(f" 📊 position_pct: {old_position_pct}% → {new_position_pct}%")
- pf["position_pct"] = new_position_pct
+ print(f" 📊 position_pct: {old_position_pct}% \u2192 {new_position_pct}%")
changes += 1
-
- pf["updated_at"] = __import__("datetime").datetime.now().strftime('%Y-%m-%d %H:%M')
if changes == 0:
- print(f" ✅ {path.name}: 无需修复({len(holdings)} 个持仓价格正常)")
+ print(f" ✅ 无需修复({len(holdings)} 个持仓价格正常)")
return True
if check_only:
print(f" ⏸️ 检查模式: {changes} 处需修复,未写入")
return True
+ # 写入 DB
try:
- with open(path, "w") as f:
- json.dump(pf, f, indent=2, ensure_ascii=False)
- print(f" ✅ {path.name}: {changes} 处已修复,已写入")
+ conn = get_conn()
+ ok, msg = write_holdings_batch(conn, holdings)
+ if not ok:
+ print(f" ❌ 写入 holdings 失败: {msg}", file=sys.stderr)
+ conn.close()
+ return False
+
+ summary = {
+ "total_assets": new_total_assets,
+ "total_mv": round(total_mv, 2),
+ "stock_value": round(total_mv, 2),
+ "cash": cash,
+ "frozen_cash": frozen,
+ "position_pct": new_position_pct,
+ "currency": pf.get("currency", "CNY"),
+ }
+ ok, msg = write_portfolio_summary(conn, summary)
+ conn.close()
+ if not ok:
+ print(f" ❌ 写入 portfolio_summary 失败: {msg}", file=sys.stderr)
+ return False
+
+ print(f" ✅ DB: {changes} 处已修复,已写入")
return True
except Exception as e:
print(f" ❌ 写入失败: {e}", file=sys.stderr)
@@ -126,25 +138,16 @@ def fix_portfolio(path: Path, price_map: dict, check_only: bool):
def main():
check_only = "--check" in sys.argv
-
+
print("=== fix_portfolio_prices.py ===")
if check_only:
print("模式:检查(不写入)")
else:
print("模式:修复")
-
- price_map = load_decisions()
- if not price_map:
- print("❌ decisions.json 无有效 price 数据,退出")
- return 1
-
- print(f"\n--- {MOFIN_PORTFOLIO.name} ---")
- ok1 = fix_portfolio(MOFIN_PORTFOLIO, price_map, check_only)
-
- print(f"\n--- {DASHBOARD_PORTFOLIO.name} ---")
- ok2 = fix_portfolio(DASHBOARD_PORTFOLIO, price_map, check_only)
-
- if ok1 and ok2:
+
+ ok = fix_portfolio(check_only)
+
+ if ok:
print("\n✅ 全部完成")
return 0
else:
diff --git a/scripts/inspect_decisions.py b/scripts/inspect_decisions.py
index f69b979b..720f5be1 100644
--- a/scripts/inspect_decisions.py
+++ b/scripts/inspect_decisions.py
@@ -1,6 +1,5 @@
-import json
-with open('/home/hmo/web-dashboard/data/decisions.json') as f:
- d = json.load(f)
+from mo_data import read_decisions
+d = read_decisions()
for i, e in enumerate(d.get('decisions', [])[:3]):
print(f"\n=== Entry {i}: {e.get('code')} {e.get('name')} ===")
for k, v in sorted(e.items()):
diff --git a/scripts/intraday_health_check.py b/scripts/intraday_health_check.py
index 3d171f95..15800f3b 100644
--- a/scripts/intraday_health_check.py
+++ b/scripts/intraday_health_check.py
@@ -241,7 +241,13 @@ def main():
check_gateways()
check_xiaoguo()
if 9 <= now.hour < 16:
- check_price_monitor()
+ # 开盘前10分钟(9:00-9:10)跳过价格新鲜度检查
+ # price_monitor 从 09:00 才开始启动,09:01 检查时数据还未更新(前一天收盘数据)
+ # 给 price_monitor 足够时间完成第一轮数据拉取更新
+ if now.hour == 9 and now.minute < 10:
+ log(True, "开盘初期,价格监控grace period(跳过新鲜度检查)")
+ else:
+ check_price_monitor()
check_signal_pipeline()
write_todos()
diff --git a/scripts/json_validation.py b/scripts/json_validation.py
new file mode 100644
index 00000000..c86fa907
--- /dev/null
+++ b/scripts/json_validation.py
@@ -0,0 +1,870 @@
+#!/usr/bin/env python3
+"""
+json_validation.py — JSON 格式校验 + 打分-重试循环 + Format Error Library
+
+为知微股票分析 Pipeline 提供三层校验:
+ 1. JSON 语法解析 (json.loads)
+ 2. JSON Schema 字段匹配(可选)
+ 3. 语义完整性检查(关键字段非空、数值范围合理)
+
+核心功能:
+ - validate_json_syntax(raw) → (score, errors, parsed_or_None)
+ - scored_retry_loop(llm_fn, max_retries=3, threshold=90, schema=None)
+ - FormatErrorLibrary — 持久化错误记录,每日/周汇总
+ - best_effort_fallback(raw) → dict(尽力恢复)
+
+用法:
+ from json_validation import scored_retry_loop, FormatErrorLibrary
+
+ def my_llm_call(prompt):
+ # 调用 LLM 返回原始文本
+ return llm_response
+
+ result = scored_retry_loop(
+ llm_fn=my_llm_call,
+ prompt=initial_prompt,
+ context={"code": "000001", "name": "平安银行"},
+ schema=SCHEMA,
+ max_retries=3,
+ threshold=90,
+ )
+ # result = {"ok": True, "data": {...}, "attempts": 2, "score": 95} 或
+ # result = {"ok": False, "data": {...}, "attempts": 3, "score": 60, "fallback": True}
+"""
+
+import json
+import os
+import re
+import sys
+import time
+from datetime import datetime, timedelta
+from pathlib import Path
+from collections import defaultdict
+
+# ─── 常量 ───────────────────────────────────────────────────
+DEFAULT_THRESHOLD = 90 # 验收阈值(0-100)
+DEFAULT_MAX_RETRIES = 3 # 最大重试次数
+ERROR_LIBRARY_PATH = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ "data", "format_error_library.json"
+)
+
+# ─── 标准 JSON Schema 种子字段 ─────────────────────────────
+# 知微股票分析输出期望包含的字段
+STOCK_ANALYSIS_SCHEMA = {
+ "required": [
+ "code", "name", "action", "stop_loss", "take_profit",
+ "entry_low", "entry_high", "timing_signal", "rr_ratio",
+ ],
+ "optional": [
+ "price", "cost", "shares", "currency",
+ "tech_snapshot", "signal_factors", "sector_context",
+ "multi_tf_context", "status", "quality_check",
+ ],
+ "types": {
+ "code": str, "name": str, "action": str,
+ "stop_loss": (int, float), "take_profit": (int, float),
+ "entry_low": (int, float), "entry_high": (int, float),
+ "timing_signal": str, "rr_ratio": (int, float),
+ }
+}
+
+
+# ═══════════════════════════════════════════════════════════
+# 第一层:JSON 语法校验 + 打分
+# ═══════════════════════════════════════════════════════════
+
+def validate_json_syntax(raw_text):
+ """JSON 语法校验 + 打分(0-100)
+
+ 返回: (score, errors, parsed_or_None)
+ score: 0-100 整数
+ errors: 错误描述列表
+ parsed_or_None: 成功 parse 的 dict 或 None
+ """
+ errors = []
+
+ if not raw_text or not raw_text.strip():
+ return (0, ["空文本"], None)
+
+ text = raw_text.strip()
+
+ # 步骤1: 尝试提取可能的 JSON(从 markdown 代码块中)
+ extracted = _extract_json_block(text)
+ if extracted != text:
+ text = extracted
+
+ # 步骤2: 初步语法检查
+ score = 50 # 基础分
+
+ # 检查是否有 JSON 的基本特征
+ if text.startswith("{") or text.startswith("["):
+ score += 10
+ else:
+ errors.append("不以 { 或 [ 开头,可能不是 JSON")
+
+ if text.endswith("}") or text.endswith("]"):
+ score += 10
+ else:
+ errors.append("不以 } 或 ] 结尾,可能被截断")
+
+ # 步骤3: 尝试解析
+ try:
+ parsed = json.loads(text)
+ score += 30 # 成功解析加 30 分
+
+ # 检查深层嵌套完整性
+ _check_nested_integrity(parsed, errors)
+
+ # 最终得分
+ if not errors:
+ score = min(100, score + 10) # 完美通过再加 10
+
+ return (min(100, score), errors, parsed)
+
+ except json.JSONDecodeError as e:
+ errors.append(f"JSON 语法错误: {e}")
+
+ # 常见错误类型归类
+ err_msg = str(e)
+ if "trailing comma" in err_msg.lower() or "extra data" in err_msg.lower():
+ errors.append("类型: 尾随逗号或多余数据")
+ score -= 5
+ elif "unterminated" in err_msg.lower():
+ errors.append("类型: 未闭合的引号/括号")
+ score -= 10
+ elif "unexpected" in err_msg.lower():
+ errors.append("类型: 意外的字符/标记")
+ score -= 8
+ elif "control character" in err_msg.lower():
+ errors.append("类型: 字符串包含未转义的控制字符")
+ score -= 5
+ elif "expect value" in err_msg.lower():
+ errors.append("类型: 缺少值或字段名")
+ score -= 8
+
+ # 尝试自动修复
+ fixed, fixed_desc = _auto_fix_json(text)
+ if fixed:
+ try:
+ parsed = json.loads(fixed)
+ score = max(score, 75) # 修复成功给 75 分
+ errors.append(f"自动修复成功: {fixed_desc}")
+ return (score, errors, parsed)
+ except json.JSONDecodeError:
+ pass
+
+ return (max(0, score - 20), errors, None)
+
+
+def _extract_json_block(text):
+ """从文本中提取 JSON 代码块(```json ... ```)"""
+ # 模式1: ```json ... ``` 或 ``` ... ```
+ m = re.search(r'```(?:json)?\s*(\{.*?\}|\[.*?\])\s*```', text, re.DOTALL)
+ if m:
+ return m.group(1).strip()
+ # 模式2: ...
+ m = re.search(r'\s*(\{.*?\}|\[.*?\])\s*', text, re.DOTALL)
+ if m:
+ return m.group(1).strip()
+ return text
+
+
+def _check_nested_integrity(obj, errors, path=""):
+ """递归检查嵌套结构的完整性"""
+ if isinstance(obj, dict):
+ for k, v in obj.items():
+ child_path = f"{path}.{k}" if path else k
+ if v is None:
+ errors.append(f"字段 {child_path} 为 null")
+ elif isinstance(v, str) and len(v) > 1000:
+ errors.append(f"字段 {child_path} 字符串过长 ({len(v)} 字符)")
+ else:
+ _check_nested_integrity(v, errors, child_path)
+ elif isinstance(obj, list):
+ if len(obj) > 100:
+ errors.append(f"数组 {path} 过长 ({len(obj)} 项)")
+ for i, item in enumerate(obj):
+ _check_nested_integrity(item, errors, f"{path}[{i}]")
+
+
+def _auto_fix_json(text):
+ """尝试自动修复常见 JSON 格式问题
+
+ 返回: (fixed_text_or_None, description_or_None)
+ """
+ original = text
+ fixes = []
+
+ # 修复1: 去掉多余尾部括号(可能有多余的 }}})
+ fixed = text.rstrip()
+ # 计算开括号和闭括号的数量
+ open_braces = fixed.count('{')
+ close_braces = fixed.count('}')
+ open_brackets = fixed.count('[')
+ close_brackets = fixed.count(']')
+
+ if close_braces > open_braces:
+ # 去掉多余的 }
+ excess = close_braces - open_braces
+ for _ in range(excess):
+ last_idx = fixed.rfind('}')
+ if last_idx >= 0:
+ fixed = fixed[:last_idx] + fixed[last_idx+1:]
+ fixes.append(f"去掉 {excess} 个多余右括号")
+
+ if close_brackets > open_brackets:
+ excess = close_brackets - open_brackets
+ for _ in range(excess):
+ last_idx = fixed.rfind(']')
+ if last_idx >= 0:
+ fixed = fixed[:last_idx] + fixed[last_idx+1:]
+ fixes.append(f"去掉 {excess} 个多余右中括号")
+
+ # 修复2: 字符串内未转义的换行符
+ result = []
+ in_str = False
+ for ch in fixed:
+ if ch == '"':
+ in_str = not in_str
+ result.append(ch)
+ elif in_str and ch in '\n\r':
+ result.append('\\n')
+ else:
+ result.append(ch)
+ fixed2 = ''.join(result)
+ if fixed2 != fixed:
+ fixes.append("转义字符串内换行符")
+ fixed = fixed2
+
+ # 修复3: 尾随逗号(在 } 或 ] 前的逗号)
+ fixed = re.sub(r',\s*}', '}', fixed)
+ fixed = re.sub(r',\s*\]', ']', fixed)
+ if fixed != original:
+ fixes.append("移除尾随逗号")
+
+ # 修复4: 如果还不行,尝试补全为有效的对象包装
+ if not fixed.startswith('{') and not fixed.startswith('['):
+ # 可能是裸值 → 包装为对象
+ try:
+ json.loads(fixed)
+ except json.JSONDecodeError:
+ pass # 保留原始
+
+ if fixed != original:
+ return fixed, '; '.join(fixes)
+
+ # 无有效修复
+ return None, None
+
+
+# ═══════════════════════════════════════════════════════════
+# 第二层:JSON Schema 校验
+# ═══════════════════════════════════════════════════════════
+
+def validate_json_schema(parsed, schema=None):
+ """JSON Schema 字段匹配验证
+
+ schema 格式:
+ {
+ "required": ["field1", "field2"],
+ "optional": ["field3"],
+ "types": {"field1": str, "field2": (int, float)},
+ "min_values": {"rr_ratio": 0},
+ "non_empty": ["action", "timing_signal"],
+ }
+
+ 返回: (score, errors)
+ score: 0-100
+ errors: 字段级别的错误列表
+ """
+ if schema is None:
+ schema = STOCK_ANALYSIS_SCHEMA
+
+ if not isinstance(parsed, dict):
+ return (30, ["顶层不是对象(dict),无法做 Schema 校验"])
+
+ errors = []
+ score = 50 # 基础分
+
+ required = schema.get("required", [])
+ optional = schema.get("optional", [])
+ types = schema.get("types", {})
+ min_values = schema.get("min_values", {})
+ non_empty = schema.get("non_empty", [])
+ ranges = schema.get("ranges", {}) # {"field": (min, max)}
+
+ # 检查必要字段
+ missing_required = []
+ for field in required:
+ if field not in parsed:
+ missing_required.append(field)
+
+ if missing_required:
+ errors.append(f"缺少必要字段: {', '.join(missing_required)}")
+ score -= len(missing_required) * 8
+ else:
+ score += 15
+
+ # 检查字段类型
+ type_errors = []
+ for field, expected_type in types.items():
+ if field in parsed and parsed[field] is not None:
+ val = parsed[field]
+ if not isinstance(val, expected_type):
+ type_errors.append(f"{field}: 期望 {expected_type.__name__}, 实际 {type(val).__name__}")
+
+ if type_errors:
+ errors.extend(type_errors)
+ score -= len(type_errors) * 5
+
+ # 检查最小值约束
+ for field, min_val in min_values.items():
+ if field in parsed and parsed[field] is not None:
+ try:
+ if float(parsed[field]) < min_val:
+ errors.append(f"{field}={parsed[field]} < 最小值 {min_val}")
+ score -= 5
+ except (TypeError, ValueError):
+ pass
+
+ # 检查非空约束
+ for field in non_empty:
+ if field in parsed:
+ val = parsed[field]
+ if val is None or (isinstance(val, str) and not val.strip()) or val == 0:
+ errors.append(f"{field} 为空/零")
+ score -= 5
+
+ # 检查数值范围
+ for field, (lo, hi) in ranges.items():
+ if field in parsed and parsed[field] is not None:
+ try:
+ v = float(parsed[field])
+ if v < lo or v > hi:
+ errors.append(f"{field}={v} 超出合理范围 [{lo}, {hi}]")
+ score -= 3
+ except (TypeError, ValueError):
+ pass
+
+ # 未知字段不扣分(可接受额外信息)
+
+ # 标准化 score
+ score = max(0, min(100, score))
+ return (score, errors)
+
+
+# ═══════════════════════════════════════════════════════════
+# 第三层:打分-重试循环
+# ═══════════════════════════════════════════════════════════
+
+def scored_retry_loop(llm_fn, prompt, context=None, schema=None,
+ max_retries=3, threshold=90,
+ error_lib=None, task_id=None):
+ """打分-重试循环
+
+ 参数:
+ llm_fn: callable(prompt) → raw_text
+ 每次重试时传入附加上一轮反馈的增强 prompt
+ prompt: 初始 prompt 文本
+ context: dict,额外的上下文信息,用于记录
+ schema: JSON Schema 定义(可选)
+ max_retries: 最大重试次数(默认 3)
+ threshold: 验收阈值 0-100(默认 90)
+ error_lib: FormatErrorLibrary 实例(可选),用于记录错误
+ task_id: 任务标识符(可选),用于错误库跟踪
+
+ 返回:
+ {
+ "ok": True/False, # True=在阈值内通过, False=最终fallback
+ "data": dict or None, # 解析后的 JSON 数据
+ "raw": str, # 最终原始输出
+ "attempts": int, # 实际尝试次数
+ "scores": [int, ...], # 每次尝试的分数
+ "errors": [str, ...], # 最终错误列表
+ "fallback": bool, # 是否走了一段 best_effort
+ }
+ """
+ # 验证最小报价字段
+ _validate_min_values(schema)
+
+ best_score = 0
+ best_data = None
+ best_raw = ""
+
+ # 构建基础 prompt 系统反馈部分
+ base_prompt = prompt
+ feedback_history = []
+ scores = []
+
+ for attempt in range(1, max_retries + 1):
+ # 构建本次的 prompt(附加上一轮的格式反馈)
+ current_prompt = base_prompt
+ if feedback_history:
+ feedback_section = "\n\n[Format Feedback from previous attempt]\n" + \
+ "\n".join(feedback_history)
+ current_prompt = base_prompt + feedback_section
+
+ # 调用 LLM
+ try:
+ raw = llm_fn(current_prompt)
+ except Exception as e:
+ scores.append(0)
+ feedback_history.append(f"Attempt {attempt}: LLM call failed: {e}")
+ continue
+
+ if not raw or not raw.strip():
+ scores.append(0)
+ feedback_history.append(f"Attempt {attempt}: 空响应")
+ continue
+
+ # 步骤1: 语法校验
+ syntax_score, syntax_errors, parsed = validate_json_syntax(raw)
+
+ # 步骤2: Schema 校验(如果语法通过且有 schema)
+ schema_score = 100
+ schema_errors = []
+ if parsed is not None and schema:
+ schema_score, schema_errors = validate_json_schema(parsed, schema)
+
+ # 综合打分: 语法占 60%, Schema 占 40%
+ if parsed is not None:
+ combined_score = int(syntax_score * 0.6 + schema_score * 0.4)
+ else:
+ combined_score = syntax_score # 语法失败时只用语法分
+
+ scores.append(combined_score)
+
+ # 记录错误信息
+ all_errors = syntax_errors + schema_errors
+
+ # 记录到 Format Error Library
+ if error_lib and task_id:
+ error_lib.record(
+ task_id=task_id,
+ attempt=attempt,
+ score=combined_score,
+ errors=all_errors,
+ context=context,
+ )
+
+ # 追踪最佳结果
+ if combined_score > best_score:
+ best_score = combined_score
+ best_data = parsed
+ best_raw = raw
+
+ # 检查是否通过阈值
+ if combined_score >= threshold:
+ return {
+ "ok": True,
+ "data": parsed,
+ "raw": raw,
+ "attempts": attempt,
+ "scores": scores,
+ "errors": all_errors,
+ "fallback": False,
+ }
+
+ # 准备下一轮反馈
+ if attempt < max_retries:
+ feedback = _build_feedback(combined_score, all_errors, attempt)
+ feedback_history.append(feedback)
+
+ # 小幅延迟避免 API 限流
+ if attempt < max_retries:
+ time.sleep(0.5)
+
+ # 所有重试都失败 → 尝试 best_effort_fallback
+ fallback_data = best_data
+ fallback_raw = best_raw
+
+ if fallback_data is None and fallback_raw:
+ # 尝试用 best_effort 从最佳原始输出恢复
+ fallback_data = best_effort_fallback(fallback_raw)
+
+ # 构建最终结果
+ result = {
+ "ok": fallback_data is not None,
+ "data": fallback_data,
+ "raw": fallback_raw,
+ "attempts": max_retries,
+ "scores": scores,
+ "errors": ["所有重试均未达到阈值", f"最高分: {best_score}/{threshold}"],
+ "fallback": True,
+ }
+
+ # 产生告警(通过 stderr 输出,可被 cron 捕获)
+ task_tag = f"[{task_id}]" if task_id else ""
+ print(
+ f"[JSON_VALIDATION] WARNING{task_tag}: "
+ f"{max_retries}次重试后最高分{best_score}/{threshold},"
+ f"使用{'fallback数据' if fallback_data else '空数据'}",
+ file=sys.stderr, flush=True
+ )
+
+ # 记录严重错误到 error_lib
+ if error_lib and task_id:
+ error_lib.record(
+ task_id=task_id,
+ attempt=max_retries + 1,
+ score=best_score,
+ errors=["ALL_RETRIES_EXHAUSTED", f"最高分{best_score}/{threshold}"],
+ context=context,
+ is_failure=True,
+ )
+
+ return result
+
+
+def _validate_min_values(schema):
+ """确保 schema 包含 min_values 约束"""
+ if schema and "min_values" not in schema:
+ schema["min_values"] = {}
+ return schema
+
+
+def _build_feedback(score, errors, attempt):
+ """构建格式反馈字符串(给 LLM 的下一轮提示)"""
+ parts = [f"--- Attempt {attempt} feedback (score: {score}/100) ---"]
+ if errors:
+ parts.append("Issues to fix:")
+ for e in errors[:5]: # 最多反馈 5 个问题
+ parts.append(f" - {e}")
+ else:
+ parts.append("No specific errors found, but score below threshold.")
+
+ return "\n".join(parts)
+
+
+# ═══════════════════════════════════════════════════════════
+# Best-effort Fallback
+# ═══════════════════════════════════════════════════════════
+
+def best_effort_fallback(raw_text):
+ """尽力从非法 JSON 中恢复数据
+
+ 策略(按优先级):
+ 1. 尝试 auto_fix 后解析
+ 2. 正则提取所有 key:value 对
+ 3. 提取股票代码等关键信息
+ """
+ if not raw_text:
+ return None
+
+ # 策略1: auto_fix
+ fixed, _ = _auto_fix_json(raw_text)
+ if fixed:
+ try:
+ return json.loads(fixed)
+ except json.JSONDecodeError:
+ pass
+
+ # 策略2: 正则提取所有字段
+ data = {}
+
+ # 提取代码
+ code_match = re.search(r'"code"\s*:\s*"(\d+)"', raw_text)
+ if code_match:
+ data["code"] = code_match.group(1)
+
+ # 提取名称
+ name_match = re.search(r'"name"\s*:\s*"([^"]+)"', raw_text)
+ if name_match:
+ data["name"] = name_match.group(1)
+
+ # 提取数值字段
+ for field in ["stop_loss", "take_profit", "entry_low", "entry_high",
+ "price", "rr_ratio", "cost"]:
+ pattern = rf'"{field}"\s*:\s*([\d.]+)'
+ m = re.search(pattern, raw_text)
+ if m:
+ try:
+ data[field] = float(m.group(1))
+ except ValueError:
+ pass
+
+ # 提取字符串字段
+ for field in ["action", "timing_signal", "currency", "status"]:
+ pattern = rf'"{field}"\s*:\s*"([^"]*)"'
+ m = re.search(pattern, raw_text)
+ if m:
+ data[field] = m.group(1)
+
+ return data if data else None
+
+
+# ═══════════════════════════════════════════════════════════
+# Format Error Library
+# ═══════════════════════════════════════════════════════════
+
+class FormatErrorLibrary:
+ """格式错误库 — 持久化记录每次格式错误
+
+ 每月/周自动汇总,支持按错误类型和任务ID查询。
+ """
+
+ def __init__(self, path=None):
+ self.path = path or ERROR_LIBRARY_PATH
+ self._records = []
+ self._load()
+
+ def _load(self):
+ """从磁盘加载已有记录"""
+ if os.path.exists(self.path):
+ try:
+ with open(self.path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+ self._records = data.get("records", [])
+ except (json.JSONDecodeError, Exception):
+ self._records = []
+ else:
+ self._records = []
+
+ def _save(self):
+ """持久化到磁盘"""
+ os.makedirs(os.path.dirname(self.path), exist_ok=True)
+ with open(self.path, "w", encoding="utf-8") as f:
+ json.dump({
+ "library_version": "1.0",
+ "total_records": len(self._records),
+ "last_updated": datetime.now().isoformat(),
+ "records": self._records[-1000:], # 最多保留最近1000条
+ }, f, ensure_ascii=False, indent=2)
+
+ def record(self, task_id, attempt, score, errors,
+ context=None, is_failure=False):
+ """记录一条格式错误"""
+ record = {
+ "task_id": task_id,
+ "timestamp": datetime.now().isoformat(),
+ "attempt": attempt,
+ "score": score,
+ "errors": errors[:10], # 最多保留10个错误
+ "is_failure": is_failure,
+ }
+ if context:
+ # 只记录上下文的关键信息,避免敏感数据
+ safe_ctx = {}
+ for k in ["code", "name", "stock"]:
+ if k in (context or {}):
+ safe_ctx[k] = context[k]
+ if safe_ctx:
+ record["context"] = safe_ctx
+
+ self._records.append(record)
+ self._save()
+
+ def get_summary(self, days=7):
+ """获取最近 N 天的错误汇总
+
+ 返回: {
+ "total": 总记录数,
+ "by_type": {"missing_field": N, "syntax_error": N, ...},
+ "by_task": {"task_id": N},
+ "worst_tasks": [最差任务列表],
+ }
+ """
+ cutoff = datetime.now() - timedelta(days=days)
+ recent = [
+ r for r in self._records
+ if datetime.fromisoformat(r["timestamp"]) >= cutoff
+ ]
+
+ if not recent:
+ return {"total": 0, "by_type": {}, "by_task": {}, "worst_tasks": []}
+
+ by_type = defaultdict(int)
+ by_task = defaultdict(int)
+ task_scores = defaultdict(list)
+
+ for r in recent:
+ task = r.get("task_id", "unknown")
+ by_task[task] += 1
+ task_scores[task].append(r.get("score", 0))
+
+ for err in r.get("errors", []):
+ # 归类错误类型
+ if "缺少" in err or "missing" in err.lower():
+ by_type["missing_field"] += 1
+ elif "syntax" in err.lower() or "JSON" in err or "json" in err:
+ by_type["syntax_error"] += 1
+ elif "trailing comma" in err.lower() or "尾随逗号" in err:
+ by_type["trailing_comma"] += 1
+ elif "truncat" in err.lower() or "截断" in err:
+ by_type["truncation"] += 1
+ else:
+ by_type["other"] += 1
+
+ # 最差任务(平均分最低)
+ worst = sorted(
+ [(t, sum(s)/len(s), by_task[t]) for t, s in task_scores.items()],
+ key=lambda x: x[1]
+ )[:5]
+
+ return {
+ "total": len(recent),
+ "period_days": days,
+ "by_type": dict(by_type),
+ "by_task": dict(by_task),
+ "worst_tasks": [
+ {"task": t, "avg_score": round(avg, 1), "count": c}
+ for t, avg, c in worst
+ ],
+ "generated_at": datetime.now().isoformat(),
+ }
+
+ def get_recent(self, limit=20):
+ """获取最近的错误记录"""
+ return list(reversed(self._records[-limit:]))
+
+ def clear_old(self, keep_days=30):
+ """清理超过 keep_days 的旧记录"""
+ cutoff = datetime.now() - timedelta(days=keep_days)
+ before = len(self._records)
+ self._records = [
+ r for r in self._records
+ if datetime.fromisoformat(r["timestamp"]) >= cutoff
+ ]
+ after = len(self._records)
+ self._save()
+ return before - after
+
+ def auto_remediate_prompt(self):
+ """自动分析最近错误模式,返回改进提示词的建议
+
+ 返回: dict {建议类型: 建议内容}
+ """
+ summary = self.get_summary(days=7)
+ suggestions = {}
+
+ by_type = summary.get("by_type", {})
+ total = summary.get("total", 0)
+
+ if total == 0:
+ return {"info": "最近7天无格式错误,系统运行良好"}
+
+ # 字段缺失问题 → 建议在 prompt 中显式列出字段
+ missing = by_type.get("missing_field", 0)
+ if missing > 0 and missing / max(total, 1) > 0.3:
+ suggestions["prompt_field_emphasis"] = (
+ f"最近7天 {missing}/{total} 错误是字段缺失,"
+ "建议在 prompt 中显式列出所有必需字段的说明和顺序"
+ )
+
+ # 语法错误 → 建议使用结构化生成模式
+ syntax = by_type.get("syntax_error", 0)
+ if syntax > 0 and syntax / max(total, 1) > 0.5:
+ suggestions["structured_generation"] = (
+ f"最近7天 {syntax}/{total} 错误是 JSON 语法错误,"
+ "建议启用 constrained decoding 或 JSON mode"
+ )
+
+ # 截断 → 检查 max_tokens
+ trunc = by_type.get("truncation", 0)
+ if trunc > 0:
+ suggestions["increase_max_tokens"] = (
+ f"最近7天 {trunc} 次截断,建议 max_tokens 从当前值上调"
+ )
+
+ if not suggestions:
+ suggestions["info"] = "错误率在可接受范围内,无需自动调整"
+
+ return suggestions
+
+
+# ═══════════════════════════════════════════════════════════
+# 便捷封装: 在 LLM 响应中提取 JSON 的统一入口
+# ═══════════════════════════════════════════════════════════
+
+def extract_json_safe(llm_response, schema=None, task_id=None):
+ """安全地从 LLM 响应中提取 JSON,带重试
+
+ 这是一个单轮快速提取版本(不做重试),适用于不需要
+ 完整 scored_retry_loop 的场景。
+
+ 返回: (parsed_dict_or_None, score, errors)
+ """
+ score, errors, parsed = validate_json_syntax(llm_response)
+
+ if parsed is not None and schema:
+ schema_score, schema_errors = validate_json_schema(parsed, schema)
+ score = int(score * 0.6 + schema_score * 0.4)
+ errors.extend(schema_errors)
+
+ if parsed is None:
+ # 尝试 fallback
+ parsed = best_effort_fallback(llm_response)
+
+ return parsed, score, errors
+
+
+# ═══════════════════════════════════════════════════════════
+# CLI 入口(测试/调试用)
+# ═══════════════════════════════════════════════════════════
+
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) > 1 and sys.argv[1] == "--summary":
+ lib = FormatErrorLibrary()
+ summary = lib.get_summary(days=7)
+ print(json.dumps(summary, ensure_ascii=False, indent=2))
+ sys.exit(0)
+
+ if len(sys.argv) > 1 and sys.argv[1] == "--recent":
+ lib = FormatErrorLibrary()
+ recent = lib.get_recent(limit=10)
+ print(json.dumps(recent, ensure_ascii=False, indent=2))
+ sys.exit(0)
+
+ if len(sys.argv) > 1 and sys.argv[1] == "--test":
+ # 自测:验证各种情况
+ test_cases = [
+ ('{"code": "000001", "name": "平安"}', "标准 JSON"),
+ ('{"code": "000001", "name": "平安",}', "尾随逗号"),
+ ('```json\n{"code": "000001"}\n```', "代码块包裹"),
+ ('{"code": "000001"}', "structured_data 标签"),
+ ('{"code": "000001",\n"name": "平\n安"}', "未转义换行符"),
+ ('{"code": "000001"}}', "多余右括号"),
+ ('{"code": "000001", "price": 12.5, "stop_loss": null, "action": ""}', "空字段"),
+ ]
+
+ for text, desc in test_cases:
+ score, errors, parsed = validate_json_syntax(text)
+ status = "✅" if parsed else "❌"
+ print(f"{status} {desc}: score={score}")
+ if errors:
+ for e in errors:
+ print(f" - {e}")
+ if parsed:
+ print(f" parsed: {json.dumps(parsed, ensure_ascii=False)[:80]}")
+ print()
+
+ # 测试 Schema 校验
+ print("=== Schema 校验测试 ===")
+ test_obj = {
+ "code": "000001", "name": "平安", "action": "持有",
+ "stop_loss": 10.5, "take_profit": 12.0,
+ "entry_low": 10.0, "entry_high": 11.0,
+ "timing_signal": "持有", "rr_ratio": 2.0,
+ }
+ score, errors = validate_json_schema(test_obj)
+ print(f"完整对象: score={score}, errors={errors}")
+
+ test_obj2 = {"code": "000001", "name": "平安"}
+ score2, errors2 = validate_json_schema(test_obj2)
+ print(f"缺失字段: score={score2}, errors={errors2}")
+
+ sys.exit(0)
+
+ # 默认:从 stdin 读取 JSON 文本并验证
+ text = sys.stdin.read()
+ score, errors, parsed = validate_json_syntax(text)
+ result = {
+ "score": score,
+ "errors": errors,
+ "parsed": parsed,
+ "valid": parsed is not None,
+ }
+ print(json.dumps(result, ensure_ascii=False, indent=2))
diff --git a/scripts/market_insight.py b/scripts/market_insight.py
index 86796ef3..c0bb082e 100644
--- a/scripts/market_insight.py
+++ b/scripts/market_insight.py
@@ -25,8 +25,9 @@ def load_holding_industry_map():
with open(DATA_DIR / "stock_profiles.json", "r", encoding="utf-8") as f:
profiles = json.load(f).get("profiles", [])
- with open(DATA_DIR / "portfolio.json", "r", encoding="utf-8") as f:
- portfolio = json.load(f)
+ # 优先从DB读取持仓(Dad铁律:禁用JSON直读)
+ from mo_data import read_portfolio
+ portfolio = read_portfolio()
except FileNotFoundError:
return {}
@@ -55,6 +56,8 @@ def load_holding_industry_map():
def generate():
+ # market_path 在DB和fallback两个分支后都会用到,所以提前定义
+ market_path = DATA_DIR / "market.json"
# 优先从 SQLite 读取市场数据
try:
from mofin_db import get_conn, query_latest_market
diff --git a/scripts/mo_alphasift_bridge.py b/scripts/mo_alphasift_bridge.py
index 75c321b5..fe4989bf 100644
--- a/scripts/mo_alphasift_bridge.py
+++ b/scripts/mo_alphasift_bridge.py
@@ -18,8 +18,8 @@ from pathlib import Path
DSA_API = "http://127.0.0.1:8001"
MOFIN_DATA = Path("/home/hmo/web-dashboard/data")
-WATCHLIST_PATH = MOFIN_DATA / "watchlist.json"
-PORTFOLIO_PATH = MOFIN_DATA / "portfolio.json"
+# 已迁移到 DB — watchlist.json / portfolio.json 不再使用
+# 保留路径仅用于兼容 import,实际数据从 mofin.db 读取
DEFAULT_STRATEGIES = "balanced_alpha,dual_low,quality_value"
DEFAULT_MARKET = "cn"
@@ -54,14 +54,18 @@ def api(endpoint, method="GET", body=None):
return None
def get_existing_codes():
+ """从 DB 读取已有持仓+自选。JSON 已废弃。"""
codes = set()
- for path in [WATCHLIST_PATH, PORTFOLIO_PATH]:
- data = load_json(path)
- if not data: continue
- key = "stocks" if "watchlist" in str(path) else "holdings"
- for item in data.get(key, []):
- c = str(item.get("code", "")).strip()
- if c: codes.add(c)
+ try:
+ import sqlite3
+ db = sqlite3.connect(str(MOFIN_DATA / "mofin.db"))
+ for row in db.execute("SELECT code FROM watchlist_stocks WHERE is_active=1"):
+ codes.add(str(row[0]).strip())
+ for row in db.execute("SELECT code FROM holdings"):
+ codes.add(str(row[0]).strip())
+ db.close()
+ except Exception as e:
+ print(f"WARN: DB读取失败: {e}")
return codes
@@ -205,12 +209,19 @@ def run_all(strategies_str, market, max_results, dry_run=False):
print("\n[DRY RUN] 未写入")
return
- # 写入
- wl = load_json(WATCHLIST_PATH) or {"stocks": []}
- wl["stocks"].extend(new_stocks)
- wl["updated_at"] = time_str
- save_json(WATCHLIST_PATH, wl)
- print(f"\n已写入 {WATCHLIST_PATH}")
+ # 写入 DB
+ try:
+ sys.path.insert(0, str(MOFIN_DATA.parent))
+ from mofin_db import get_conn, write_watchlist_stock
+ conn = get_conn()
+ for s in new_stocks:
+ s.setdefault('currency', 'CNY')
+ write_watchlist_stock(conn, s)
+ conn.close()
+ print(f"\n已写入 {len(new_stocks)} 只到 DB watchlist_stocks")
+ except Exception as e:
+ print(f"WARN: DB写入失败: {e}")
+ return
# 策略生成
print("\n调用 regenerate_all()...")
diff --git a/scripts/mo_config.py b/scripts/mo_config.py
index 1199092c..df1c7b8b 100644
--- a/scripts/mo_config.py
+++ b/scripts/mo_config.py
@@ -42,22 +42,26 @@ class MoConfig:
# Hermes 状态目录
hermes_dir: Path = field(default_factory=lambda: Path.home() / ".hermes")
- # ── 关键数据文件路径 ──────────────────────────────────────────
-
+ # ── 关键数据文件路径(已废弃,仅保留为检查逻辑。新代码勿用) ──────
+
@property
def portfolio_path(self) -> Path:
+ """⚠️ 已废弃!数据在 mofin.db holdings + portfolio_summary 表。"""
return self.data_dir / "portfolio.json"
-
+
@property
def decisions_path(self) -> Path:
+ """⚠️ 已废弃!数据在 mofin.db holding_strategies 表。"""
return self.data_dir / "decisions.json"
-
+
@property
def watchlist_path(self) -> Path:
+ """⚠️ 已废弃!数据在 mofin.db watchlist_stocks 表。"""
return self.data_dir / "watchlist.json"
-
+
@property
def price_events_path(self) -> Path:
+ """⚠️ 已废弃!数据在 mofin.db price_events 表。"""
return self.data_dir / "price_events.json"
@property
diff --git a/scripts/mofin_collect.py b/scripts/mofin_collect.py
index b94f3248..8441844e 100644
--- a/scripts/mofin_collect.py
+++ b/scripts/mofin_collect.py
@@ -155,11 +155,28 @@ try:
for wr in conn.execute("SELECT code, name, price, entry_low, entry_high, stop_loss FROM watchlist_stocks WHERE is_active=1"):
code = wr["code"]
name = wr["name"]
+ wl_price = wr["price"] or 0
+ # 自选股price可能为0(新加入未更新),从实时API获取
+ if wl_price <= 0:
+ try:
+ import urllib.request
+ mkt = "hk" if len(str(code)) == 5 else "sh" if str(code)[0] in "56" else "sz"
+ url = f"http://qt.gtimg.cn/q={mkt}{code}"
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
+ resp = urllib.request.urlopen(req, timeout=5).read()
+ text = resp.decode("gbk")
+ parts = text.split("~")
+ if len(parts) > 3:
+ p = float(parts[3])
+ if p > 0:
+ wl_price = p
+ except Exception:
+ pass
# 自选股无cost/shares,传0
try:
from strategy_lifecycle import reassess_with_context
result = reassess_with_context(
- code, name, wr["price"] or 0,
+ code, name, wl_price,
0, 0, ""
)
if result and result.get("action"):
diff --git a/scripts/mofin_db.py b/scripts/mofin_db.py
index ca765a4c..9965877f 100644
--- a/scripts/mofin_db.py
+++ b/scripts/mofin_db.py
@@ -21,7 +21,7 @@ from datetime import datetime
from pathlib import Path
from typing import Optional, Callable
-DATA_DIR = Path(__file__).parent / "data"
+DATA_DIR = Path(__file__).parent.parent / "data"
DB_PATH = DATA_DIR / "mofin.db"
# ═══════════════════════════════════════════════════════════
@@ -1097,7 +1097,7 @@ def write_holding_strategy(conn, code: str, name: str, data: dict) -> tuple[bool
signal_factors_json, time_horizon, decision_type)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
datetime('now','localtime'),
- ?,?,?,?,?,?,?,?,?)
+ ?,?,?,?,?,?,?,?,?,?)
""", (
code, name,
data.get('version', 1), data.get('price'), data.get('cost'),
@@ -1187,7 +1187,7 @@ def write_portfolio_summary(conn, data: dict) -> tuple[bool, str]:
def write_watchlist_stock(conn, stock: dict) -> tuple[bool, str]:
- """写入自选股(替代 watchlist.json)"""
+ """写入自选股(写入 watchlist_stocks 表)"""
try:
conn.execute("""
INSERT INTO watchlist_stocks (code, name, price, entry_low, entry_high,
diff --git a/scripts/morning_health_check.py b/scripts/morning_health_check.py
index 4f3f76bd..89ca01f7 100755
--- a/scripts/morning_health_check.py
+++ b/scripts/morning_health_check.py
@@ -672,7 +672,7 @@ def run_check(item):
if 'macro_risk_state' in content:
consumer_info = 'macro_risk_state.json'
if 'watchlist' in content.lower():
- consumer_info = 'watchlist.json / decisions.json'
+ consumer_info = 'watchlist_stocks表 / holding_strategies表'
if 'INSERT INTO' in content:
for tbl in ['todos', 'price_events', 'macro_context_log', 'accuracy_stats']:
if tbl in content:
diff --git a/scripts/multi_timeframe.py b/scripts/multi_timeframe.py
index 69f4291d..514bc1fa 100644
--- a/scripts/multi_timeframe.py
+++ b/scripts/multi_timeframe.py
@@ -17,7 +17,7 @@ import urllib.error
from datetime import datetime, date, timedelta
from typing import Optional
-DATA_DIR = "/home/hmo/web-dashboard/data"
+DATA_DIR = "/home/hmo/MoFin/data"
HISTORY_PATH = os.path.join(DATA_DIR, "price_history.json")
# multi_tf_cache.json 已迁移到 DB (mtf_cache 表)
@@ -92,7 +92,7 @@ def _load_mtf_cache():
return _MTF_CACHE_DATA
try:
import sqlite3
- db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
+ db = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
rows = db.execute("SELECT code, cache_json FROM mtf_cache").fetchall()
_MTF_CACHE_DATA = {}
for code, json_str in rows:
@@ -113,7 +113,7 @@ def _save_mtf_cache():
return
try:
import sqlite3
- db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
+ db = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
for code, data in _MTF_CACHE_DATA.items():
db.execute(
"INSERT OR REPLACE INTO mtf_cache (code, cache_json, updated_at) VALUES (?,?,datetime('now','localtime'))",
@@ -222,9 +222,21 @@ def calc_moving_averages(klines: list, windows: list = [5, 10, 20, 60]) -> dict:
return {f"ma{w}": None for w in windows}
# 确保按时间正序(旧的在前)
+ # 使用日期判断顺序(不能用价格:下跌趋势下closes[0]>closes[-1]也会触发反转)
closes = [k["close"] for k in klines]
- # 检查是否倒序(最新的在前)
- if len(closes) >= 2 and closes[0] > closes[-1]:
+ is_reversed = False
+ if len(klines) >= 2:
+ d0 = klines[0].get("date", "")
+ d1 = klines[-1].get("date", "")
+ if d0 and d1:
+ from datetime import datetime
+ try:
+ is_reversed = datetime.strptime(d0, "%Y-%m-%d") > datetime.strptime(d1, "%Y-%m-%d")
+ except:
+ # 日期格式不对时的fallback: 仅当首价显著高于末价才判定倒序(50%阈值)
+ is_reversed = (closes[0] > closes[-1] * 1.5) if len(closes) >= 2 else False
+
+ if is_reversed:
closes = list(reversed(closes))
result = {}
diff --git a/scripts/per_stock_reassess.py b/scripts/per_stock_reassess.py
index cee0bd6c..50d1047a 100644
--- a/scripts/per_stock_reassess.py
+++ b/scripts/per_stock_reassess.py
@@ -172,16 +172,10 @@ def main():
print(f"[ERROR] {code}: {e}", file=sys.stderr)
errors += 1
- # 写回 decisions.json(只更新被修改的那条,其余保留原样)
- raw["decisions"] = list(decisions_map.values())
- raw["total"] = len(raw["decisions"])
- from datetime import datetime
- raw["regenerated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
- with open(DECISIONS_PATH, "w") as f:
- json.dump(raw, f, ensure_ascii=False, indent=2)
-
+ # 策略数据已通过DB写入(holding_strategies表),json.dump到decisions.json已废弃
# 同步自选股更新回 watchlist_stocks 表
try:
+ from datetime import datetime as _dt
import sqlite3
_db2 = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
for _code in codes:
@@ -203,7 +197,7 @@ def main():
"stop_loss": _entry.get("stop_loss", 0),
"tech_snapshot": _entry.get("tech_snapshot", ""),
"rr": _entry.get("rr_ratio", 0),
- "reassessed_at": datetime.now().strftime("%Y-%m-%d")
+ "reassessed_at": _dt.now().strftime("%Y-%m-%d")
}, ensure_ascii=False),
_code
))
diff --git a/scripts/pre-flight-check.py b/scripts/pre-flight-check.py
index 5c6c91c9..00e1c61a 100644
--- a/scripts/pre-flight-check.py
+++ b/scripts/pre-flight-check.py
@@ -25,10 +25,10 @@ import sys
from pathlib import Path
from datetime import datetime, timezone
+from mo_data import read_decisions
+
# === 路径 ===
REGISTRY = Path("/home/hmo/projects/MoFin/data/prompts/registry.json")
-DECISIONS = Path("/home/hmo/web-dashboard/data/decisions.json")
-PORTFOLIO = Path("/home/hmo/web-dashboard/data/portfolio.json")
# === 检查 7:版本一致性 ===
def check_version_consistency():
@@ -62,33 +62,39 @@ def check_version_consistency():
# === 检查 1:数据时效 ===
def check_data_freshness():
- """检查 decisions.json 的更新时间是否在合理范围内"""
+ """检查 holding_strategies 表的最新更新时间"""
+ from datetime import datetime, timezone
try:
- mtime = DECISIONS.stat().st_mtime
- mtime_dt = datetime.fromtimestamp(mtime, tz=timezone.utc)
- now = datetime.now(timezone.utc)
- age_minutes = (now - mtime_dt).total_seconds() / 60
+ import sqlite3
+ c = sqlite3.connect(str(WEB_DATA / "mofin.db"))
+ row = c.execute("SELECT MAX(updated_at) FROM holding_strategies WHERE status IN ('active','updated')").fetchone()
+ c.close()
+ if row and row[0]:
+ mtime_dt = datetime.strptime(row[0][:19], '%Y-%m-%d %H:%M:%S')
+ age_minutes = (datetime.now() - mtime_dt).total_seconds() / 60
+ else:
+ return ("⚠️ 数据时效", "策略表无有效数据")
except Exception as e:
- return ("⚠️ 数据时效", f"无法检查 decisions.json: {e}")
+ return ("⚠️ 数据时效", f"无法检查策略表: {e}")
if age_minutes < 60:
return ("✅ 数据时效",
- f"decisions.json 更新于 {age_minutes:.0f} 分钟前 ({mtime_dt.strftime('%H:%M')})")
+ f"策略数据 更新于 {age_minutes:.0f} 分钟前 ({mtime_dt.strftime('%H:%M')})")
elif age_minutes < 240:
return ("⚠️ 数据时效",
- f"decisions.json 已 {age_minutes:.0f} 分钟未更新(可能已收盘),数据视为陈旧")
+ f"策略数据 已 {age_minutes:.0f} 分钟未更新(可能已收盘),数据视为陈旧")
else:
return ("❌ 数据时效",
- f"decisions.json 已 {age_minutes:.0f} 分钟未更新,数据过期,请检查数据管线")
+ f"策略数据 已 {age_minutes:.0f} 分钟未更新,数据过期,请检查数据管线")
# === 检查 5:成本有效 ===
def check_cost_validity():
"""检查 decisions.json 中所有持仓的成本是否有效"""
try:
- dec = json.loads(DECISIONS.read_text())
+ dec = read_decisions()
except Exception as e:
- return ("⚠️ 成本有效性", f"无法读取 decisions.json: {e}")
+ return ("⚠️ 成本有效性", f"无法读取 decisions: {e}")
stocks = dec.get("stocks", dec.get("holdings", dec.get("strategies", [])))
if not stocks:
@@ -122,9 +128,9 @@ def check_cost_validity():
def check_stop_technical(code):
"""检查单只股票的止损是否基于技术位(仅对单股模式生效)"""
try:
- dec = json.loads(DECISIONS.read_text())
+ dec = read_decisions()
except Exception as e:
- return ("⚠️ 止损技术位", f"无法读取 decisions.json: {e}")
+ return ("⚠️ 止损技术位", f"无法读取 decisions: {e}")
stocks = dec.get("stocks", dec.get("strategies", []))
for s in stocks:
@@ -150,9 +156,9 @@ def check_stop_technical(code):
def check_rr(code, price=None):
"""检查单只股票的 R/R 是否达标"""
try:
- dec = json.loads(DECISIONS.read_text())
+ dec = read_decisions()
except Exception as e:
- return ("⚠️ R/R 达标", f"无法读取 decisions.json: {e}")
+ return ("⚠️ R/R 达标", f"无法读取 decisions: {e}")
stocks = dec.get("stocks", dec.get("strategies", []))
for s in stocks:
diff --git a/scripts/price_data_inject.py b/scripts/price_data_inject.py
index 6d5c43a0..004a98b3 100755
--- a/scripts/price_data_inject.py
+++ b/scripts/price_data_inject.py
@@ -6,6 +6,8 @@ import json
import sys
import os
+from mo_data import read_portfolio, read_watchlist
+
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
WATCHLIST_PATH = "/home/hmo/web-dashboard/data/watchlist.json"
MTF_CACHE_PATH = "/home/hmo/web-dashboard/data/multi_tf_cache.json"
@@ -160,7 +162,7 @@ def classify_from_cache(code, name, mtf_cache):
def main():
try:
- pf = json.load(open(PORTFOLIO_PATH))
+ pf = read_portfolio()
except Exception as e:
print(f"[ERROR] {e}")
sys.exit(0)
@@ -220,7 +222,7 @@ def main():
# ===== 自选股买入区监控 =====
try:
- wl = json.load(open(WATCHLIST_PATH))
+ wl = read_watchlist()
wl_stocks = wl.get("stocks", [])
# 过滤掉已经在持仓里的
held_codes = {h["code"] for h in holdings}
diff --git a/scripts/price_monitor.py b/scripts/price_monitor.py
index 67d011b3..da254439 100644
--- a/scripts/price_monitor.py
+++ b/scripts/price_monitor.py
@@ -3,7 +3,6 @@
规则:进入区间报一次,离开区间报一次,中间不重复。
每次运行时一次性刷新所有持仓+自选股的实时价。
"""
-import json
import urllib.request
import os
import sys
@@ -11,9 +10,8 @@ import time
import sqlite3
from datetime import datetime
-DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
-PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
-WATCHLIST_PATH = "/home/hmo/web-dashboard/data/watchlist.json"
+from mo_data import read_decisions
+
BREACH_PATH = "/home/hmo/.hermes/zone_breach.json"
STATE_PATH = os.path.expanduser("~/.hermes/price_trigger_state.json")
EVENTS_PATH = "/home/hmo/web-dashboard/data/price_events.json"
@@ -319,10 +317,9 @@ def run_once(round_label=""):
# === 第二步:检查触发条件 ===
try:
- with open(DECISIONS_PATH) as f:
- dec = json.load(f)
+ dec = read_decisions()
except:
- print(f"❌{label} 无法读取decisions.json", file=sys.stderr)
+ print(f"❌{label} 无法读取decisions(DB)", file=sys.stderr)
return
active = [d for d in dec.get("decisions", []) if d.get("status") == "active"]
diff --git a/scripts/server.py b/scripts/server.py
index 0b46cbbe..8eb6f143 100644
--- a/scripts/server.py
+++ b/scripts/server.py
@@ -21,7 +21,7 @@ from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, wr
app = Flask(__name__, static_folder="static", static_url_path="")
-DATA_DIR = Path(__file__).parent / "data"
+DATA_DIR = Path(__file__).parent.parent / "data"
UPLOAD_DIR = Path(__file__).parent / "uploads"
# Hermes Gateway
@@ -1038,7 +1038,7 @@ def update_realtime():
pf_holdings[code]["updated_at"] = datetime.now().isoformat()
updated += 1
- # 也更新 watchlist.json
+ # 也更新 watchlist_stocks 表(DB)
wl = read_watchlist()
wl_stocks = {s["code"]: s for s in wl.get("stocks", [])}
diff --git a/scripts/strategy_summary.py b/scripts/strategy_summary.py
index e2fcc629..560d71d5 100644
--- a/scripts/strategy_summary.py
+++ b/scripts/strategy_summary.py
@@ -1,11 +1,9 @@
#!/usr/bin/env python3
"""生成策略评估摘要"""
-import json
+from mo_data import read_decisions, read_portfolio
-with open('/home/hmo/web-dashboard/data/decisions.json') as f:
- dec = json.load(f)
-with open('/home/hmo/MoFin/data/portfolio.json') as f:
- pf = json.load(f)
+dec = read_decisions()
+pf = read_portfolio()
holdings = pf.get('holdings', [])
cash = pf.get('cash', 321271)
diff --git a/scripts/strategy_tree.py b/scripts/strategy_tree.py
index 3a8edfc3..337fd0ba 100644
--- a/scripts/strategy_tree.py
+++ b/scripts/strategy_tree.py
@@ -74,7 +74,7 @@ def detect_scenario():
# 优先 DB
import sqlite3
from pathlib import Path
- db = sqlite3.connect(str(Path(__file__).parent / "data" / "mofin.db"))
+ db = sqlite3.connect(str(Path(__file__).parent.parent / "data" / "mofin.db"))
mrow = db.execute(
"SELECT indices, structure, sector_mood FROM macro_context_log "
"WHERE has_valid_data=1 ORDER BY created_at DESC LIMIT 1"
diff --git a/scripts/system_audit.py b/scripts/system_audit.py
index 5feb9c7a..cae99f4b 100644
--- a/scripts/system_audit.py
+++ b/scripts/system_audit.py
@@ -202,33 +202,18 @@ def audit_pipeline():
except Exception as e:
log_issue("数据管道", "HIGH", f"{name} 检查失败: {e}")
- # 特殊检查:holding_strategies和decisions.json是否为空
+ # 检查 holding_strategies 表策略数量
try:
- hs = conn.execute("SELECT COUNT(*) FROM holding_strategies").fetchone()[0]
- if hs == 0:
- log_issue("数据管道", "HIGH", "holding_strategies表为空(策略评估产出未写入)", fix="检查holding_strategies写入逻辑")
+ hs_count = conn.execute("SELECT COUNT(*) FROM holding_strategies WHERE status IN ('active','updated')").fetchone()[0]
+ if hs_count < 5:
+ log_issue("数据管道", "HIGH", f"holding_strategies 仅{hs_count}条策略(异常)", fix="检查策略写入逻辑")
else:
- log_ok("数据管道", f"holding_strategies {hs}条")
+ log_ok("数据管道", f"holding_strategies {hs_count}条策略")
except Exception as e:
log_issue("数据管道", "HIGH", f"holding_strategies检查失败: {e}")
conn.close()
-
- # 检查decisions.json文件
- try:
- import json
- with open(WEB_DATA / "decisions.json") as f:
- dec = json.load(f)
- cnt = len(dec.get("decisions", []))
- if cnt < 5:
- log_issue("数据管道", "HIGH", f"decisions.json仅{cnt}条决策(异常)", fix="检查decisions.json写入逻辑")
- else:
- log_ok("数据管道", f"decisions.json {cnt}条决策")
- except Exception as e:
- log_issue("数据管道", "HIGH", f"decisions.json读取失败: {e}")
-
-
-# ── 7. 系统服务 ──
+
def audit_services():
services = [
("Dashboard", "http://127.0.0.1:8899/", "200"),
diff --git a/scripts/system_health_check.py b/scripts/system_health_check.py
index b22dbcde..8a8207fa 100644
--- a/scripts/system_health_check.py
+++ b/scripts/system_health_check.py
@@ -90,15 +90,15 @@ def run():
try:
from mo_data import read_portfolio, read_decisions, read_watchlist
pf = read_portfolio()
- lines.append(check(len(pf.get("holdings", [])) > 0, f"portfolio.json DB记录: {len(pf.get('holdings', []))}条"))
+ lines.append(check(len(pf.get("holdings", [])) > 0, f"持仓 DB记录: {len(pf.get('holdings', []))}条"))
ok_count += 1
wl = read_watchlist()
- lines.append(check(len(wl.get("stocks", [])) > 0, f"watchlist.json DB记录: {len(wl.get('stocks', []))}条"))
+ lines.append(check(len(wl.get("stocks", [])) > 0, f"自选股 DB记录: {len(wl.get('stocks', []))}条"))
ok_count += 1
dec = read_decisions()
# 注意:holding_strategies 表是决策的DB版本
dec_count = len(dec.get("decisions", []))
- lines.append(check(dec_count > 0, f"decisions.json 记录: {dec_count}条(DB holding_strategies表: {len(pf.get('holdings',[]))}只)"))
+ lines.append(check(dec_count > 0, f"DB策略记录: {dec_count}条"))
ok_count += 1
except Exception:
lines.append(check(False, "MoFin DB 数据读取失败"))
diff --git a/scripts/technical_analysis.py b/scripts/technical_analysis.py
index 1f3037d9..5a6ddcae 100644
--- a/scripts/technical_analysis.py
+++ b/scripts/technical_analysis.py
@@ -472,7 +472,7 @@ def analyze_volume_deep(code):
import sqlite3
from pathlib import Path
- DATA_DIR = Path(__file__).parent / "data"
+ DATA_DIR = Path(__file__).parent.parent / "data"
try:
conn = sqlite3.connect(str(DATA_DIR / "mofin.db"))
row = conn.execute("SELECT cache_json FROM mtf_cache WHERE code=?", (code,)).fetchone()
diff --git a/scripts/xiaoguo_signal_consumer.py b/scripts/xiaoguo_signal_consumer.py
index 59ea3195..23416633 100644
--- a/scripts/xiaoguo_signal_consumer.py
+++ b/scripts/xiaoguo_signal_consumer.py
@@ -246,7 +246,7 @@ def main():
if action == "watchlist":
# 加自选
results.append(f"✅ {sector_name}({code}): {summary}")
- # 写入 watchlist.json
+ # 写入 watchlist_stocks 表(DB)
try:
wl = read_watchlist()
wl.setdefault("stocks", [])
diff --git a/static/mofin_health.json b/static/mofin_health.json
new file mode 100644
index 00000000..7315512d
--- /dev/null
+++ b/static/mofin_health.json
@@ -0,0 +1,1791 @@
+{
+ "generated_at": "2026-07-08 12:30:50",
+ "feature_tree": {
+ "label": "MoFin 系统",
+ "status": "warn",
+ "children": [
+ {
+ "label": "数据采集",
+ "status": "warn",
+ "children": [
+ {
+ "label": "市场快照 (market_watch.py)",
+ "status": "ok"
+ },
+ {
+ "label": "宏观新闻 (macro_context_collector.py)",
+ "status": "ok"
+ },
+ {
+ "label": "价格监控 (price_monitor.py)",
+ "status": "ok"
+ },
+ {
+ "label": "小果扫描 (xiaoguo_scanner.py)",
+ "status": "warn"
+ },
+ {
+ "label": "资金流采集 (capital_flow_collector.py)",
+ "status": "ok"
+ },
+ {
+ "label": "宏观上下文刷新 (refresh_macro_context.py)",
+ "status": "ok"
+ }
+ ]
+ },
+ {
+ "label": "策略分析",
+ "status": "ok",
+ "children": [
+ {
+ "label": "策略重评 (mofin_collect→reassess_with_context)",
+ "status": "ok"
+ },
+ {
+ "label": "持仓自选新鲜度检查",
+ "status": "ok"
+ },
+ {
+ "label": "自选买入区提醒 (stale_push_wlin.py)",
+ "status": "ok"
+ },
+ {
+ "label": "策略评估 (strategy_evaluator.py)",
+ "status": "ok"
+ },
+ {
+ "label": "分支自成长 (branch_scanner.py)",
+ "status": "ok"
+ },
+ {
+ "label": "元自成长 (meta_growth.py)",
+ "status": "ok"
+ }
+ ]
+ },
+ {
+ "label": "推荐推送",
+ "status": "warn",
+ "children": [
+ {
+ "label": "MoFin盘前中监控 (LLM cron)",
+ "status": "warn"
+ },
+ {
+ "label": "MoFin午后监控 (LLM cron)",
+ "status": "warn"
+ },
+ {
+ "label": "cron报告推XMPP (cron_to_xmpp.py)",
+ "status": "ok"
+ },
+ {
+ "label": "开盘简报 (LLM cron)",
+ "status": "ok"
+ },
+ {
+ "label": "收盘简报 (LLM cron)",
+ "status": "ok"
+ },
+ {
+ "label": "市场精选推荐 (LLM cron)",
+ "status": "ok"
+ }
+ ]
+ },
+ {
+ "label": "自检/审计",
+ "status": "ok",
+ "children": [
+ {
+ "label": "系统全局审计 (system_audit.py)",
+ "status": "ok"
+ },
+ {
+ "label": "全局cron健康监控 (cron_health_monitor.py)",
+ "status": "ok"
+ },
+ {
+ "label": "重评管道审计 (verify_reassess_pipeline.py)",
+ "status": "ok"
+ },
+ {
+ "label": "系统体检 (morning_health_check.py)",
+ "status": "ok"
+ },
+ {
+ "label": "盘中自检 (intraday_health_check.py)",
+ "status": "ok"
+ },
+ {
+ "label": "记忆守卫 (memory_guardian.py)",
+ "status": "ok"
+ },
+ {
+ "label": "硬编码扫描 (hardcode_scanner.py)",
+ "status": "ok"
+ }
+ ]
+ },
+ {
+ "label": "风险监控",
+ "status": "ok",
+ "children": [
+ {
+ "label": "宏观风险扫描 (LLM cron)",
+ "status": "ok"
+ },
+ {
+ "label": "宏观风险信号消费 (macro_signal_consumer.py)",
+ "status": "ok"
+ },
+ {
+ "label": "跨市场背离检测 (divergence_detector.py)",
+ "status": "ok"
+ }
+ ]
+ },
+ {
+ "label": "执行/修复",
+ "status": "ok",
+ "children": [
+ {
+ "label": "自愈执行器 (self_todo_executor.py)",
+ "status": "ok"
+ },
+ {
+ "label": "策略质量门禁 (review_needed_watchdog.py)",
+ "status": "ok"
+ },
+ {
+ "label": "自选自动清理 (clean_watchlist.py)",
+ "status": "ok"
+ },
+ {
+ "label": "建议对账 (advice_reconciliation.py)",
+ "status": "ok"
+ }
+ ]
+ },
+ {
+ "label": "系统服务",
+ "status": "ok",
+ "children": [
+ {
+ "label": "XMPP Bot (zhiwei)",
+ "status": "ok"
+ },
+ {
+ "label": "Gateway (8643)",
+ "status": "ok"
+ },
+ {
+ "label": "HTTP Bridge (5805)",
+ "status": "ok"
+ },
+ {
+ "label": "Dashboard (8899)",
+ "status": "ok"
+ },
+ {
+ "label": "state.db SQLite",
+ "status": "ok"
+ }
+ ]
+ }
+ ]
+ },
+ "entities": [
+ {
+ "name": "accuracy_stats",
+ "desc": "建议准确率统计",
+ "rows": 1,
+ "readers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "writers": [
+ "strategy_review",
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "advice_timeline",
+ "desc": "建议执行时间线",
+ "rows": 12735,
+ "readers": [
+ "advice_reconciliation",
+ "mofin_db",
+ "migrate_all"
+ ],
+ "writers": [
+ "advice_reconciliation",
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "candidate_score_history",
+ "desc": "",
+ "rows": 133,
+ "readers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "writers": [
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "candidates",
+ "desc": "潜力股候选池(小果扫描产出)",
+ "rows": 12,
+ "readers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "writers": [
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "capital_flow_cache",
+ "desc": "资金流缓存",
+ "rows": 0,
+ "readers": [
+ "mofin_db"
+ ],
+ "writers": [
+ "mofin_db"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "cash_log",
+ "desc": "资金变动记录",
+ "rows": 13,
+ "readers": [
+ "mofin_db"
+ ],
+ "writers": [
+ "mofin_db",
+ "mo_data"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "health_check_log",
+ "desc": "健康检查日志",
+ "rows": 12,
+ "readers": [
+ "morning_health_check"
+ ],
+ "writers": [
+ "morning_health_check"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "holding_strategies",
+ "desc": "每只股票的完整策略参数",
+ "rows": 0,
+ "readers": [
+ "stale_push_wlin",
+ "deep_check_01888",
+ "strategy_lifecycle",
+ "fix_and_regenerate",
+ "mofin_collect",
+ "run_all_tests",
+ "system_audit",
+ "pre-flight-check",
+ "verify_reassess_pipeline",
+ "migrate_all"
+ ],
+ "writers": [
+ "fix_and_regenerate",
+ "fix_decisions_currency",
+ "migrate_all",
+ "data_governance",
+ "mofin_db",
+ "rollback_currency"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": true
+ },
+ {
+ "name": "holdings",
+ "desc": "当前持仓(权威源)",
+ "rows": 19,
+ "readers": [
+ "stale_push_wlin",
+ "fix_summary",
+ "server",
+ "fix_currency_and_summary",
+ "refresh_mtf_cache",
+ "migrate_all",
+ "mo_alphasift_bridge",
+ "per_stock_reassess",
+ "verify_01888",
+ "rollback_currency"
+ ],
+ "writers": [
+ "fix_currency_and_summary",
+ "migrate_all",
+ "fix_hk_cost_cny",
+ "import_holding_xls",
+ "mofin_db",
+ "rollback_currency"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "live_prices",
+ "desc": "所有持仓+自选最新实时价",
+ "rows": 19,
+ "readers": [
+ "cron_health_monitor",
+ "verify_reassess_pipeline",
+ "mofin_db",
+ "system_audit"
+ ],
+ "writers": [
+ "mofin_db"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "macro_context_log",
+ "desc": "宏观上下文(大盘偏向/指数)",
+ "rows": 7,
+ "readers": [
+ "stale_push_wlin",
+ "divergence_detector",
+ "strategy_lifecycle",
+ "stock_profile",
+ "system_audit",
+ "xiaoguo_signal_consumer",
+ "strategy_tree"
+ ],
+ "writers": [
+ "refresh_macro_context"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "macro_raw_news",
+ "desc": "宏观新闻原始数据",
+ "rows": 7944,
+ "readers": [
+ "macro_context_collector",
+ "system_audit"
+ ],
+ "writers": [
+ "macro_context_collector"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "market_snapshots",
+ "desc": "大盘指数快照(每10分)",
+ "rows": 525,
+ "readers": [
+ "trend_detector",
+ "system_audit",
+ "mofin_query",
+ "market_screener",
+ "mofin_db"
+ ],
+ "writers": [
+ "mofin_db"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "mtf_cache",
+ "desc": "多周期均线缓存",
+ "rows": 34,
+ "readers": [
+ "technical_analysis",
+ "multi_timeframe",
+ "mofin_db"
+ ],
+ "writers": [
+ "multi_timeframe",
+ "mofin_db"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "portfolio_state",
+ "desc": "组合状态快照(只读派生)",
+ "rows": 1,
+ "readers": [],
+ "writers": [],
+ "has_input": false,
+ "has_output": false,
+ "orphan": true,
+ "warn": true
+ },
+ {
+ "name": "portfolio_summary",
+ "desc": "总资产/现金/仓位汇总",
+ "rows": 1,
+ "readers": [
+ "fix_summary",
+ "verify_pnl",
+ "fix_currency_and_summary",
+ "migrate_all",
+ "check_price_monitor",
+ "price_monitor",
+ "mo_data",
+ "rollback_currency",
+ "import_holding_xls",
+ "mofin_db"
+ ],
+ "writers": [
+ "fix_summary",
+ "fix_currency_and_summary",
+ "migrate_all",
+ "import_holding_xls",
+ "mofin_db",
+ "rollback_currency"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "price_events",
+ "desc": "价格区间突破事件日志",
+ "rows": 2289,
+ "readers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "writers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "sector_signals",
+ "desc": "行业信号(趋势检测产出)",
+ "rows": 629,
+ "readers": [
+ "mofin_news",
+ "xiaoguo_news_processor",
+ "trend_detector",
+ "server"
+ ],
+ "writers": [
+ "mofin_news",
+ "xiaoguo_news_processor",
+ "trend_detector"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "sector_snapshots",
+ "desc": "行业板块数据",
+ "rows": 42310,
+ "readers": [
+ "strategy_lifecycle",
+ "mofin_db",
+ "trend_detector",
+ "market_screener"
+ ],
+ "writers": [
+ "mofin_db"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "signal_news",
+ "desc": "信号相关新闻",
+ "rows": 984,
+ "readers": [
+ "xiaoguo_signal_consumer",
+ "system_audit",
+ "server",
+ "macro_signal_consumer",
+ "intraday_health_check"
+ ],
+ "writers": [
+ "divergence_detector",
+ "xiaoguo_news_processor",
+ "xiaoguo_signal_consumer",
+ "macro_context_collector",
+ "xiaoguo_scanner",
+ "macro_signal_consumer",
+ "mofin_news"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "sqlite_sequence",
+ "desc": "",
+ "rows": 16,
+ "readers": [],
+ "writers": [],
+ "has_input": false,
+ "has_output": false,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "state_meta",
+ "desc": "系统状态元数据",
+ "rows": 1,
+ "readers": [
+ "xiaoguo_scanner"
+ ],
+ "writers": [
+ "xiaoguo_scanner"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "stock_daily",
+ "desc": "日线行情",
+ "rows": 8615,
+ "readers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "writers": [
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "stock_fundamentals",
+ "desc": "基本面数据(PE/PB)",
+ "rows": 37,
+ "readers": [
+ "strategy_lifecycle",
+ "migrate_all"
+ ],
+ "writers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "stock_monthly",
+ "desc": "月线行情",
+ "rows": 1196,
+ "readers": [
+ "migrate_all"
+ ],
+ "writers": [
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "stock_sectors",
+ "desc": "股票行业映射",
+ "rows": 62,
+ "readers": [
+ "strategy_lifecycle",
+ "xiaoguo_news_processor",
+ "trend_detector",
+ "migrate_all",
+ "mofin_news",
+ "mofin_db"
+ ],
+ "writers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "stock_weekly",
+ "desc": "周线行情",
+ "rows": 1988,
+ "readers": [
+ "migrate_all"
+ ],
+ "writers": [
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "stocks",
+ "desc": "全量股票代码",
+ "rows": 84,
+ "readers": [
+ "xiaoguo_news_processor",
+ "trend_detector",
+ "migrate_all",
+ "mofin_news",
+ "mofin_db"
+ ],
+ "writers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "strategy_evaluations",
+ "desc": "策略重评历史记录",
+ "rows": 217,
+ "readers": [
+ "verify_reassess_pipeline",
+ "mofin_db",
+ "migrate_all",
+ "system_audit"
+ ],
+ "writers": [
+ "migrate_all",
+ "mofin_collect"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "strategy_feedback",
+ "desc": "策略效果反馈",
+ "rows": 222,
+ "readers": [
+ "mofin_db",
+ "migrate_all"
+ ],
+ "writers": [
+ "migrate_all"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "todos",
+ "desc": "自愈任务队列",
+ "rows": 61,
+ "readers": [
+ "morning_health_check",
+ "intraday_health_check",
+ "strategy-staleness-check",
+ "self_todo_executor"
+ ],
+ "writers": [
+ "morning_health_check",
+ "mofin_collect",
+ "self_todo_executor",
+ "intraday_health_check",
+ "strategy-staleness-check"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "watchlist_stocks",
+ "desc": "自选股列表",
+ "rows": 39,
+ "readers": [
+ "stale_detector",
+ "stale_push_wlin",
+ "refresh_mtf_cache",
+ "trend_detector",
+ "run_all_tests",
+ "mofin_collect",
+ "xiaoguo_signal_consumer",
+ "xiaoguo_scanner",
+ "migrate_all",
+ "price_monitor"
+ ],
+ "writers": [
+ "mofin_db",
+ "migrate_all",
+ "per_stock_reassess"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ },
+ {
+ "name": "xiaoguo_scan_tracker",
+ "desc": "小果扫描跟踪",
+ "rows": 464,
+ "readers": [
+ "xiaoguo_scanner",
+ "server"
+ ],
+ "writers": [
+ "xiaoguo_scanner"
+ ],
+ "has_input": true,
+ "has_output": true,
+ "orphan": false,
+ "warn": false
+ }
+ ],
+ "json_files": [
+ {
+ "name": "accuracy_stats.json",
+ "desc": "",
+ "size_kb": 7.3,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-06 11:32",
+ "warn": true
+ },
+ {
+ "name": "analysis_output.json",
+ "desc": "",
+ "size_kb": 0.8,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "candidate_pool.json",
+ "desc": "潜力股候选池完整数据",
+ "size_kb": 43.6,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-07 14:07",
+ "warn": true
+ },
+ {
+ "name": "capital_flow_cache.json",
+ "desc": "资金流缓存",
+ "size_kb": 0.1,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-01 15:32",
+ "warn": true
+ },
+ {
+ "name": "daily_reviews.json",
+ "desc": "",
+ "size_kb": 9.0,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "decisions.json",
+ "desc": "策略决策(DB→JSON同步,兼容层)",
+ "size_kb": 21.8,
+ "readers": [
+ "deep_check_01888",
+ "import_holding_xls",
+ "inspect_decisions",
+ "get_realtime_prices"
+ ],
+ "writers": [],
+ "last_modified": "07-08 11:42",
+ "warn": false
+ },
+ {
+ "name": "decisions_backup.json",
+ "desc": "",
+ "size_kb": 9.8,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "decisions_backup_1129.json",
+ "desc": "",
+ "size_kb": 18.5,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "decisions_backup_before_tagfix.json",
+ "desc": "",
+ "size_kb": 133.7,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "evaluation.json",
+ "desc": "",
+ "size_kb": 51.1,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "evaluation_input.json",
+ "desc": "",
+ "size_kb": 48.4,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-06 11:32",
+ "warn": true
+ },
+ {
+ "name": "growth_registry.json",
+ "desc": "",
+ "size_kb": 1.6,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-07 12:45",
+ "warn": true
+ },
+ {
+ "name": "hardcode_audit.json",
+ "desc": "",
+ "size_kb": 3385.3,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-07 17:25",
+ "warn": true
+ },
+ {
+ "name": "health_checklist.json",
+ "desc": "",
+ "size_kb": 12.2,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 09:59",
+ "warn": true
+ },
+ {
+ "name": "live_prices.json",
+ "desc": "",
+ "size_kb": 2.8,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-06 11:00",
+ "warn": true
+ },
+ {
+ "name": "macro_context.json",
+ "desc": "宏观上下文JSON(旧兼容层)",
+ "size_kb": 3.5,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-06 11:32",
+ "warn": true
+ },
+ {
+ "name": "macro_divergence_state.json",
+ "desc": "",
+ "size_kb": 0.3,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 12:30",
+ "warn": true
+ },
+ {
+ "name": "macro_risk_state.json",
+ "desc": "宏观风险状态(采集器写入)",
+ "size_kb": 1.6,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 12:30",
+ "warn": true
+ },
+ {
+ "name": "mofin_health.json",
+ "desc": "健康监控数据",
+ "size_kb": 41.4,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 12:23",
+ "warn": true
+ },
+ {
+ "name": "multi_tf_cache.json",
+ "desc": "多周期均线缓存",
+ "size_kb": 1006.0,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-06 11:32",
+ "warn": true
+ },
+ {
+ "name": "new_opportunities.json",
+ "desc": "",
+ "size_kb": 1.7,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "pipeline_registry.json",
+ "desc": "",
+ "size_kb": 22.8,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 08:01",
+ "warn": true
+ },
+ {
+ "name": "portfolio.json",
+ "desc": "持仓汇总(兼容层)",
+ "size_kb": 0.2,
+ "readers": [
+ "get_realtime_prices"
+ ],
+ "writers": [],
+ "last_modified": "07-08 09:58",
+ "warn": false
+ },
+ {
+ "name": "price_events.json",
+ "desc": "",
+ "size_kb": 247.1,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-06 11:32",
+ "warn": true
+ },
+ {
+ "name": "price_history.json",
+ "desc": "",
+ "size_kb": 11.7,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 12:29",
+ "warn": true
+ },
+ {
+ "name": "push_cooldown.json",
+ "desc": "",
+ "size_kb": 0.4,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-30 14:31",
+ "warn": true
+ },
+ {
+ "name": "scanner_state.json",
+ "desc": "",
+ "size_kb": 0.1,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 12:30",
+ "warn": true
+ },
+ {
+ "name": "sector_recommendation_test.json",
+ "desc": "",
+ "size_kb": 1.3,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "stock_name_code_cache.json",
+ "desc": "",
+ "size_kb": 1.7,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-22 19:58",
+ "warn": true
+ },
+ {
+ "name": "stock_profiles.json",
+ "desc": "",
+ "size_kb": 10.9,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "stock_sector_map.json",
+ "desc": "",
+ "size_kb": 2.0,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "strategy_feedback.json",
+ "desc": "",
+ "size_kb": 16.8,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "strategy_staleness_report.json",
+ "desc": "策略过期报告",
+ "size_kb": 0.3,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 09:00",
+ "warn": true
+ },
+ {
+ "name": "system_audit.json",
+ "desc": "",
+ "size_kb": 0.9,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-07 17:33",
+ "warn": true
+ },
+ {
+ "name": "system_audit_report.json",
+ "desc": "",
+ "size_kb": 2.1,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 11:13",
+ "warn": true
+ },
+ {
+ "name": "system_inventory.json",
+ "desc": "全量系统清单",
+ "size_kb": 354.8,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-08 11:34",
+ "warn": true
+ },
+ {
+ "name": "system_state.json",
+ "desc": "",
+ "size_kb": 1.1,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ },
+ {
+ "name": "xiaoguo_insights.json",
+ "desc": "小果分析洞察",
+ "size_kb": 2.2,
+ "readers": [],
+ "writers": [],
+ "last_modified": "07-07 16:14",
+ "warn": true
+ },
+ {
+ "name": "xiaoguo_sentiment.json",
+ "desc": "",
+ "size_kb": 2.7,
+ "readers": [],
+ "writers": [],
+ "last_modified": "06-20 12:15",
+ "warn": true
+ }
+ ],
+ "pipelines": [
+ {
+ "name": "300308入场信号紧盯",
+ "type": "no_agent",
+ "script": "monitor_300308.py",
+ "schedule": "*/2 13-14 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T14:58:22"
+ },
+ {
+ "name": "300308午后紧盯",
+ "type": "no_agent",
+ "script": "300308_monitor.py",
+ "schedule": "*/2 13-14 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T14:58:22"
+ },
+ {
+ "name": "Cron监护-高频",
+ "type": "no_agent",
+ "script": "cron_watchdog.py",
+ "schedule": "*/15 9-16 * * 1-5",
+ "status": "error",
+ "last_run": "2026-07-08T12:15:29"
+ },
+ {
+ "name": "MoFin 午后监控",
+ "type": "LLM",
+ "script": "mofin_collect.py",
+ "schedule": "5,20,35,50 13-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T15:56:05"
+ },
+ {
+ "name": "MoFin 盘前中监控",
+ "type": "LLM",
+ "script": "mofin_collect.py",
+ "schedule": "25,40,55,10 9-11 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:20:23"
+ },
+ {
+ "name": "MoFin 系统常规体检-开盘前",
+ "type": "no_agent",
+ "script": "morning_health_check.py",
+ "schedule": "0 8 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T08:01:34"
+ },
+ {
+ "name": "bot-process-monitor",
+ "type": "no_agent",
+ "script": "bot_monitor.sh",
+ "schedule": "every 60m",
+ "status": "ok",
+ "last_run": "2026-07-08T11:39:34"
+ },
+ {
+ "name": "cron-推XMPP中继",
+ "type": "no_agent",
+ "script": "cron_to_xmpp.py",
+ "schedule": "* 9-16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:29:57"
+ },
+ {
+ "name": "cron报告推XMPP-每5分",
+ "type": "no_agent",
+ "script": "cron_to_xmpp.py",
+ "schedule": "*/5 9-16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:25:17"
+ },
+ {
+ "name": "evolution-pulse",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 * * * *",
+ "status": "ok",
+ "last_run": "2026-07-08T12:08:01"
+ },
+ {
+ "name": "state.db真空整理-每周",
+ "type": "no_agent",
+ "script": "vacuum_state_db.py",
+ "schedule": "0 3 * * 6",
+ "status": null,
+ "last_run": "None"
+ },
+ {
+ "name": "wiki-self-growth",
+ "type": "LLM",
+ "script": "wiki_health_aggregate.py",
+ "schedule": "0 3 * * *",
+ "status": "ok",
+ "last_run": "2026-07-08T03:07:57"
+ },
+ {
+ "name": "xiaoguo-quick-scan",
+ "type": "no_agent",
+ "script": "xiaoguo_quick_scan.py",
+ "schedule": "*/15 * * * *",
+ "status": "ok",
+ "last_run": "2026-07-08T12:15:33"
+ },
+ {
+ "name": "xiaoguo-tunnel-watchdog",
+ "type": "no_agent",
+ "script": "xiaoguo_tunnel_watchdog.sh",
+ "schedule": "*/5 * * * *",
+ "status": "ok",
+ "last_run": "2026-07-08T12:25:55"
+ },
+ {
+ "name": "价格监控-高频",
+ "type": "no_agent",
+ "script": "price_monitor.py",
+ "schedule": "*/2 9-16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:28:34"
+ },
+ {
+ "name": "健康监控数据采集-每15分",
+ "type": "no_agent",
+ "script": "mofin_health.py",
+ "schedule": "*/15 9-16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:16:04"
+ },
+ {
+ "name": "元自成长-每日",
+ "type": "no_agent",
+ "script": "meta_growth.py",
+ "schedule": "45 0,12 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T00:45:33"
+ },
+ {
+ "name": "全局cron健康监控-每10分",
+ "type": "no_agent",
+ "script": "cron_health_monitor.py",
+ "schedule": "*/10 9-16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:21:40"
+ },
+ {
+ "name": "分支剪枝-每日",
+ "type": "no_agent",
+ "script": "prune_branches.py",
+ "schedule": "0 21 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T21:00:02"
+ },
+ {
+ "name": "分支自成长-盘中",
+ "type": "no_agent",
+ "script": "branch_scanner.py",
+ "schedule": "15,30,45,00 9-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:15:57"
+ },
+ {
+ "name": "分析师-持仓复查",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 20 * * 4",
+ "status": "ok",
+ "last_run": "2026-07-02T20:12:33"
+ },
+ {
+ "name": "多周期缓存刷新-开盘前",
+ "type": "no_agent",
+ "script": "refresh_mtf_cache.py",
+ "schedule": "0 9 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T09:01:47"
+ },
+ {
+ "name": "多周期缓存刷新-盘中",
+ "type": "no_agent",
+ "script": "refresh_mtf_cache.py",
+ "schedule": "0 11,14 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T11:02:47"
+ },
+ {
+ "name": "大脑任务执行",
+ "type": "LLM",
+ "script": "",
+ "schedule": "{'kind': 'cron', 'expr': '*/10 * * * *'}",
+ "status": "ok",
+ "last_run": "2026-07-08T12:21:19"
+ },
+ {
+ "name": "宏观上下文刷新-每30分",
+ "type": "no_agent",
+ "script": "refresh_macro_context.py",
+ "schedule": "*/30 9-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:01:30"
+ },
+ {
+ "name": "宏观新闻采集",
+ "type": "no_agent",
+ "script": "macro_context_collector.py",
+ "schedule": "*/30 8-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:01:37"
+ },
+ {
+ "name": "宏观新闻采集-周末",
+ "type": "no_agent",
+ "script": "macro_context_collector.py",
+ "schedule": "0 8-16/2 * * 0,6",
+ "status": "ok",
+ "last_run": "2026-07-05T16:00:46"
+ },
+ {
+ "name": "宏观采集-午间",
+ "type": "no_agent",
+ "script": "macro_context_collector.py",
+ "schedule": "0 12 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:01:43"
+ },
+ {
+ "name": "宏观采集-早盘",
+ "type": "no_agent",
+ "script": "macro_context_collector.py",
+ "schedule": "35 9 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T09:35:52"
+ },
+ {
+ "name": "宏观风险信号消费",
+ "type": "no_agent",
+ "script": "macro_signal_consumer.py",
+ "schedule": "5,20,35,50 9-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:21:09"
+ },
+ {
+ "name": "宏观风险扫描",
+ "type": "LLM",
+ "script": null,
+ "schedule": "30 8 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T08:39:56"
+ },
+ {
+ "name": "宏观风险扫描-午间",
+ "type": "LLM",
+ "script": null,
+ "schedule": "30 11 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T11:37:35"
+ },
+ {
+ "name": "宏观风险扫描-周末",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 10 * * 0,6",
+ "status": "ok",
+ "last_run": "2026-07-05T10:09:55"
+ },
+ {
+ "name": "小果信号消费-盘中",
+ "type": "no_agent",
+ "script": "xiaoguo_signal_consumer.py",
+ "schedule": "0,15,30,45 9-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:15:58"
+ },
+ {
+ "name": "小果市场筛选-全市场",
+ "type": "no_agent",
+ "script": "market_screener.py",
+ "schedule": "35 15 * * 1-5",
+ "status": "error",
+ "last_run": "2026-07-07T15:37:36"
+ },
+ {
+ "name": "小果情感分析",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T16:16:08"
+ },
+ {
+ "name": "小果独立扫描",
+ "type": "no_agent",
+ "script": "xiaoguo_scanner.py",
+ "schedule": "*/5 9-15 * * 1-5",
+ "status": "error",
+ "last_run": "2026-07-08T12:27:20"
+ },
+ {
+ "name": "市场数据采集",
+ "type": "no_agent",
+ "script": "market_watch.py",
+ "schedule": "*/10 9-11,13-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T11:50:54"
+ },
+ {
+ "name": "市场数据采集",
+ "type": "no_agent",
+ "script": "market_watch.py",
+ "schedule": "0,30 9-16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:01:19"
+ },
+ {
+ "name": "市场精选推荐-每日",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T16:12:45"
+ },
+ {
+ "name": "建议对账-每周",
+ "type": "no_agent",
+ "script": "advice_reconciliation.py",
+ "schedule": "0 20 * * 6",
+ "status": "ok",
+ "last_run": "2026-07-04T20:00:43"
+ },
+ {
+ "name": "开盘简报",
+ "type": "LLM",
+ "script": null,
+ "schedule": "35 9 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T09:43:31"
+ },
+ {
+ "name": "持仓情报-盘后",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 20 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T20:20:45"
+ },
+ {
+ "name": "收盘简报",
+ "type": "LLM",
+ "script": null,
+ "schedule": "10 16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T16:15:26"
+ },
+ {
+ "name": "数据同步-dashboard",
+ "type": "no_agent",
+ "script": "sync_dashboard.py",
+ "schedule": "55 8 * * *",
+ "status": "ok",
+ "last_run": "2026-07-08T08:55:56"
+ },
+ {
+ "name": "数据治理-每周",
+ "type": "no_agent",
+ "script": "data_governance.py",
+ "schedule": "0 10 * * 6",
+ "status": "error",
+ "last_run": "2026-07-04T10:00:57"
+ },
+ {
+ "name": "数据采集-策略评估前",
+ "type": "no_agent",
+ "script": "collect_evaluation_data.py",
+ "schedule": "30 20 * * 1-5",
+ "status": "error",
+ "last_run": "2026-07-07T20:30:58"
+ },
+ {
+ "name": "梦境循环-知识库归并",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 22 * * *",
+ "status": "ok",
+ "last_run": "2026-07-07T22:18:30"
+ },
+ {
+ "name": "盘中自检-高频",
+ "type": "no_agent",
+ "script": "intraday_health_check.py",
+ "schedule": "*/15 9-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:15:59"
+ },
+ {
+ "name": "盘前热点扫描",
+ "type": "LLM",
+ "script": null,
+ "schedule": "30 8 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T08:39:50"
+ },
+ {
+ "name": "知微周复盘",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 22 * * 0",
+ "status": "ok",
+ "last_run": "2026-07-05T22:05:42"
+ },
+ {
+ "name": "知微洞察生成",
+ "type": "no_agent",
+ "script": "market_insight.py",
+ "schedule": "35 15 * * 1-5",
+ "status": "error",
+ "last_run": "2026-07-07T15:35:35"
+ },
+ {
+ "name": "知识研究-日常",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 22 * * *",
+ "status": "ok",
+ "last_run": "2026-07-07T22:10:24"
+ },
+ {
+ "name": "硬编码扫描-每日",
+ "type": "no_agent",
+ "script": "hardcode_scanner.py",
+ "schedule": "25 17 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T17:25:58"
+ },
+ {
+ "name": "策略复盘-每日",
+ "type": "no_agent",
+ "script": "strategy_review.py",
+ "schedule": "0 20 * * 1-5",
+ "status": "error",
+ "last_run": "2026-07-07T20:00:53"
+ },
+ {
+ "name": "策略时效性检查(日)",
+ "type": "LLM",
+ "script": "strategy-staleness-check.py",
+ "schedule": "0 9 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T09:10:49"
+ },
+ {
+ "name": "策略评估-每周",
+ "type": "no_agent",
+ "script": "strategy_evaluator.py",
+ "schedule": "0 21 * * 6",
+ "status": "error",
+ "last_run": "2026-07-04T21:00:51"
+ },
+ {
+ "name": "策略评估-每日",
+ "type": "LLM",
+ "script": "stale_detector.py",
+ "schedule": "0 21 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T21:16:47"
+ },
+ {
+ "name": "策略质量门禁-重评跟进",
+ "type": "no_agent",
+ "script": "review_needed_watchdog.py",
+ "schedule": "0,30 9-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:30:45"
+ },
+ {
+ "name": "系统健康检查-开盘前",
+ "type": "LLM",
+ "script": "system_health_check.py",
+ "schedule": "0 9 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T09:32:03"
+ },
+ {
+ "name": "系统健康检查-每日",
+ "type": "no_agent",
+ "script": "system_health_check.py",
+ "schedule": "0 9 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T09:01:03"
+ },
+ {
+ "name": "系统全局审计",
+ "type": "LLM",
+ "script": "system_audit.py",
+ "schedule": "30 17 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T17:34:37"
+ },
+ {
+ "name": "自愈执行器-TODO自动处理",
+ "type": "no_agent",
+ "script": "self_todo_executor.py",
+ "schedule": "*/10 8-22 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:30:47"
+ },
+ {
+ "name": "自成长知识库-22:10中继推送",
+ "type": "no_agent",
+ "script": "relay_knowledge_report.py",
+ "schedule": "10 22 * * *",
+ "status": "ok",
+ "last_run": "2026-07-07T22:10:03"
+ },
+ {
+ "name": "自选买入区提醒",
+ "type": "no_agent",
+ "script": "stale_push_wlin.py",
+ "schedule": "1,31 9-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:02:34"
+ },
+ {
+ "name": "自选买入区提醒-盘前午间尾盘",
+ "type": "no_agent",
+ "script": "per_stock_reassess.py",
+ "schedule": "0 9,12,15 * * 1-5",
+ "status": "error",
+ "last_run": "2026-07-08T12:03:23"
+ },
+ {
+ "name": "自选股体检-每周",
+ "type": "LLM",
+ "script": null,
+ "schedule": "0 20 * * 6",
+ "status": "ok",
+ "last_run": "2026-07-04T20:13:24"
+ },
+ {
+ "name": "自选股自动重评-周末",
+ "type": "no_agent",
+ "script": "stale_detector.py",
+ "schedule": "30 10 * * 0,6",
+ "status": null,
+ "last_run": "None"
+ },
+ {
+ "name": "自选自动清理-开盘前",
+ "type": "no_agent",
+ "script": "clean_watchlist.py",
+ "schedule": "5 9 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T09:05:08"
+ },
+ {
+ "name": "芯碁微装-价格监控",
+ "type": "LLM",
+ "script": "price_monitor.py",
+ "schedule": "0 10,14 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-07T14:13:27"
+ },
+ {
+ "name": "记忆守卫-每日",
+ "type": "no_agent",
+ "script": "memory_guardian.py",
+ "schedule": "0 7 * * *",
+ "status": "ok",
+ "last_run": "2026-07-08T07:01:31"
+ },
+ {
+ "name": "记忆守卫-每日",
+ "type": "no_agent",
+ "script": "memory_guardian.py",
+ "schedule": "0 7 * * *",
+ "status": "ok",
+ "last_run": "2026-07-08T07:02:21"
+ },
+ {
+ "name": "资金流采集-盘中",
+ "type": "no_agent",
+ "script": "capital_flow_collector.py",
+ "schedule": "1,31 9-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:02:26"
+ },
+ {
+ "name": "跨市场背离检测",
+ "type": "no_agent",
+ "script": "divergence_detector.py",
+ "schedule": "*/30 8-15 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:01:20"
+ },
+ {
+ "name": "跨市场背离检测-周末",
+ "type": "no_agent",
+ "script": "divergence_detector.py",
+ "schedule": "30 8-16/2 * * 0,6",
+ "status": "ok",
+ "last_run": "2026-07-05T16:30:50"
+ },
+ {
+ "name": "重评管道审计-每30分",
+ "type": "no_agent",
+ "script": "verify_reassess_pipeline.py",
+ "schedule": "*/30 9-16 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T12:01:24"
+ },
+ {
+ "name": "集合竞价观察",
+ "type": "LLM",
+ "script": null,
+ "schedule": "28 9 * * 1-5",
+ "status": "ok",
+ "last_run": "2026-07-08T09:46:38"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/strategy_lifecycle.py.bak.1783484714 b/strategy_lifecycle.py.bak.1783484714
new file mode 100644
index 00000000..02643989
--- /dev/null
+++ b/strategy_lifecycle.py.bak.1783484714
@@ -0,0 +1,2610 @@
+#!/usr/bin/env python3
+"""策略生命周期管理系统 — 技术面驱动版本 v2
+
+核心原则:
+1. 止损放在合理的技术位,不拍数字
+2. 新买入推荐:止损=弱支撑(约3%跌幅),止盈=强压力,盈亏比≥2:1
+3. 已持仓:止损=强支撑(约5-8%跌幅),目标=强压力
+4. 买入区间:弱支撑~弱压力之间
+5. 买入时机:量价齐跌不买,缩量至支撑买,量价齐升追买
+"""
+
+import json
+import urllib.request
+import os
+import sys
+import re
+from datetime import datetime
+import technical_analysis as ta
+import multi_timeframe as mtf
+from mo_data import read_portfolio, read_decisions, read_watchlist
+from mo_models import is_hk_stock, to_cny, get_hk_rate
+from strategy_tree import detect_scenario
+
+# ─── 策略准入门禁 — 硬性质量红线 ───────────────────────────────
+# 每一条策略写入前必须过此门禁。不过的不得写入DB/JSON,
+# 必须触发重评修复。代码层面硬拦截,不依赖prompt或文档。
+#
+# 规则列表 + 严重程度 + 修复建议
+STRATEGY_QUALITY_GATES = [
+ {
+ "id": "GATE_LOSS_EXISTS",
+ "desc": "止损必须存在且>0",
+ "check": lambda d: (d.get("stop_loss") or 0) > 0,
+ "severity": "CRITICAL",
+ "fix": "调用 technical_analysis 计算支撑位设置止损"
+ },
+ {
+ "id": "GATE_PROFIT_EXISTS",
+ "desc": "止盈必须存在且>0(纯自选股可放宽)",
+ "check": lambda d: (d.get("take_profit") or 0) > 0,
+ "severity": "CRITICAL",
+ "fix": "调用 technical_analysis 计算阻力位设置止盈目标"
+ },
+ {
+ "id": "GATE_SL_GTE_LOW",
+ "desc": "止损必须 ≤ 买入区下沿",
+ "check": lambda d: (d.get("stop_loss") or 0) <= (d.get("entry_low") or 99999),
+ "severity": "HIGH",
+ "fix": "止损不能高于买入区,调整止损至买入区以下"
+ },
+ {
+ "id": "GATE_ENTRY_RANGE",
+ "desc": "买入区下沿 < 上沿",
+ "check": lambda d: (d.get("entry_low") or 0) < (d.get("entry_high") or 0),
+ "severity": "CRITICAL",
+ "fix": "entry_low=现价×0.95, entry_high=现价×1.05 取近似区间"
+ },
+ {
+ "id": "GATE_RR_COMPUTED",
+ "desc": "买入推荐必须含RR",
+ "check": lambda d: not ("买入" in (d.get("timing_signal") or "") or "加仓" in (d.get("timing_signal") or "")) or (d.get("rr_ratio") or 0) > 0,
+ "severity": "HIGH",
+ "fix": "RR = (止盈-现价)/(现价-止损),数据齐全后自动算"
+ },
+ {
+ "id": "GATE_RR_MINIMUM",
+ "desc": "买入推荐RR≥1.5(非买入信号跳过)",
+ "check": lambda d: not ("买入" in (d.get("timing_signal") or "") or "加仓" in (d.get("timing_signal") or "")) or (d.get("rr_ratio") or 0) >= 1.5,
+ "severity": "HIGH",
+ "fix": "RR不足→signal降级为'信号不充分',不进推荐区"
+ },
+ {
+ "id": "GATE_SIGNAL_SHORT",
+ "desc": "timing_signal 必须是短词(2-4字)",
+ "check": lambda d: len((d.get("timing_signal") or "").strip().split()) <= 4 and (d.get("timing_signal") or "") not in ("neutral", ""),
+ "severity": "MEDIUM",
+ "fix": "使用短词:买入/加仓/观望/持有/关注/信号不充分"
+ },
+ {
+ "id": "GATE_TECH_SNAPSHOT",
+ "desc": "tech_snapshot 必须包含技术位数值",
+ "check": lambda d: bool(d.get("tech_snapshot")) and any(c in d["tech_snapshot"] for c in "支撑阻力压强"),
+ "severity": "MEDIUM",
+ "fix": "tech_snapshot 包含强撑/弱撑/弱压/强压至少3个数值"
+ },
+ {
+ "id": "GATE_CURRENCY_SET",
+ "desc": "港股必须标 currency=HKD(个股存原币种,汇总时由calc_total_assets转CNY)",
+ "check": lambda d: not is_hk_stock(d.get("code","")) or d.get("currency") == "HKD",
+ "severity": "HIGH",
+ "fix": "设置 d['currency']='HKD'"
+ },
+ # --- 第4条 CRITICAL 红线:9维交叉验证 (2026-07-02 Dad要求) ---
+ # 策略不能只有价格数字,必须有证据经过了多维分析:
+ # 横切面: 大盘+行业+个股 | 纵切面: 基本面+消息面+技术面+资金流
+ # 代码层面可验证: sector_context(行业) + signal_factors(多因子) 或 tech_snapshot
+ {
+ "id": "GATE_9D_ANALYSIS",
+ "desc": "策略必须经过多维分析(sector_context + signal_factors)",
+ "check": lambda d: (
+ bool(d.get("sector_context") and str(d.get("sector_context","")).strip() not in ("neutral","","N/A","-"))
+ and (
+ bool(d.get("signal_factors") and isinstance(d.get("signal_factors"), (list,tuple)) and len(d["signal_factors"]) >= 1)
+ or bool(d.get("tech_snapshot") and any(c in str(d.get("tech_snapshot","")) for c in "支撑阻力压强"))
+ )
+ ),
+ "severity": "CRITICAL",
+ "fix": "重新运行 reassess_with_context() 完整重评确保 sector_context/signal_factors/tech_snapshot 均已填充"
+ },
+]
+
+
+def _hk_stock(code):
+ return bool(len(str(code)) == 5 and str(code)[0] in ('0','1'))
+
+def _is_buy_signal_str(signal):
+ """买入/加仓/建仓类信号"""
+ if not signal:
+ return False
+ return any(kw in signal for kw in ["买入", "加仓", "建仓"])
+
+# _is_buy_signal is defined later in this file (~line 1240)
+
+def validate_strategy(d, debug=True):
+ """策略评审:硬性门禁检查
+
+ 返回 (passed: bool, failures: list)
+ 任一 CRITICAL 失败 → 拒绝写入,标记 TODO 触发重评
+ 任一 HIGH 失败 → 标记 quality_check=failed,写入但不出现在推荐区
+ MEDIUM 失败 → 记录但不拦截
+ """
+ failures = []
+ for gate in STRATEGY_QUALITY_GATES:
+ try:
+ ok = gate["check"](d)
+ except Exception as e:
+ ok = False
+ if debug:
+ print(f" [VALIDATE] {gate['id']} 检查异常: {e}", flush=True)
+ if not ok:
+ failures.append(gate)
+ if debug:
+ print(f" [VALIDATE] ✗ {gate['id']} ({gate['severity']}): {gate['desc']}", flush=True)
+
+ passed = all(f["severity"] != "CRITICAL" for f in failures)
+
+ if debug:
+ criticals = [f for f in failures if f["severity"] == "CRITICAL"]
+ highs = [f for f in failures if f["severity"] == "HIGH"]
+ if passed:
+ print(f" [VALIDATE] ✅ 通过 ({len(failures)}条警告)" if failures else " [VALIDATE] ✅ 全通过", flush=True)
+ else:
+ print(f" [VALIDATE] ❌ {len(criticals)}条CRITICAL未通过 → 拒绝写入", flush=True)
+
+ return passed, failures
+
+
+def enforce_strategy_quality(code, name, result):
+ """策略写入前的强制质量门禁
+
+ 三段自动修复:
+ - Round 1: 技术分析(ta.full_analysis/chip_sr)
+ - Round 2: DB + 价格百分比推算
+ - Round 3: 最低可用策略标记强推
+ 3轮全不过 → review_needed
+ """
+ price = result.get("price", 0) or result.get("current", 0) or result.get("last_price", 0)
+ # 价格归零保护:自选股等场景可能没设price字段,尝试从TA/API获取
+ if not price:
+ try:
+ import urllib.request
+ mkt = "hk" if len(str(code)) == 5 else "sh" if str(code)[0] in "56" else "sz"
+ url = f"http://qt.gtimg.cn/q={mkt}{code}"
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
+ resp = urllib.request.urlopen(req, timeout=5).read()
+ text = resp.decode("gbk")
+ parts = text.split("~")
+ if len(parts) > 3:
+ p = float(parts[3])
+ if p > 0:
+ price = p
+ result["price"] = p # 写回result,让后续修复流程用真实价
+ except Exception:
+ pass
+ # 最后兜底
+ if not price:
+ price = 100 # 极少数API全线挂掉时的兜底,但会尽量走TA技术支持
+ code_str = str(code)
+ import sqlite3 # 本函数多处使用
+
+ def _db_sector():
+ """从 DB 取行业名"""
+ try:
+ _db = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=5)
+ r = _db.execute("SELECT sector_name FROM stock_sectors WHERE code=?", (code_str,)).fetchone()
+ _db.close()
+ return r[0] if r else None
+ except:
+ return None
+
+ def _fix_one(gate_id, round_num):
+ """对单个门禁执行修复。round_num越大修复越激进。"""
+ if gate_id == "GATE_LOSS_EXISTS" and (result.get("stop_loss") or 0) <= 0:
+ if round_num <= 2:
+ # Round 1-2: 技术分析算支撑
+ tech = ta.full_analysis(code)
+ if tech and "support_resistance" in tech:
+ sr = tech["support_resistance"]
+ ws = sr.get("weak_support")
+ ss = sr.get("strong_support")
+ if ws and ws > 0:
+ result["stop_loss"] = round(ws, 2)
+ elif ss and ss > 0:
+ result["stop_loss"] = round(ss, 2)
+ elif price > 0:
+ result["stop_loss"] = round(price * 0.95, 2)
+ elif price > 0:
+ result["stop_loss"] = round(price * 0.95, 2)
+ else:
+ # Round 3: 强制fallback
+ if price > 0:
+ result["stop_loss"] = round(price * 0.90, 2) # 更宽
+ else:
+ result["stop_loss"] = 1
+ print(f" R{round_num} 止损={result.get('stop_loss',0)}", flush=True)
+
+ if gate_id == "GATE_PROFIT_EXISTS" and (result.get("take_profit") or 0) <= 0:
+ if round_num <= 2:
+ tech = ta.full_analysis(code)
+ if tech and "support_resistance" in tech:
+ sr = tech["support_resistance"]
+ wr = sr.get("weak_resist")
+ sr_resist = sr.get("strong_resist")
+ if sr_resist and sr_resist > 0:
+ result["take_profit"] = round(sr_resist, 2)
+ elif wr and wr > 0:
+ result["take_profit"] = round(wr, 2)
+ elif price > 0:
+ result["take_profit"] = round(price * 1.08, 2)
+ elif price > 0:
+ result["take_profit"] = round(price * 1.08, 2)
+ else:
+ if price > 0:
+ result["take_profit"] = round(price * 1.20, 2) # 更宽
+ else:
+ # price=0 → 从DB或API获取
+ try:
+ import sqlite3 as _s3
+ _db = _s3.connect("/home/hmo/web-dashboard/data/mofin.db", timeout=5)
+ _r = _db.execute("SELECT price FROM holdings WHERE code=?", (code_str,)).fetchone()
+ _db.close()
+ if _r and _r[0] and _r[0] > 0:
+ result["take_profit"] = round(float(_r[0]) * 1.20, 2)
+ else:
+ result["take_profit"] = 2 # 真的兜底
+ except:
+ result["take_profit"] = 2
+ print(f" R{round_num} 止盈={result.get('take_profit',0)}", flush=True)
+
+ if gate_id == "GATE_ENTRY_RANGE" and ((result.get("entry_low") or 0) >= (result.get("entry_high") or 0) or (result.get("entry_low") or 0) <= 0):
+ p = price or 100
+ sl = result.get("stop_loss", 0)
+ tp = result.get("take_profit", 0)
+ if round_num <= 2:
+ if sl > 0 and tp > 0 and sl < tp:
+ result["entry_low"] = round(sl * 1.02, 2)
+ result["entry_high"] = round(tp * 0.85, 2)
+ if result["entry_low"] >= result["entry_high"]:
+ result["entry_low"] = round(p * 0.95, 2)
+ result["entry_high"] = round(p * 0.99, 2)
+ else:
+ result["entry_low"] = round(p * 0.93, 2)
+ result["entry_high"] = round(p * 1.02, 2)
+ else:
+ result["entry_low"] = round(p * 0.90, 2)
+ result["entry_high"] = round(p * 1.10, 2)
+ print(f" R{round_num} 买入区={result['entry_low']}~{result['entry_high']}", flush=True)
+
+ if gate_id == "GATE_9D_ANALYSIS":
+ # 行业
+ if not result.get("sector_context") or str(result.get("sector_context","")).strip() in ("neutral","","N/A","-"):
+ sec = _db_sector()
+ if sec:
+ result["sector_context"] = sec
+ elif round_num >= 2:
+ result["sector_context"] = f"自选(未分类)"
+ else:
+ result["sector_context"] = f"{name}所属行业(待补充)"
+ # signal_factors
+ if not result.get("signal_factors") or (isinstance(result.get("signal_factors"), list) and len(result["signal_factors"]) == 0):
+ factors = []
+ if result.get("timing_signal"):
+ factors.append(f"信号:{result['timing_signal']}")
+ if result.get("rr_ratio", 0) > 0:
+ factors.append(f"RR:{result['rr_ratio']}")
+ if result.get("stop_loss", 0) > 0 and result.get("take_profit", 0) > 0:
+ factors.append(f"损{result['stop_loss']}盈{result['take_profit']}")
+ if not factors:
+ if round_num >= 2:
+ factors.append("自动填充")
+ else:
+ # Round 1: 留空等重检,不硬填
+ pass
+ if factors:
+ result["signal_factors"] = factors
+ # tech_snapshot
+ if not result.get("tech_snapshot") or not any(c in str(result.get("tech_snapshot","")) for c in "支撑阻力压强"):
+ sl = result.get("stop_loss", 0)
+ tp = result.get("take_profit", 0)
+ if sl > 0 and tp > 0:
+ result["tech_snapshot"] = f"自动:损{sl}盈{tp}"
+ elif price:
+ result["tech_snapshot"] = f"自动:价{price}"
+ elif round_num >= 2:
+ result["tech_snapshot"] = "自动生成(未补全技术位)"
+ print(f" R{round_num} 9维分析: sector={result.get('sector_context','')[:20]} factors={result.get('signal_factors',[])}", flush=True)
+
+ # 循环重试
+ MAX_RETRIES = 3
+ passed, failures = validate_strategy(result)
+ retry_count = 0
+
+ for _retry_num in range(1, MAX_RETRIES + 1):
+ if passed:
+ break
+
+ critical_issues = [f["id"] for f in failures if f["severity"] == "CRITICAL"]
+ if not critical_issues:
+ # 没有CRITICAL了,只有HIGH/MEDIUM → 可以放行
+ passed = True
+ break
+
+ retry_count = _retry_num
+ print(f" [RETRY {retry_count}/{MAX_RETRIES}] {name}({code}) → 修复: {critical_issues}", flush=True)
+
+ for gate_id in critical_issues:
+ _fix_one(gate_id, retry_count)
+
+ # 重检
+ passed, failures = validate_strategy(result)
+
+ # --- 最终结果 ---
+ if passed:
+ print(f" ✅ {name}({code}) 质量门禁通过 ({retry_count}轮重试)", flush=True)
+ result["quality_check"] = "passed"
+ result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
+
+ # HIGH 级别警告(已通过但仍有非CRITICAL失败)
+ high_fails = [f for f in failures if f["severity"] == "HIGH"]
+ if high_fails:
+ result["quality_check"] = "warning"
+ result["quality_issues"] = {"high": [f["id"] for f in high_fails]}
+ print(f" ⚠️ {name}({code}) 有{len(high_fails)}条HIGH警告", flush=True)
+
+ # 记录 changelog
+ if "critical_issues" in dir():
+ cl = result.setdefault("changelog", [])
+ cl.append({
+ "time": datetime.now().strftime("%Y-%m-%d %H:%M"),
+ "event": f"质量门禁通过 (重试{retry_count}轮)",
+ })
+
+ result["status"] = "active"
+ return True
+ else:
+ # 3轮全不过 → review_needed
+ remaining_critical = [f["id"] for f in failures if f["severity"] == "CRITICAL"]
+ result["quality_check"] = "failed"
+ result["quality_issues"] = {
+ "critical": remaining_critical,
+ "all": [f["id"] for f in failures],
+ }
+ result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
+ result["status"] = "review_needed"
+ result["timing_signal"] = "信号不充分"
+
+ cl = result.setdefault("changelog", [])
+ cl.append({
+ "time": datetime.now().strftime("%Y-%m-%d %H:%M"),
+ "event": f"质量门禁3轮全拒 → review_needed ({remaining_critical})",
+ })
+
+ print(f" 🚫 {name}({code}) 3轮修复后仍有 {remaining_critical} → review_needed", flush=True)
+ return False
+
+
+# is_hk_stock 已从 mo_models 导入(见文件头部 import),不再在此复写。
+
+
+def calc_atr(code, period=14):
+ """从腾讯API K线数据计算ATR(period),返回ATR值或None"""
+ try:
+ url = f"http://ifzq.gtimg.cn/appstock/app/fqkline/get?param=hk{code},day,,,60,qfq"
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
+ resp = urllib.request.urlopen(req, timeout=5).read().decode('utf-8')
+ data = json.loads(resp)
+ bars = data.get('data', {}).get(f'hk{code}', {}).get('day', [])
+ if len(bars) < period + 1:
+ return None
+ trs = []
+ for i in range(1, min(len(bars), period + 1)):
+ try:
+ high = float(bars[i][2])
+ low = float(bars[i][3])
+ prev_close = float(bars[i-1][4]) if len(bars[i-1]) > 4 else float(bars[i-1][3])
+ tr = max(high - low, abs(high - prev_close), abs(low - prev_close))
+ trs.append(tr)
+ except (ValueError, IndexError):
+ continue
+ if not trs:
+ return None
+ return round(sum(trs) / len(trs), 2)
+ except Exception:
+ return None
+
+
+def calc_chip_sr(code, price):
+ """从筹码分布计算支撑/阻力位。
+
+ 返回: {"chip_ss": 筹码强支撑, "chip_sr": 筹码强阻力} 或 None
+ 筹码强支撑 = 当前价下方成交量最大的价格区间
+ 筹码强阻力 = 当前价上方成交量最大的价格区间
+
+ 用法:
+ sr = calc_chip_sr("600519", 1193)
+ if sr:
+ print(f"筹码支撑{sr['chip_ss']} 筹码阻力{sr['chip_sr']}")
+ """
+ if not price or price <= 0:
+ return None
+ try:
+ # 复用chip_factors的筹码分布构建
+ import sys as _sys
+ _sys.path.insert(0, "/home/hmo/MoFin/scripts")
+ from chip_factors import ChipFactors
+ cf = ChipFactors()
+ chip = cf._build_chip_distribution(code)
+ if not chip:
+ return None
+ total = sum(chip.values())
+ if total <= 0:
+ return None
+ # 2%区间聚合
+ step = max(round(price * 0.02, 2), 1.0)
+ bins = {}
+ for p, v in chip.items():
+ k = round(p / step) * step
+ bins[k] = bins.get(k, 0) + v
+ sb = sorted(bins.items())
+ below = [(p, v) for p, v in sb if p < price]
+ above = [(p, v) for p, v in sb if p >= price]
+ if not below or not above:
+ return None
+
+ # 支撑 = 下方成交量最大的密集区
+ chip_ss = max(below, key=lambda x: x[1])[0]
+ # 阻力 = 上方成交量最大的密集区
+ chip_sr = max(above, key=lambda x: x[1])[0]
+
+ return {"chip_ss": chip_ss, "chip_sr": chip_sr}
+ except Exception as e:
+ print(f" ⚠️ 筹码S/R计算失败: {e}", file=sys.stderr)
+ return None
+
+# 提示词版本追踪
+try:
+ from prompt_manager.tracking import record_strategy_generation
+ HAS_PROMPT_TRACKING = True
+except ImportError:
+ HAS_PROMPT_TRACKING = False
+
+def safe_json_load(path, default=None):
+ """安全加载 JSON,遇到坏数据自动修复"""
+ if not os.path.exists(path):
+ return default if default is not None else {}
+ try:
+ with open(path, "r", encoding="utf-8") as f:
+ return json.load(f)
+ except json.JSONDecodeError:
+ # 使用 json_validator 增强修复(更健壮的错误处理)
+ with open(path, "r", encoding="utf-8") as f:
+ raw = f.read()
+
+ try:
+ import sys
+ sys.path.insert(0, "/home/hmo/MoFin")
+ from json_validator import validate_json_syntax, _record_format_error
+
+ score, errors, parsed = validate_json_syntax(raw)
+ if parsed is not None and score >= 60:
+ if errors:
+ _record_format_error(raw, errors, 0)
+ return parsed
+
+ _record_format_error(raw, errors, 0)
+ print(f"[WARN] {path} JSON 自动修复失败 (score={score})", file=sys.stderr)
+ return default if default is not None else {}
+ except ImportError:
+ pass
+
+ # 尝试修复:字符串内未转义的换行符,去多余括号
+ fixed = raw
+ result = []
+ in_str = False
+ for ch in fixed:
+ if ch == '"':
+ in_str = not in_str
+ result.append(ch)
+ elif in_str and ch in '\n\r':
+ result.append('\\n')
+ else:
+ result.append(ch)
+ fixed = ''.join(result)
+ fixed = fixed.rstrip('}')
+ if not fixed.endswith('}'):
+ fixed += '}'
+ try:
+ return json.loads(fixed)
+ except json.JSONDecodeError as e:
+ print(f"[WARN] {path} 自动修复失败: {e}", file=sys.stderr)
+ return default if default is not None else {}
+KNOWLEDGE_LOG = "/home/hmo/Obsidian/knowledge/finance/analyst-knowledge-log.md"
+MACRO_CONTEXT_PATH = "/home/hmo/web-dashboard/data/macro_context.json"
+MARKET_CONTEXT_PATH = "/home/hmo/web-dashboard/data/market.json"
+STOCK_SECTOR_MAP_PATH = "/home/hmo/web-dashboard/data/stock_sector_map.json"
+
+
+def load_stock_sector_map():
+ """读取个股归属行业映射
+
+ stock_sector_map.json 格式: {code: [sector1, sector2, ...]}
+ 跳过 _note, _created_at 等元数据键。
+ """
+ # 优先从 SQLite 读取
+ try:
+ from mofin_db import get_conn, query_sector_stocks
+ conn = get_conn()
+ # 从 stock_sectors 表反向构建 code→[sectors] 映射
+ rows = conn.execute("SELECT code, sector_name FROM stock_sectors ORDER BY code").fetchall()
+ conn.close()
+ code_to_sectors = {}
+ for code, sector in rows:
+ if code not in code_to_sectors:
+ code_to_sectors[code] = []
+ code_to_sectors[code].append(sector)
+ return code_to_sectors
+ except Exception:
+ pass
+ try:
+ with open(STOCK_SECTOR_MAP_PATH) as f:
+ data = json.load(f)
+ code_to_sectors = {}
+ for key, value in data.items():
+ if key.startswith("_"):
+ continue
+ if isinstance(value, list):
+ code_to_sectors[key] = value
+ return code_to_sectors
+ except Exception:
+ return {}
+
+
+def load_market_context():
+ """读取市场上下文,优先 SQLite,回退 market.json"""
+ # 优先从 SQLite 读取
+ try:
+ from mofin_db import get_conn, query_latest_market
+ conn = get_conn()
+ market = query_latest_market(conn)
+ conn.close()
+ if market and market.get("sectors"):
+ sector_perf = {}
+ for s in market["sectors"]:
+ name = s.get("name", "")
+ if name:
+ sector_perf[name] = {
+ "change": s.get("change_pct", 0),
+ "up_count": s.get("up_count", 0),
+ "down_count": s.get("down_count", 0),
+ "net_inflow": s.get("net_inflow", 0),
+ "lead_stock": s.get("lead_stock", ""),
+ "lead_stock_change": s.get("lead_stock_change", 0),
+ }
+ return {
+ "sector_perf": sector_perf,
+ "breadth": market.get("up_ratio", 50),
+ "mood": market.get("mood", "neutral"),
+ "top_gainers": {g["name"]: g["change_pct"] for g in market.get("top_gainers", [])},
+ "top_losers": {g["name"]: g["change_pct"] for g in market.get("top_losers", [])},
+ "total_sectors": len(market["sectors"]),
+ "market_timestamp": market.get("timestamp", ""),
+ }
+ except Exception:
+ pass
+ try:
+ with open(MARKET_CONTEXT_PATH) as f:
+ market = json.load(f)
+ sectors = market.get("sectors", [])
+ sector_perf = {}
+ for s in sectors:
+ name = s.get("name", "")
+ if name:
+ sector_perf[name] = {
+ "change": s.get("change", 0),
+ "up_count": s.get("up_count", 0),
+ "down_count": s.get("down_count", 0),
+ "net_inflow": s.get("net_inflow", 0),
+ "lead_stock": s.get("lead_stock", ""),
+ "lead_stock_change": s.get("lead_stock_change", 0),
+ }
+ top_gainers = {s.get("name", ""): s.get("change", 0)
+ for s in market.get("top_gainers", [])}
+ top_losers = {s.get("name", ""): s.get("change", 0)
+ for s in market.get("top_losers", [])}
+ return {
+ "sector_perf": sector_perf,
+ "breadth": market.get("up_ratio", 50),
+ "mood": market.get("mood", "neutral"),
+ "top_gainers": top_gainers,
+ "top_losers": top_losers,
+ "total_sectors": market.get("total_sectors", 0),
+ "market_timestamp": market.get("timestamp", ""),
+ }
+ except Exception:
+ return {
+ "sector_perf": {},
+ "breadth": 50,
+ "mood": "neutral",
+ "top_gainers": {},
+ "top_losers": {},
+ "total_sectors": 0,
+ "market_timestamp": "",
+ }
+
+
+def compute_sector_adjustment(code, market_ctx, stock_sector_map):
+ """根据个股所属行业的市场表现+小果情感,返回调整系数
+
+ 返回 dict:
+ stop_bias: 止损调整系数(<1.0收紧, >1.0放宽)
+ target_bias: 止盈调整系数
+ note: 行业背景一句话
+ sector_name: 匹配到的行业名称
+ sector_change: 行业涨跌幅
+ """
+ # 默认无调整
+ adj = {"stop_bias": 1.0, "target_bias": 1.0, "note": "",
+ "sector_name": "", "sector_change": 0}
+
+ sectors_for_code = stock_sector_map.get(code, [])
+ if not sectors_for_code:
+ return adj
+
+ sector_perf = market_ctx.get("sector_perf", {})
+ breadth = market_ctx.get("breadth", 50)
+
+ # 找第一个能匹配到的行业
+ for sec in sectors_for_code:
+ if sec in sector_perf:
+ perf = sector_perf[sec]
+ chg = perf.get("change", 0)
+ adj["sector_name"] = sec
+ adj["sector_change"] = chg
+
+ # 行业暴跌 > 3%
+ if chg <= -3:
+ adj["stop_bias"] = 0.92 # 止损收紧8%
+ adj["target_bias"] = 0.90 # 止盈下调10%
+ adj["note"] = f"行业{sec}大跌{chg:+.1f}%,收紧止损"
+ # 行业大跌 1~3%
+ elif chg <= -1:
+ adj["stop_bias"] = 0.96
+ adj["target_bias"] = 0.95
+ adj["note"] = f"行业{sec}下跌{chg:+.1f}%,适度防御"
+ # 行业大涨 > 3%
+ elif chg >= 3:
+ adj["stop_bias"] = 1.05 # 止损放宽5%(给趋势空间)
+ adj["target_bias"] = 1.03
+ adj["note"] = f"行业{sec}大涨{chg:+.1f}%,可适度积极"
+ # 行业上涨 1~3%
+ elif chg >= 1:
+ adj["stop_bias"] = 1.02
+ adj["note"] = f"行业{sec}上涨{chg:+.1f}%,正常"
+ else:
+ adj["note"] = f"行业{sec}{chg:+.1f}%,中性"
+ break
+ # 尝试处理命名差异:market.json中的行业名可能多了"板块"后缀
+ for market_sec_name in sector_perf:
+ if sec in market_sec_name or market_sec_name in sec:
+ perf = sector_perf[market_sec_name]
+ chg = perf.get("change", 0)
+ adj["sector_name"] = market_sec_name
+ adj["sector_change"] = chg
+ if chg <= -3:
+ adj["stop_bias"] = 0.92
+ adj["target_bias"] = 0.90
+ adj["note"] = f"行业{market_sec_name}大跌{chg:+.1f}%,收紧止损"
+ elif chg <= -1:
+ adj["stop_bias"] = 0.96
+ adj["target_bias"] = 0.95
+ adj["note"] = f"行业{market_sec_name}下跌{chg:+.1f}%,适度防御"
+ elif chg >= 3:
+ adj["stop_bias"] = 1.05
+ adj["target_bias"] = 1.03
+ adj["note"] = f"行业{market_sec_name}大涨{chg:+.1f}%,可适度积极"
+ elif chg >= 1:
+ adj["stop_bias"] = 1.02
+ adj["note"] = f"行业{market_sec_name}上涨{chg:+.1f}%,正常"
+ else:
+ adj["note"] = f"行业{market_sec_name}{chg:+.1f}%,中性"
+ break
+
+ # 如果breath<30% (大盘极弱),再加一层收紧
+ if breadth < 30:
+ adj["stop_bias"] *= 0.97 # 再收紧3%
+ breadth_note = "大盘仅{}%个股上涨".format(int(breadth))
+ adj["note"] = (adj["note"] + " | " + breadth_note) if adj["note"] else breadth_note
+ elif breadth < 40:
+ adj["stop_bias"] *= 0.99
+ breadth_note = "大盘偏弱({}%上涨)".format(int(breadth))
+ adj["note"] = (adj["note"] + " | " + breadth_note) if adj["note"] else breadth_note
+
+ # 小果情感约束:利空置信度>80%时收紧止损
+ try:
+ xiaoguo_path = "/home/hmo/web-dashboard/data/xiaoguo_sentiment.json"
+ if os.path.exists(xiaoguo_path):
+ xg = json.load(open(xiaoguo_path))
+ stock_sentiment = xg.get("stocks", {}).get(code, {})
+ if stock_sentiment:
+ sentiment = stock_sentiment.get("sentiment", "")
+ confidence = stock_sentiment.get("confidence", 0)
+ summary = stock_sentiment.get("summary", "")
+ if sentiment == "negative" and confidence > 0.8:
+ adj["stop_bias"] = min(adj["stop_bias"], 0.95)
+ adj["note"] += f" | 小果利空{confidence:.0%}:{summary[:30]}"
+ except Exception:
+ pass
+
+ return adj
+
+
+def load_macro_context():
+ """读取宏观上下文,返回 (bias, desc),优先 DB,回退 JSON"""
+ try:
+ from mofin_db import get_conn
+ conn = get_conn()
+ row = conn.execute(
+ "SELECT indices, structure FROM macro_context_log "
+ "WHERE has_valid_data=1 ORDER BY created_at DESC LIMIT 1"
+ ).fetchone()
+ conn.close()
+ if row:
+ indices = json.loads(row[0]) if row[0] else {}
+ structure = json.loads(row[1]) if row[1] else {}
+ overall = structure.get("overall", "neutral")
+ desc = structure.get("description", "")
+ else:
+ raise ValueError("no db data")
+ except Exception:
+ try:
+ with open(MACRO_CONTEXT_PATH) as f:
+ ctx = json.load(f)
+ overall = ctx.get("structure", {}).get("overall", "neutral")
+ desc = ctx.get("structure", {}).get("description", "")
+ except Exception:
+ return 1.0, "宏观未加载"
+ if "bearish" in overall:
+ return 0.8, f"宏观{desc}"
+ elif overall == "bullish":
+ return 1.05, f"宏观{desc}"
+ elif overall == "strong_bullish":
+ return 1.1, f"宏观{desc}"
+ else:
+ return 1.0, f"宏观{desc}"
+
+
+def batch_fetch_prices(codes):
+ """获取实时价格。优先从 DB 读取(price_monitor 每 2 分钟更新),失败才拉腾讯 API。"""
+ if not codes:
+ return {}
+
+ all_results = {}
+
+ # 主通道:从 DB 读取(price_monitor 唯一价格入口)
+ try:
+ from mofin_db import get_conn
+ db = get_conn()
+ for raw_code in codes:
+ raw_code = str(raw_code).split('_')[0]
+ if not raw_code: continue
+ row = db.execute(
+ "SELECT price, change_pct FROM holdings WHERE code=? AND is_active=1", (raw_code,)
+ ).fetchone()
+ if not row:
+ row = db.execute(
+ "SELECT price, change_pct FROM holding_strategies WHERE code=? AND status='active' ORDER BY updated_at DESC LIMIT 1", (raw_code,)
+ ).fetchone()
+ if row and row['price']:
+ all_results[raw_code] = {
+ "price": row['price'],
+ "close": row['price'], # 用现价近似昨收,仅用于sentiment计算
+ "high": row['price'],
+ "low": row['price'],
+ "code": raw_code,
+ }
+ db.close()
+ if all_results:
+ return all_results
+ except Exception:
+ pass
+
+ # Fallback: 腾讯 API(仅当 DB 无数据时)
+ batch_size = 15
+ for batch_start in range(0, len(codes), batch_size):
+ batch = codes[batch_start:batch_start + batch_size]
+ symbols = []
+ code_map = {}
+ for raw_code in batch:
+ raw_code = str(raw_code).split('_')[0]
+ if not raw_code:
+ continue
+ if len(raw_code) == 5 and raw_code.isdigit():
+ prefix = "hk"
+ elif raw_code.startswith(("6", "5")):
+ prefix = "sh"
+ else:
+ prefix = "sz"
+ sym = f"{prefix}{raw_code}"
+ symbols.append(sym)
+ code_map[sym] = raw_code
+ if not symbols:
+ continue
+
+ url = f"http://qt.gtimg.cn/q={','.join(symbols)}"
+ max_retries = 2
+ for attempt in range(max_retries + 1):
+ try:
+ r = urllib.request.urlopen(url, timeout=10)
+ text = r.read().decode("gbk")
+ except Exception as e:
+ if attempt < max_retries:
+ continue
+ print(f" batch_fetch_prices error: {e}", file=sys.stderr)
+ continue
+
+ for line in text.strip().split("\n"):
+ line = line.strip()
+ if not line or "=" not in line:
+ continue
+ try:
+ sym = line.split("=", 1)[0].strip().lstrip("v_")
+ raw_value = line.split("=", 1)[1].strip().strip('"').strip(";")
+ fields = raw_value.split("~")
+ if len(fields) < 35:
+ continue
+ orig_code = code_map.get(sym)
+ if not orig_code:
+ continue
+ def f(i):
+ try:
+ return float(fields[i]) if fields[i].strip() else 0.0
+ except:
+ return 0.0
+ price_raw = f(3)
+ # 港股:腾讯 API 返回 HKD,需转 CNY
+ if is_hk_stock(orig_code) and price_raw > 0:
+ price_raw = to_cny(price_raw)
+ all_results[orig_code] = {
+ "price": price_raw, "close": f(4), "high": f(33), "low": f(34),
+ "code": orig_code,
+ }
+ except Exception:
+ continue
+ break # Success - break retry loop
+
+ return all_results
+
+
+def get_price_tencent(code):
+ """获取实时价格。优先 DB(price_monitor 维护),失败才拉腾讯。港股价格已是 CNY。"""
+ raw_code = str(code).split('_')[0]
+ if not raw_code:
+ return None
+
+ # 主通道: DB
+ try:
+ from mofin_db import get_conn
+ db = get_conn()
+ row = db.execute("SELECT price FROM holdings WHERE code=? AND is_active=1", (raw_code,)).fetchone()
+ if not row:
+ row = db.execute("SELECT price FROM holding_strategies WHERE code=? AND status='active' ORDER BY updated_at DESC LIMIT 1", (raw_code,)).fetchone()
+ if row and row['price']:
+ db.close()
+ return row['price']
+ db.close()
+ except Exception:
+ pass
+
+ # Fallback: 腾讯 API
+ try:
+ from mo_models import to_cny, is_hk_stock
+ except ImportError:
+ to_cny = lambda v, r=None: v
+ is_hk_stock = lambda c: len(str(c).strip()) == 5 and str(c).strip().isdigit()
+ try:
+ if is_hk_stock(raw_code):
+ prefix = "hk"
+ elif raw_code.startswith("6") or raw_code.startswith("5"):
+ prefix = "sh"
+ else:
+ prefix = "sz"
+ url = f"http://qt.gtimg.cn/q={prefix}{raw_code}"
+ r = urllib.request.urlopen(url, timeout=5)
+ fields = r.read().decode("gbk").split('"')[1].split("~")
+ def f(i):
+ try:
+ return float(fields[i]) if fields[i].strip() else 0.0
+ except:
+ return 0.0
+ price = f(3)
+ if is_hk_stock(raw_code) and price > 0:
+ price = to_cny(price)
+ return {
+ "price": price, "close": f(4), "high": f(33), "low": f(34),
+ "code": raw_code,
+ }
+ except Exception as e:
+ print(f" get_price error {code}: {e}", file=sys.stderr)
+ return None
+
+
+def reassess_strategy(code, name, price, cost, shares, current_action,
+ volume_signal="", sentiment="neutral",
+ is_watchlist=False):
+ """根据技术分析重评策略"""
+
+ tech = ta.full_analysis(code)
+ if tech and "support_resistance" in tech:
+ sr = tech["support_resistance"]
+ candle = tech.get("candlestick", {})
+ vol = tech.get("volume", {})
+ ss = sr.get("strong_support")
+ ws = sr.get("weak_support")
+ wr = sr.get("weak_resist")
+ sr_resist = sr.get("strong_resist")
+ pivot = sr.get("pivot")
+ effective_range = sr.get("effective_range")
+ print(f" TECH: 强撑={ss} 弱撑={ws} 枢轴={pivot} 弱压={wr} 强压={sr_resist} 有效区间={effective_range}")
+ else:
+ print(f" ⚠️ 技术分析不可用", file=sys.stderr)
+ ss = ws = wr = sr_resist = pivot = None
+ candle = {}
+ vol = {}
+
+ # ----- 多周期技术分析(周线/月线/均线) -----
+ mtf_analysis = {}
+ mtf_adj = {}
+ try:
+ mtf_result = mtf.full_multi_tf_analysis(code)
+ if mtf_result.get("daily") and mtf_result["daily"].get("count", 0) >= 5:
+ mtf_analysis = mtf_result
+ mtf_adj = mtf_result.get("strategy_adjustment", {})
+ daily_mas = mtf_result.get("daily", {}).get("mas", {})
+ weekly = mtf_result.get("weekly", {})
+ monthly = mtf_result.get("monthly", {})
+ trend_align = mtf_adj.get("trend_alignment", "未知")
+ print(f" 多周期: {trend_align} | "
+ f"MA5={daily_mas.get('ma5','?')} MA20={daily_mas.get('ma20','?')} MA60={daily_mas.get('ma60','?')} | "
+ f"周线{weekly.get('trend',{}).get('description','?')} 月线{monthly.get('trend',{}).get('description','?')}")
+ except Exception as e:
+ print(f" 多周期分析失败: {e}", file=sys.stderr)
+
+ # ----- 筹码分布支撑/阻力(中长线参考,加情景权重) -----
+ chip_sr = None
+ chip_weight = 0.5 # 默认中等权重
+ regime = detect_scenario()
+ regime_id = regime.get("id", "weak_consolidation")
+
+ # 情景决定筹码因子权重
+ if regime_id == "weak_consolidation":
+ chip_weight = 0.9 # 震荡市筹码最准
+ elif regime_id == "bullish_recovery":
+ chip_weight = 0.4 # 上涨趋势筹码阻力可能被突破
+ elif regime_id == "sharp_decline":
+ chip_weight = 0.2 # 急跌中筹码支撑可能失效
+ elif regime_id == "sector_rotation":
+ chip_weight = 0.6 # 轮动市中筹码有一定参考
+
+ try:
+ chip_sr = calc_chip_sr(code, price)
+ if chip_sr:
+ print(f" 筹码: 撑={chip_sr['chip_ss']:.0f} 阻={chip_sr['chip_sr']:.0f} | 情景={regime_id} 权重={chip_weight:.1f}")
+ # 与枢轴点对比
+ if ss and ws and pivot and chip_sr['chip_ss'] > 0 and chip_sr['chip_sr'] > 0:
+ chip_ss_pct = (price - chip_sr['chip_ss']) / price * 100
+ chip_sr_pct = (chip_sr['chip_sr'] - price) / price * 100
+
+ # 共振检测:筹码支撑 vs 枢轴弱支撑(都是最近支撑位)
+ if ws and ws > 0:
+ resonance_ss = abs(chip_ss_pct - ((price - ws) / price * 100)) < 3
+ else:
+ resonance_ss = False
+ # 共振检测:筹码阻力 vs 枢轴弱阻力(都是最近阻力位)
+ if wr and wr > 0:
+ resonance_sr = abs(chip_sr_pct - ((wr - price) / price * 100)) < 3
+ else:
+ resonance_sr = False
+
+ if resonance_ss and chip_weight >= 0.5:
+ print(f" ⚡ 支撑共振({chip_weight:.0f}): 筹码+枢轴均指向{chip_sr['chip_ss']:.0f}")
+ elif chip_weight < 0.5:
+ print(f" 📎 支撑一致但权重低({chip_weight:.1f}): {regime_id}下筹码支撑不可靠")
+
+ if resonance_sr and chip_weight >= 0.5:
+ print(f" ⚡ 阻力共振({chip_weight:.0f}): 筹码+枢轴均指向{chip_sr['chip_sr']:.0f}")
+ elif chip_weight < 0.5:
+ print(f" 📎 阻力一致但权重低({chip_weight:.1f}): {regime_id}下筹码阻力不可靠")
+ except Exception:
+ pass
+
+ profit_pct = (price - cost) / cost * 100 if cost else 0
+ is_new_entry = (cost == 0) or (shares == 0)
+ is_deep_loss = profit_pct < -20
+
+ # ----- 股票分类(短炒/中短线/中长线/弱势/深套) -----
+ stock_category = "中短线"
+ time_horizon = "2周~3月"
+ position_advice = "中等仓位"
+ try:
+ mtf_cache = mtf._load_mtf_cache()
+ stock_data = mtf_cache.get(code, {})
+ daily_klines = stock_data.get("daily", [])
+ fund = stock_data.get("fundamentals", {})
+ closes = [d["close"] for d in daily_klines] if daily_klines else []
+
+ if len(closes) >= 10:
+ cur = closes[-1]
+ ma20 = sum(closes[-20:])/20 if len(closes)>=20 else 0
+ ma60 = sum(closes[-60:])/60 if len(closes)>=60 else 0
+ highs = [d["high"] for d in daily_klines[-20:]]
+ lows = [d["low"] for d in daily_klines[-20:]]
+ volatility = ((max(highs)-min(lows))/min(lows)*100) if min(lows)>0 else 0
+ pe = fund.get("pe") or 0
+ eps = fund.get("eps") or 0
+ mcap = fund.get("mcap_total") or 0
+ is_high_vol = volatility > 30
+ is_high_pe = pe > 100 or pe < 0
+ is_value = 0 < pe < 20 and eps > 0.5
+
+ if is_deep_loss:
+ stock_category = "深套"
+ time_horizon = "长期"
+ position_advice = "不补不割"
+ elif is_high_vol and is_high_pe:
+ stock_category = "短炒"
+ time_horizon = "数日~2周"
+ position_advice = "小仓快进快出"
+ elif cur < ma20 and cur < ma60 and ma20 > 0:
+ stock_category = "弱势"
+ time_horizon = "观望"
+ position_advice = "减仓或观望"
+ elif (is_value or mcap > 1000) and cur > ma20:
+ stock_category = "中长线"
+ time_horizon = "数月~1年"
+ position_advice = "正常配置"
+ elif volatility > 20:
+ stock_category = "中短线"
+ time_horizon = "2~6周"
+ position_advice = "中等仓位"
+ except Exception:
+ pass
+
+ print(f" 分类: {stock_category} | {time_horizon} | {position_advice}")
+
+ # ----- 短炒+强趋势检测:短炒分类但多周期多头时用移动止损代替弱支撑止损 -----
+ is_short_term_strong_trend = False
+ if stock_category == "短炒":
+ trend_align = mtf_adj.get("trend_alignment", "")
+ strong_trend_indicators = ["多周期看多", "多周期多头", "上升"]
+ if any(ind in trend_align for ind in strong_trend_indicators):
+ is_short_term_strong_trend = True
+ print(f" ⚡ 短炒+强趋势检测: 趋势={trend_align} → 启用移动止损, 不止盈")
+ position_advice = "小仓强趋势让利润跑"
+
+ # ----- 止损设置(含最小距离3%保护) -----
+ if is_new_entry:
+ # 新买入推荐:止损 = 弱支撑(约2-3%跌幅,合理可控)
+ if ws and ws > 0:
+ new_stop = round(ws, 2)
+ else:
+ new_stop = round(price * 0.96, 2)
+ elif is_deep_loss:
+ # 深套:止损 = 强支撑再下移(不轻易割)
+ if ss and ss > 0:
+ new_stop = round(min(ss, price * 0.85), 2)
+ else:
+ new_stop = round(price * 0.85, 2)
+ else:
+ # 已持仓正常:止损 = 强支撑
+ if is_short_term_strong_trend:
+ # 短炒+强趋势:用移动止损(距现价-5%),不止盈让利润跑
+ trailing_sl = round(max(ws or 0, price * 0.95), 2) if ws else round(price * 0.95, 2)
+ new_stop = trailing_sl
+ print(f" 短炒强趋势移动止损: {new_stop} (距现价-{(1-new_stop/price)*100:.1f}%)")
+ elif ss and ss > 0:
+ new_stop = round(ss, 2)
+ else:
+ new_stop = round(price * 0.88, 2)
+
+ # 已盈利仓位(>5%):用较紧的移动止损保护利润,但不超过成本线
+ if profit_pct > 5 and not is_new_entry and not is_deep_loss:
+ # 取 max(弱支撑, 成本线, 当前价×0.95) 作为止损
+ cost_protect = cost if cost > 0 else 0
+ trailing_stop = round(max(ws or 0, cost_protect, price * 0.95), 2)
+ if trailing_stop > new_stop:
+ new_stop = trailing_stop
+ print(f" 已启用移动止损: {new_stop} (保护+{profit_pct:.1f}%利润)", file=sys.stderr)
+
+ # 最小止损距离 —— 随趋势强度调整(2026-06-23 震度保护规则)
+ # 强趋势(多周期看多 + MA多头排列):最小1.5%下行空间
+ # 普通/弱势:最小3%下行空间
+ is_strong_trend = False
+ trend_align = mtf_adj.get("trend_alignment", "")
+ strong_trend_indicators = ["多周期看多", "多周期多头", "上升"]
+ try:
+ if any(ind in trend_align for ind in strong_trend_indicators) and ma20 > ma60 and cur >= ma20:
+ is_strong_trend = True
+ except (NameError, TypeError):
+ pass # ma20/ma60/cur may be unbound if MTF data insufficient
+
+ if is_strong_trend:
+ min_stop_gap = 0.015 # 1.5%
+ else:
+ min_stop_gap = 0.03 # 3%
+
+ min_stop = round(price * (1 - min_stop_gap), 2)
+ if new_stop > min_stop and not is_deep_loss:
+ old_stop = new_stop
+ new_stop = min_stop
+ if old_stop != new_stop:
+ print(f" 最小止损 {round(min_stop_gap*100)}%间距约束: {old_stop}→{new_stop} (趋势{'强' if is_strong_trend else '普通'})")
+
+ # 港股附加:ATR波动率校验 — 止损距现价不得小于 1×ATR(14)
+ if is_hk_stock(code):
+ atr = calc_atr(code)
+ if atr and atr > 0:
+ min_atr_stop = round(price - atr, 2)
+ if new_stop > min_atr_stop:
+ old_stop_val = new_stop
+ new_stop = min_atr_stop
+ print(f" 港股ATR波动率校验({atr:.2f}): 止损 {old_stop_val}→{new_stop} (1×ATR间距)")
+
+ # ----- 止盈设置 -----
+ if is_short_term_strong_trend and not is_new_entry:
+ # 短炒+强趋势:不止盈让利润跑
+ mtf_tp = mtf_adj.get("take_profit_reference", {})
+ if mtf_tp and mtf_tp.get("level", 0) > price * 1.2:
+ new_target = round(mtf_tp["level"], 2)
+ else:
+ new_target = 0 # 无多周期阻力时不编造止盈
+ print(f" 短炒强趋势不止盈: 止盈设为{new_target} (+{(new_target/price-1)*100:.0f}%)")
+ elif sr_resist and sr_resist > 0:
+ new_target = round(sr_resist, 2)
+ else:
+ new_target = 0 # 无技术面数据时不编造止盈
+
+ # ----- 风险回报比校验 -----
+ stop_distance = price - new_stop if price > new_stop else price * 0.02
+ target_distance = new_target - price if new_target > price else 0
+
+ # 1:2 检查
+ min_target_distance = stop_distance * 2.0
+ if target_distance < min_target_distance:
+ # 尝试更高的阻力位,但不超过下一个真实压力位
+ candidate_targets = []
+ if wr and wr > price and wr != sr_resist:
+ candidate_targets.append(wr)
+ if sr_resist and sr_resist > price:
+ candidate_targets.append(sr_resist)
+ # 检查有效区间,如果有更高的自然目标位
+ if effective_range and price < effective_range * 0.9:
+ candidate_targets.append(effective_range)
+
+ found = False
+ for level in candidate_targets:
+ if (level - price) >= min_target_distance:
+ new_target = level
+ found = True
+ break
+
+ # 如果仍然不满足,检查是否至少能到 1:1.5
+ min15_distance = stop_distance * 1.5
+ if not found:
+ for level in candidate_targets:
+ if (level - price) >= min15_distance:
+ new_target = level
+ found = True
+ break
+
+ # ----- 风险回报比最终计算 -----
+ risk = max(price - new_stop, price * 0.01)
+ reward = max(new_target - price, 0)
+ rr_ratio = reward / risk if risk > 0 else 0
+
+ # ----- 状态判断 -----
+ if is_deep_loss:
+ status = "updated"
+ action_note = "深套持有"
+ elif is_new_entry:
+ if rr_ratio < 1.5:
+ status = "review"
+ action_note = "⚠️盈亏比不足1:1.5,不建议买入"
+ elif rr_ratio < 2.0:
+ status = "updated"
+ action_note = "⚠️盈亏比偏低(1:{:.1f}),谨慎买入".format(rr_ratio)
+ else:
+ status = "updated"
+ action_note = ""
+ else:
+ if rr_ratio < 0.5:
+ status = "updated"
+ action_note = "⚠️盈亏比极低,关注"
+ elif rr_ratio < 1.5:
+ status = "updated"
+ action_note = "⚠️盈亏比偏低(1:{:.1f}),不建议加仓".format(rr_ratio)
+ else:
+ status = "updated"
+ action_note = ""
+
+ # 短炒+强趋势:在action_note追加标记
+ if is_short_term_strong_trend and not is_new_entry and not is_deep_loss:
+ extra_note = "短炒强趋势持" if "深套" not in action_note else ""
+ if extra_note:
+ action_note = f"{action_note} | {extra_note}" if action_note else extra_note
+
+ # ----- 买入区间(有盈亏比严格约束) -----
+ max_acceptable_entry = None # 最大可接受买入价(满足R/R约束)
+
+ if new_target and new_stop and new_target > new_stop and not is_deep_loss:
+ # 买入价的R/R约束:
+ # 要求 (target - entry) / (entry - stop) >= min_rr
+ # 即 entry <= (target + min_rr * stop) / (1 + min_rr)
+ min_rr = 1.0 # 至少1:1,才不亏
+ recommend_rr = 1.5 # 推荐1:1.5以上
+
+ max_for_recommend = (new_target + recommend_rr * new_stop) / (1 + recommend_rr)
+ max_for_neutral = (new_target + min_rr * new_stop) / (1 + min_rr)
+
+ if is_new_entry:
+ # 新买入:要求1:1.5+
+ max_acceptable_entry = max_for_recommend
+ else:
+ # 已持仓加仓:至少1:1
+ max_acceptable_entry = max_for_neutral
+
+ if is_new_entry:
+ # 新买入:买入区 = 弱支撑附近(不是当前价附近!)
+ # 只在价格跌到弱支撑附近时才推买入
+ entry_low = round(price * 0.98, 2)
+ entry_high = round(price * 1.02, 2)
+ if max_acceptable_entry and entry_high > max_acceptable_entry:
+ entry_high = round(max_acceptable_entry, 2)
+ # 确保买入区不小于1%
+ if entry_high - entry_low < price * 0.01:
+ if max_acceptable_entry and price <= max_acceptable_entry:
+ entry_low = round(max(price * 0.99, new_stop), 2)
+ entry_high = round(min(price * 1.01, max_acceptable_entry), 2)
+ elif ws and ws > 0 and wr and wr > 0 and not is_deep_loss:
+ # 已持仓正常:买入区 = 弱支撑~弱支撑上方5%(给合理回调空间)
+ # 上限不能低于成本价×0.95(保护已有持仓不被高位逼空)
+ entry_low = round(ws, 2)
+ entry_max = round(ws * 1.05, 2) # 比弱支撑高5%,有足够空间
+ # 如果当前价已远离买入区,保持买入区不变(不因价格涨了就收窄)
+ min_upper = round(cost * 0.95, 2) if cost > 0 else 0
+ if entry_max < min_upper:
+ entry_max = min_upper
+ if max_acceptable_entry:
+ entry_high = round(min(entry_max, max_acceptable_entry), 2)
+ else:
+ entry_high = entry_max
+ # 如果当前价已远离买入区(高于买入区上沿),禁止加仓推荐
+ if price > entry_high:
+ # 买入区锁定在弱支撑位,但标记为"价格远离"
+ pass
+ # 如果买入区过窄,标记但不扩展(加仓必须在支撑位)
+ if entry_high - entry_low < price * 0.005:
+ entry_low = round(ws * 0.995, 2)
+ entry_high = round(ws * 1.005, 2)
+ else:
+ entry_low = round(price * 0.90, 2)
+ entry_high = round(price * 1.05, 2)
+
+ # 买入区间稳定性保护:上边界单次变动不超过5%
+ if 'entry_high' in dir() and entry_high:
+ # 读取当前策略中已有的买入区上界,如果有且变化过大则限制
+ old_entry_high = None
+ if 'current_action' in dir() and current_action:
+ import re
+ m = re.search(r'买入区[\d.]+~([\d.]+)', current_action)
+ if m:
+ old_entry_high = float(m.group(1))
+ if old_entry_high and old_entry_high > 0:
+ max_change = old_entry_high * 0.95 # 单次最多下降5%
+ if entry_high < max_change:
+ entry_high = round(max_change, 2)
+
+ # ----- 买入时机信号(三维分析:大盘+行业+个股,基本面+消息面+技术面+资金流)-----
+ # [2026-07-01] 扩展:不再只看volume_signal + candlestick_sentiment
+ # 融合大盘趋势、行业板块强弱、基本面估值作为修正因子
+ volume_signal = vol.get("volume_signal", "")
+ candlestick_sentiment = candle.get("sentiment", "neutral")
+ timing_signal = "neutral"
+
+ # --- 三维分析数据装载 ---
+ # 因子1: 大盘环境(从macro_context_log读)
+ market_bearish = False
+ market_bullish = False
+ try:
+ import sqlite3
+ _db = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=5)
+ _mc = _db.execute(
+ "SELECT structure FROM macro_context_log WHERE has_valid_data=1 ORDER BY rowid DESC LIMIT 1"
+ ).fetchone()
+ if _mc and _mc[0]:
+ _s = json.loads(_mc[0])
+ _overall = _s.get("overall", "")
+ if "bearish" in _overall:
+ market_bearish = True
+ elif _overall == "bullish":
+ market_bullish = True
+ _db.close()
+ except Exception:
+ pass
+
+ # 因子2: 行业板块强弱
+ sector_strong = False
+ sector_weak = False
+ try:
+ _db2 = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=5)
+ _rows2 = _db2.execute(
+ "SELECT name, change_pct FROM sector_snapshots ORDER BY change_pct DESC"
+ ).fetchall()
+ if _rows2:
+ # 找到该股所属行业(简单匹配name或通过stock_sectors)
+ _my_sectors = _db2.execute(
+ "SELECT sector_name FROM stock_sectors WHERE code=?",
+ (code,)
+ ).fetchall()
+ if _my_sectors:
+ for (_sn,) in _my_sectors:
+ for r_name, r_chg in _rows2:
+ if _sn in r_name or r_name in _sn:
+ _rank = [r[0] for r in _rows2].index(r_name) if r_name in [x[0] for x in _rows2] else -1
+ _total = len(_rows2)
+ if _rank >= 0:
+ if _rank < _total * 0.2:
+ sector_strong = True
+ if _rank > _total * 0.8:
+ sector_weak = True
+ break
+ _db2.close()
+ except Exception:
+ pass
+
+ # 因子3: 基本面估值
+ is_value_stock = False
+ try:
+ _db3 = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=5)
+ _fd = _db3.execute(
+ "SELECT pe, eps FROM stock_fundamentals WHERE code=?", (code,)
+ ).fetchone()
+ if _fd:
+ _pe, _eps = _fd
+ is_value_stock = (0 < (_pe or 0) < 25 and (_eps or 0) > 0.3)
+ _db3.close()
+ except Exception:
+ pass
+
+ # --- 三维修正规则 ---
+ # 大盘偏弱时收紧买入信号,大盘偏强时放宽
+ # 行业领先加分,行业落后减分
+ # 低估值加分(有安全边际)
+
+ def _adjust_timing(signal, market_b, market_bb, sec_s, sec_w, is_val):
+ """根据三维因子修正 timing_signal"""
+ # 大盘偏弱时降级买入信号
+ if market_b:
+ if signal in ("买入", "加仓"):
+ if not sec_s: # 大盘弱+行业不强→降级
+ return "关注"
+ # 大盘偏强时放宽
+ if market_bb:
+ if signal == "关注" and (sec_s or is_val):
+ return "买入"
+ # 行业弱势时降级买入信号
+ if sec_w:
+ if signal in ("买入", "加仓"):
+ return "关注"
+ # 行业强势+低估时升级关注
+ if sec_s and is_val:
+ if signal == "关注":
+ return "买入"
+ return signal
+
+ if is_new_entry:
+ # 新买入时机
+ if volume_signal == "主动买盘占优" and candlestick_sentiment == "bullish":
+ timing_signal = "买入"
+ elif volume_signal == "主动卖盘占优":
+ timing_signal = "观望"
+ elif volume_signal == "买卖均衡" and ws and price <= ws * 1.03:
+ timing_signal = "买入"
+ elif candlestick_sentiment == "bullish":
+ timing_signal = "买入"
+ elif ws and price < ws * 1.02:
+ timing_signal = "关注"
+ # 新买入时三维修正:大盘向上+行业强→升级,大盘弱→降级
+ _pre_signal = timing_signal
+ timing_signal = _adjust_timing(timing_signal, market_bearish, market_bullish,
+ sector_strong, sector_weak, is_value_stock)
+ if timing_signal != _pre_signal:
+ print(f" 三维修正(新入): {_pre_signal}→{timing_signal} "
+ f"| 大盘{'弱' if market_bearish else '强' if market_bullish else '中性'}"
+ f"| 行业{'强' if sector_strong else '弱' if sector_weak else '中性'}"
+ f"| 估值{'低' if is_value_stock else '一般'}")
+ else:
+ # 已持仓时机(用于加仓/减仓参考)
+ if is_short_term_strong_trend:
+ # 短炒+强趋势:强趋势持有,禁止加仓信号
+ timing_signal = "持有"
+ elif profit_pct > 5:
+ # 已盈利
+ if volume_signal == "主动买盘占优":
+ timing_signal = "持有"
+ elif volume_signal == "主动卖盘占优" and not is_new_entry:
+ timing_signal = "关注"
+ else:
+ timing_signal = "持有"
+ elif profit_pct > 0:
+ # 微盈
+ if volume_signal == "主动买盘占优":
+ timing_signal = "持有"
+ elif ws and price <= ws * 1.02:
+ timing_signal = "加仓"
+ else:
+ timing_signal = "持有"
+ else:
+ # 浮亏
+ if volume_signal == "主动卖盘占优" and ss and price <= ss * 1.03:
+ timing_signal = "关注"
+ elif volume_signal == "主动买盘占优" and sr_resist and price >= sr_resist * 0.97:
+ timing_signal = "关注"
+ elif volume_signal == "买卖均衡" and ws and price <= ws * 1.02:
+ timing_signal = "加仓"
+ else:
+ timing_signal = "持有"
+
+ # ----- 【v3.2新增】分类约束:弱势/深套禁止输出买入/加仓类信号 -----
+ if stock_category == "弱势" or is_deep_loss:
+ buy_signals = ["买入", "加仓", "可追"]
+ if any(s in timing_signal for s in buy_signals):
+ old_signal = timing_signal
+ timing_signal = "弱势持有" if stock_category == "弱势" else "深套持有"
+ print(f" 分类约束: {stock_category} 原信号\"{old_signal}\" → \"{timing_signal}\"")
+
+ # ----- 构造 action 描述(供 cron prompt 使用) -----
+ action_parts = []
+ if profit_pct < -20:
+ action_parts.append("深套持有")
+ elif profit_pct < -10:
+ action_parts.append("持有观察")
+ elif profit_pct < 0:
+ action_parts.append("持有观察")
+ elif profit_pct < 5:
+ action_parts.append("盈利持有")
+ else:
+ action_parts.append("盈利良好")
+
+ if action_note:
+ action_parts.append(action_note)
+
+ if is_watchlist:
+ # 自选股(未入场):有止损参考+买入区,内部算RR需要止盈位
+ action_parts.append(f"目标参考{new_target}")
+ action_parts.append(f"止损参考{new_stop}")
+ action_parts.append(f"买入区{entry_low}~{entry_high}")
+ elif is_new_entry:
+ action_parts.append(f"损{new_stop}")
+ action_parts.append(f"盈{new_target}")
+ action_parts.append(f"买{entry_low}~{entry_high}")
+ else:
+ action_parts.append(f"止损{new_stop}")
+ action_parts.append(f"目标{new_target}")
+ action_parts.append(f"买入区{entry_low}~{entry_high}")
+
+ if timing_signal != "neutral":
+ action_parts.append(f"信号:{timing_signal}")
+
+ new_action = " | ".join(action_parts)
+
+ # 技术面快照
+ tech_snapshot = ""
+ if candle:
+ tech_snapshot = (f"形态:{candle.get('pattern','?')}/{candle.get('sentiment','?')} "
+ f"量价:{vol.get('volume_signal','?')} "
+ f"强撑:{ss} 弱撑:{ws} 弱压:{wr} 强压:{sr_resist}")
+ # 加入均线信息(如果可用)
+ try:
+ dm = mtf_analysis.get("daily", {}).get("mas", {})
+ ma_parts = []
+ for m in ['ma5', 'ma10', 'ma20', 'ma60']:
+ v = dm.get(m)
+ if v:
+ ma_parts.append(f"{m.upper()}={v}")
+ if ma_parts:
+ tech_snapshot += " | " + " ".join(ma_parts)
+ except (NameError, AttributeError):
+ pass
+
+ # 多周期快照(追加到 tech_snapshot)
+ mtf_context = ""
+ if mtf_adj:
+ trend_align = mtf_adj.get("trend_alignment", "")
+ daily_mas = mtf_analysis.get("daily", {}).get("mas", {})
+ ma20 = daily_mas.get("ma20")
+ ma60 = daily_mas.get("ma60")
+ stop_ref = mtf_adj.get("stop_loss_reference", {})
+ take_ref = mtf_adj.get("take_profit_reference", {})
+
+ parts = []
+ if trend_align:
+ parts.append(trend_align)
+ if ma20:
+ parts.append(f"MA20={ma20}")
+ if ma60:
+ parts.append(f"MA60={ma60}")
+ if stop_ref:
+ parts.append(f"长撑:{stop_ref.get('source','?')}={stop_ref['level']}")
+ if take_ref:
+ parts.append(f"长压:{take_ref.get('source','?')}={take_ref['level']}")
+ mtf_context = " | ".join(parts)
+
+ now_str = datetime.now().strftime('%Y-%m-%d %H:%M')
+ return {
+ 'price': price, # 2026-07-07 修复:加入price供质量门禁使用
+ 'stop_loss': new_stop,
+ 'take_profit': new_target,
+ 'entry_low': entry_low,
+ 'entry_high': entry_high,
+ 'action': new_action,
+ 'status': status,
+ 'tech_snapshot': tech_snapshot,
+ 'timing_signal': timing_signal,
+ 'rr_ratio': round(rr_ratio, 2),
+ 'action_note': action_note,
+ 'reassessed_at': now_str,
+ 'multi_tf_context': mtf_context, # 多周期上下文
+ 'stock_category': stock_category, # 股票分类:短炒/中短线/中长线/弱势/深套
+ 'time_horizon': time_horizon, # 时间跨度
+ 'position_advice': position_advice, # 仓位建议
+ }
+
+
+def load_stock_news_sentiment(code):
+ """加载小果消息面情感"""
+ try:
+ path = "/home/hmo/web-dashboard/data/xiaoguo_sentiment.json"
+ if not os.path.exists(path):
+ return {}
+ xg = json.load(open(path))
+ return xg.get("stocks", {}).get(code, {})
+ except Exception:
+ return {}
+
+
+def load_fundamentals(code):
+ """加载个股基本面"""
+ try:
+ cache = mtf._load_mtf_cache()
+ return cache.get(code, {}).get("fundamentals", {}) or {}
+ except Exception:
+ return {}
+
+
+def _get_portfolio_risk_state():
+ """读取 portfolio 组合风险状态(2026-06-23 引擎协调)"""
+ try:
+ # 数据一致性检查:警告多副本(2026-06-23 bugfix)
+ _check_portfolio_consistency()
+ p = read_portfolio()
+ pos_pct = p.get('position_pct', 0)
+ cash = p.get('cash', 0)
+ holdings = p.get('holdings', [])
+ weak_cnt = sum(1 for h in holdings if h.get('change_pct', 0) < -15)
+ total = len(holdings) or 1
+ weak_ratio = weak_cnt / total
+ return {
+ 'position_pct': pos_pct,
+ 'cash': cash,
+ 'is_high_position': pos_pct > 80,
+ 'is_very_high_position': pos_pct > 90,
+ 'is_high_weak': weak_ratio > 0.35,
+ 'weak_ratio': round(weak_ratio * 100),
+ 'total_holdings': total,
+ }
+ except:
+ return {}
+
+
+def _is_buy_signal(signal):
+ """判断信号是否为买入/持有类(用于防洗盘)"""
+ if not signal:
+ return False
+ buy_keywords = ['买入', '持有', '加仓', '关注']
+ for kw in buy_keywords:
+ if kw in signal:
+ return True
+ return False
+
+
+def _check_portfolio_consistency():
+ """数据一致性检查:如果存在多份 portfolio.json 则报警(2026-06-23 bugfix)"""
+ main = '/home/hmo/web-dashboard/data/portfolio.json'
+ main_cash = None
+ try:
+ import json
+ main_cash = json.load(open(main)).get('cash')
+ except Exception:
+ return
+ for path in [
+ '/home/hmo/data/portfolio.json',
+ '/home/hmo/projects/MoFin/data/portfolio.json',
+ '/home/hmo/web-dashboard.bak/data/portfolio.json',
+ ]:
+ if os.path.exists(path):
+ try:
+ other = json.load(open(path))
+ if other.get('cash') != main_cash:
+ print(f"⚠️ 数据一致性: {os.path.realpath(path)} cash={other.get('cash')} ≠ 主文件 cash={main_cash} (需清理)", file=sys.stderr)
+ except Exception:
+ pass
+
+
+def _check_contradiction(code, today_only=True):
+ """反馈循环核——检查本股是否有刚卖出的记录
+
+ 返回 dict or None:
+ - sold_reason: 'portfolio_trim'|'stop_loss'
+ - sold_at: 卖出日期
+ - days_ago: 卖出距今交易日数
+ - is_today: 是否今日卖出
+ - tag: 追加到信号的标注
+ """
+ try:
+ from datetime import datetime, date
+ dec = read_decisions()
+ for e in dec.get('decisions', []):
+ if e.get('code') != code:
+ continue
+ sold_at = e.get('sold_at', '')
+ if not sold_at:
+ return None
+ try:
+ sd = datetime.strptime(sold_at, '%Y-%m-%d').date()
+ td = date.today()
+ days = (td - sd).days
+ except:
+ return None
+
+ reason = e.get('sold_reason', 'portfolio_trim')
+ if reason == 'stop_loss':
+ tag = '止损离场(逻辑破坏,短期不关注)'
+ else:
+ tag = '组合减仓后关注(已清仓,等回踩确认)'
+
+ return {
+ 'sold_reason': reason,
+ 'sold_at': sold_at,
+ 'days_ago': days,
+ 'is_today': days == 0,
+ 'tag': tag,
+ }
+ except:
+ return None
+ return None
+
+
+def _get_sell_priority_list():
+ """减仓优先级排序:深套>亏损>微盈>盈利(2026-06-23 反馈循环)
+
+ 返回 [(code, name, change_pct, position_pct, priority_label), ...]
+ 按卖出的优先顺序排列(最先应该卖的在最前)
+ """
+ try:
+ p = read_portfolio()
+ holdings = p.get('holdings', [])
+ ranked = []
+ for h in holdings:
+ chg = h.get('change_pct', 0)
+ pos = h.get('position_pct', 0)
+ if chg < -30:
+ label = '深套(>30%),优先减'
+ rank = 0
+ elif chg < -20:
+ label = '深套(>20%),优先减'
+ rank = 1
+ elif chg < -10:
+ label = '亏损,建议减'
+ rank = 2
+ elif chg < 0:
+ label = '微亏,可减'
+ rank = 3
+ elif chg < 10:
+ label = '微盈,持有'
+ rank = 4
+ else:
+ label = '盈利,最后减'
+ rank = 5
+ ranked.append((rank, h['code'], h.get('name',''), chg, pos, label))
+ ranked.sort(key=lambda x: (x[0], -x[4])) # 优先 rank, 其次仓位大优先
+ return [{'code':c,'name':n,'change_pct':chg,'position_pct':pos,'label':l}
+ for r,c,n,chg,pos,l in ranked]
+ except:
+ return []
+
+
+def enrich_timing_signal(base_signal, macro_desc="", sector_note="",
+ profit_pct=0, stock_category="", is_new_entry=False,
+ fundamentals=None, news_sentiment=None,
+ timing_signal_override=None,
+ portfolio_context=None,
+ rr_ratio=0): # 2026-06-24 新参:盈亏比约束
+ """多因子合成timing_signal——大盘+行业+基本面+技术+组合风险+盈亏比
+
+ 返回 (enriched_signal, factors_list)
+ - enriched_signal: 可读的多因子信号描述
+ - factors_list: 各因子的摘要列表(用于后续显示)
+ """
+ # 如果已手动设定,尊重手动
+ if timing_signal_override and timing_signal_override != "neutral":
+ return timing_signal_override, [timing_signal_override]
+
+ factors = []
+
+ # 1. 大盘因子
+ if "偏强" in macro_desc or "大涨" in macro_desc or "bullish" in macro_desc.lower():
+ macro_txt = "大盘偏强"
+ factors.append(macro_txt)
+ elif "偏弱" in macro_desc or "大跌" in macro_desc or "bearish" in macro_desc.lower():
+ macro_txt = "大盘偏弱"
+ factors.append(macro_txt)
+ elif macro_desc and macro_desc != "宏观未加载":
+ factors.append("大盘中性")
+
+ # 2. 行业因子
+ if sector_note:
+ # 把"行业X大跌3%+"简化为"行业偏弱","行业X大涨3%+"简化为"行业偏强"
+ if "大跌" in sector_note or "下跌" in sector_note:
+ factors.append("行业偏弱")
+ elif "大涨" in sector_note:
+ factors.append("行业偏强")
+ elif "上涨" in sector_note:
+ factors.append("行业偏强")
+ else:
+ factors.append("行业中性")
+
+ # 3. 基本面因子
+ if fundamentals:
+ pe = fundamentals.get("pe", 0)
+ eps = fundamentals.get("eps", 0)
+ profit_growth = fundamentals.get("profit_growth", fundamentals.get("yoy_profit", ""))
+ revenue_growth = fundamentals.get("revenue_growth", fundamentals.get("yoy_revenue", ""))
+ mcap = fundamentals.get("mcap_total", 0)
+
+ pe = pe or 0
+ eps = eps or 0
+ profit_growth_str = str(profit_growth or "")
+ revenue_growth_str = str(revenue_growth or "")
+
+ # 净利增长
+ for val in [profit_growth_str, revenue_growth_str]:
+ try:
+ v = float(val.replace("%", "").replace("+", ""))
+ if v > 50:
+ factors.append("净利增50%+")
+ break
+ elif v > 20:
+ factors.append(f"净利增{int(v)}%")
+ break
+ elif v < -20:
+ factors.append("净利降20%+")
+ break
+ except (ValueError, AttributeError):
+ continue
+
+ # PE估值
+ if 0 < pe < 15:
+ factors.append("低估值")
+ elif pe > 100 or pe < 0:
+ factors.append("高估值")
+
+ # 市值
+ if mcap and mcap > 5000:
+ factors.append("蓝筹")
+
+ # 4. 消息面因子(小果情感)
+ if news_sentiment:
+ ns = news_sentiment.get("sentiment", "")
+ nc = news_sentiment.get("confidence", 0)
+ if ns == "positive" and nc >= 0.7:
+ kws = news_sentiment.get("keywords", [])
+ kw_str = f"({'/'.join(kws[:3])})" if kws else ""
+ factors.append(f"消息偏多{kw_str}")
+ elif ns == "negative" and nc >= 0.7:
+ kws = news_sentiment.get("keywords", [])
+ kw_str = f"({'/'.join(kws[:3])})" if kws else ""
+ factors.append(f"消息偏空{kw_str}")
+
+ # 5. 技术面(基础信号)
+ if base_signal and base_signal != "neutral":
+ factors.append(base_signal)
+
+ # 5.5 组合风险因子(2026-06-23 双引擎协调)
+ if portfolio_context and not is_new_entry:
+ if portfolio_context.get('is_very_high_position'):
+ factors.append("组合仓位极重(>90%)")
+ elif portfolio_context.get('is_high_position'):
+ factors.append("组合仓位偏重(>80%)")
+ if portfolio_context.get('is_high_weak'):
+ factors.append(f"弱势占{portfolio_context.get('weak_ratio')}%")
+ elif portfolio_context and is_new_entry:
+ # 新买入推荐:注明组合上下文
+ if portfolio_context.get('is_high_position'):
+ factors.append(f"仓{portfolio_context.get('position_pct')}%现金有限")
+ elif portfolio_context.get('is_high_weak'):
+ factors.append("组合风险信号")
+
+ # 5.7 盈亏比因子(2026-06-24 新增——RR<1.5降级买入信号)
+ if rr_ratio > 0:
+ if rr_ratio < 1.5:
+ factors.append(f"RR{rr_ratio}过低")
+ elif rr_ratio >= 3:
+ factors.append(f"RR{rr_ratio}")
+ # 1.5~3之间:中性,不特别标注
+
+ # 如果没有足够因素,返回信号不充分
+ if not factors:
+ return "信号不充分", []
+
+ # 信号只应包含明确的买卖方向,不能从行业/大盘等上下文因子拼凑
+ # base_signal 存在且非 neutral → 用 base_signal
+ # 否则 → 信号不充分(不拿 factors[-1] 当信号)
+ if base_signal and base_signal != "neutral":
+ clean_signal = base_signal
+ else:
+ # 从 factors 中找第一个有效的操作方向信号
+ valid_direction = {"买入", "加仓", "观望", "持有", "关注", "信号不充分"}
+ signal_found = ""
+ for f in reversed(factors):
+ if f in valid_direction:
+ signal_found = f
+ break
+ clean_signal = signal_found if signal_found else "信号不充分"
+
+ # 6. RR约束降级(2026-06-24 新增)
+ # 买入/加仓信号但RR<1.5 → 降级为"信号不充分"
+ buy_signals = {"买入", "加仓"}
+ if clean_signal in buy_signals and 0 < rr_ratio < 1.5:
+ clean_signal = "信号不充分"
+ factors.append("RR过低降级")
+
+ return clean_signal, factors
+
+
+def reassess_with_context(code, name, price, cost, shares, current_action,
+ volume_signal="", sentiment="neutral", is_watchlist=False):
+ """reassess_strategy + 多因子信号合成(大盘+行业+技术)
+
+ 为 per_stock_reassess 等单只场景提供一站式多因子分析
+ """
+ result = reassess_strategy(
+ code, name, price, cost, shares,
+ current_action, volume_signal, sentiment, is_watchlist
+ )
+ if not result:
+ return result
+
+ # 加载宏观+行业+消息+基本面上下文
+ try:
+ macro_bias, macro_desc = load_macro_context()
+ market_ctx = load_market_context()
+ stock_sector_map = load_stock_sector_map()
+ sector_adj = compute_sector_adjustment(code, market_ctx, stock_sector_map)
+ sector_note = sector_adj.get("note", "")
+ news_sentiment = load_stock_news_sentiment(code)
+ fund = load_fundamentals(code)
+ except Exception:
+ macro_desc = ""
+ sector_note = ""
+ news_sentiment = {}
+ fund = {}
+
+ # ── DSA 集成:注入大盘复盘 + 新闻情报 ──────────────────────────
+ try:
+ from mo_bridge import enrich_analysis_context
+ region = "hk" if len(str(code)) == 5 and str(code)[0] in ('0','1') else "cn"
+ dsa_ctx = enrich_analysis_context(stock_code=code, stock_name=name,
+ region=region, include_news=True)
+ if dsa_ctx:
+ macro_desc = (macro_desc + "\n\n" + dsa_ctx).strip()
+ except Exception:
+ pass # DSA 不可用时静默跳过
+
+ enriched, factors = enrich_timing_signal(
+ base_signal=result.get("timing_signal", ""),
+ macro_desc=macro_desc,
+ sector_note=sector_note,
+ profit_pct=(price - cost) / cost * 100 if cost else 0,
+ stock_category=result.get("stock_category", ""),
+ is_new_entry=is_watchlist,
+ fundamentals=fund,
+ news_sentiment=news_sentiment,
+ portfolio_context=_get_portfolio_risk_state(),
+ rr_ratio=result.get("rr_ratio", 0),
+ )
+ result["timing_signal"] = enriched
+ result["signal_factors"] = factors
+
+ # 6. 防洗盘:信号不要一天一翻(2026-06-23)
+ # 如果旧信号是买入/持有类,新信号是谨慎/等待类,但中期趋势未破→维持旧信号
+ try:
+ dec = read_decisions()
+ for e in dec.get('decisions', []):
+ if e.get('code') == code:
+ old_signal = e.get('timing_signal', '')
+ if old_signal and _is_buy_signal(old_signal) and not _is_buy_signal(enriched):
+ # 中等趋势检查:MA5 > MA20 + 多周期看多
+ mtf = result.get('multi_tf_context', '')
+ if '看多' in mtf or '多头' in mtf:
+ try:
+ closes = [float(k.split()[2]) for k in mtf.split('|') if 'MA5' in k]
+ except:
+ closes = []
+ has_uptrend = 'MA5' in mtf and 'MA20' in mtf
+ if has_uptrend:
+ print(f" 防洗盘: {old_signal}→保持旧信号(中期趋势完整)")
+ result["timing_signal"] = f"{old_signal}(正常回调价稳)"
+ sf = result.get("signal_factors") or []
+ if "正常回调价稳" not in sf:
+ result["signal_factors"] = sf + ["正常回调价稳"]
+ break
+ except Exception as e:
+ print(f" 防洗盘跳过: {e}")
+
+ # 7. 反馈循环核:检查本股是否有刚卖出的记录(2026-06-23)
+ contradiction = _check_contradiction(code)
+ if contradiction and contradiction.get('is_today'):
+ # 今日刚卖出 → 不屏蔽信号,但必须自标注矛盾
+ print(f" 反馈循环: {contradiction.get('tag')} (sold_at={contradiction.get('sold_at')})")
+ if _is_buy_signal(result.get('timing_signal', '')):
+ result['action_note'] = contradiction['tag']
+ # 在 timing_signal 中追加反馈标注,供报告层可见
+ curr_signal = result.get('timing_signal', '')
+ if '⚠️' not in curr_signal:
+ result['timing_signal'] = f"⚠️{contradiction['tag']}|{curr_signal}"
+ elif contradiction:
+ # 非今日卖出但近期卖出 → 标注已清仓
+ print(f" 近期清仓: sold_at={contradiction.get('sold_at')} ({contradiction.get('days_ago')}日前)")
+ if _is_buy_signal(result.get('timing_signal', '')):
+ curr_signal = result.get('timing_signal', '')
+ if '已清仓' not in curr_signal:
+ result['timing_signal'] = f"已清仓,{curr_signal}"
+
+ # 重建 action 文本(同步多因子信号)
+ try:
+ if new_action_needs_refresh(result, {"source": "auto"}, price):
+ _refresh_action_text(result, price, name)
+ except Exception:
+ pass
+
+ # ── 策略质量门禁 ──
+ enforce_strategy_quality(code, name, result)
+
+ return result
+
+
+def new_action_needs_refresh(result, old_entry, price):
+ """判断宏观/行业调整后是否需要刷新action文本"""
+ # 自选股和手动策略不做调整,不需要刷新
+ if old_entry.get("source") == "manual":
+ return False
+ return True
+
+
+def _refresh_action_text(result, price, name):
+ """根据调整后的止损/止盈重建action文本"""
+ sl = result.get("stop_loss", 0)
+ tp = result.get("take_profit", 0)
+ el = result.get("entry_low", 0)
+ eh = result.get("entry_high", 0)
+ ts = result.get("timing_signal", "")
+ an = result.get("action_note", "")
+ old_action = result.get("action", "")
+
+ # 保持原action的前缀(持有状态部分不变)
+ # action格式一般是: "状态 | 止损X | 目标Y | 买入区X~Y | 信号:Z"
+ parts = old_action.split(" | ")
+ new_parts = []
+ for p in parts:
+ p = p.strip()
+ # 替换止损数字
+ if p.startswith("止损") or p.startswith("止损参考"):
+ if sl:
+ p = f"止损{sl}" if "止损参考" not in old_action.split(" | ")[0] else f"止损参考{sl}"
+ # 替换目标/止盈数字
+ if p.startswith("目标") or p.startswith("止盈"):
+ if tp:
+ p = f"目标{tp}"
+ # 替换买入区数字
+ if "买入区" in p and "~" in p:
+ if el and eh:
+ p = f"买入区{el}~{eh}"
+ new_parts.append(p)
+ result["action"] = " | ".join(new_parts)
+
+
+def check_sector_alerts(market_ctx, stock_sector_map, holdings, wl):
+ """行业轮动主动预警:检测板块崩盘级别信号→查持仓→输出预警
+
+ 返回 list of alerts: [{code, name, sector, chg, action}]
+ """
+ alerts = []
+ if not market_ctx:
+ return alerts
+
+ sector_perf = market_ctx.get("sector_perf", {})
+
+ # 找出所有跌幅>3%的行业
+ crashing_sectors = {name: data for name, data in sector_perf.items()
+ if data.get("change", 0) <= -3}
+
+ if not crashing_sectors:
+ return alerts
+
+ # 构建 code→持仓信息 的映射
+ holding_map = {}
+ for h in holdings:
+ c = h.get("code", "")
+ if c:
+ holding_map[c] = {"name": h.get("name", c), "type": "持仓"}
+ for s in wl.get("stocks", []):
+ c = s.get("code", "")
+ if c and c not in holding_map:
+ holding_map[c] = {"name": s.get("name", c), "type": "自选"}
+
+ # 对每个暴跌行业,查持仓中是否有股票属于该行业
+ for sec_name, sec_data in sorted(crashing_sectors.items(),
+ key=lambda x: x[1].get("change", 0)):
+ chg = sec_data.get("change", 0)
+ for code, sectors in stock_sector_map.items():
+ if code in holding_map and sec_name in sectors:
+ info = holding_map[code]
+ alerts.append({
+ "code": code,
+ "name": info["name"],
+ "sector": sec_name,
+ "sector_change": chg,
+ "type": info["type"],
+ "action": f"行业{sec_name}跌{chg:+.1f}%,{info['type']}需关注",
+ })
+
+ alerts.sort(key=lambda a: a["sector_change"])
+ return alerts
+
+
+def regenerate_all(stdout=True):
+ """全量重评所有持仓+自选策略"""
+ # 优先从 SQLite 读取
+ try:
+ from mofin_db import get_conn, query_holdings, query_watchlist
+ conn = get_conn()
+ holdings = query_holdings(conn)
+ wl_stocks = query_watchlist(conn)
+ conn.close()
+ pf = {"holdings": holdings}
+ wl = {"stocks": wl_stocks}
+ except Exception:
+ try:
+ pf = read_portfolio()
+ wl = read_watchlist()
+ except Exception:
+ pf = {}
+ wl = {}
+
+ all_stocks = {}
+ for item in pf.get("holdings", []):
+ code = item.get("code", "")
+ if code:
+ all_stocks[code] = {"source": "portfolio", "data": item}
+ for item in wl.get("stocks", []):
+ code = item.get("code", "")
+ if code and code not in all_stocks:
+ all_stocks[code] = {"source": "watchlist", "data": item}
+
+ total = len(all_stocks)
+ ok = 0
+ errors = 0
+ results = []
+ decisions = []
+
+ # 加载现有 decisions.json 以便追踪变更
+ decisions_path = "/home/hmo/web-dashboard/data/decisions.json"
+ try:
+ existing_decisions = {d["code"]: d for d in read_decisions().get("decisions", []) if d.get("code")}
+ except:
+ existing_decisions = {}
+
+ # 加载宏观上下文(影响策略参数调整)
+ macro_bias, macro_desc = load_macro_context()
+ if stdout:
+ print(f" 宏观参考: {macro_desc} (bias={macro_bias})")
+
+ # 加载市场上下文 — 行业板块表现 + 大盘宽度(策略参数调整用)
+ market_ctx = load_market_context()
+ stock_sector_map = load_stock_sector_map()
+ market_breadth = market_ctx.get("breadth", 50)
+ market_mood = market_ctx.get("mood", "neutral")
+ if stdout:
+ sectors_found = sum(1 for c in all_stocks if stock_sector_map.get(c))
+ print(f" 市场参考: {market_mood} 上涨比{market_breadth}% 已匹配{sectors_found}/{total}只个股行业")
+
+ # 批量预取所有价格(一次API调用 vs 之前N次)
+ prices_map = batch_fetch_prices(list(all_stocks.keys()))
+ if stdout:
+ print(f" 批量获取价格: {len(prices_map)}/{total} 成功")
+
+ for code, info in sorted(all_stocks.items()):
+ stock = info["data"]
+ name = stock.get("name", code)
+ cost = stock.get("cost", 0) or 0
+ shares = stock.get("shares", 0) or 0
+ source = info["source"]
+
+ q = prices_map.get(code)
+ if not q or not q.get("price"):
+ results.append({"code": code, "name": name, "error": "腾讯API无数据"})
+ errors += 1
+ if stdout:
+ print(f" ❌ {name}({code}): 腾讯API无数据")
+ continue
+
+ price = q["price"]
+ profit_pct = (price - cost) / cost * 100 if cost else 0
+ current_action = stock.get("analysis", {}).get("action", "")
+ close_yest = q.get("close", 0)
+ sentiment = "neutral"
+ if close_yest and price > close_yest * 1.02:
+ sentiment = "bullish"
+ elif close_yest and price < close_yest * 0.98:
+ sentiment = "bearish"
+
+ try:
+ is_wl = (source == "watchlist")
+ result = reassess_strategy(
+ code, name, price, cost, shares,
+ current_action, volume_signal="中性", sentiment=sentiment,
+ is_watchlist=(source == "watchlist"),
+ )
+
+ # --- Manual param preservation: 用户手动策略永不覆盖 ---
+ old_entry = existing_decisions.get(code, {})
+ if old_entry.get("source") == "manual":
+ # 仅覆盖策略参数,技术分析/信号/价格照常保留
+ for key in ["entry_low", "entry_high", "stop_loss", "take_profit"]:
+ if key in old_entry and old_entry[key] is not None:
+ result[key] = old_entry[key]
+ # 重算盈亏比(基于手动参数)
+ manual_stop = result.get("stop_loss", 0) or 0
+ manual_target = result.get("take_profit", 0) or 0
+ risk = max(price - manual_stop, price * 0.01) if manual_stop > 0 else price * 0.01
+ reward = max(manual_target - price, 0) if manual_target > 0 else 0
+ result["rr_ratio"] = round(reward / risk, 2) if risk > 0 else 0
+ # 重建 action 文本(引用手动参数,不引用自动计算的)
+ profit_pct = (price - cost) / cost * 100 if cost else 0
+ manual_action_parts = []
+ if profit_pct < -20:
+ manual_action_parts.append("深套持有")
+ elif profit_pct < -10:
+ manual_action_parts.append("持有观察")
+ elif profit_pct < 0:
+ manual_action_parts.append("持有观察")
+ elif profit_pct < 5:
+ manual_action_parts.append("盈利持有")
+ else:
+ manual_action_parts.append("盈利良好")
+ if result.get("action_note"):
+ manual_action_parts.append(result["action_note"])
+ if is_wl:
+ if manual_stop > 0:
+ manual_action_parts.append(f"止损参考{manual_stop}")
+ manual_action_parts.append(f"买入区{result['entry_low']}~{result['entry_high']}")
+ else:
+ if manual_stop > 0:
+ manual_action_parts.append(f"止损{manual_stop}")
+ if manual_target > 0:
+ manual_action_parts.append(f"目标{manual_target}")
+ manual_action_parts.append(f"买入区{result['entry_low']}~{result['entry_high']}")
+ ts = result.get("timing_signal", "")
+ if ts and ts != "neutral":
+ manual_action_parts.append(f"信号:{ts}")
+ result["action"] = " | ".join(manual_action_parts)
+ result["status"] = "manual" # 标记为手动管理,变更追踪不受影响
+ if stdout:
+ print(f" [手动保留] {name}({code}) 策略参数未覆盖")
+
+ # 宏观偏差调整:收盘后重评时根据宏观方向微调止损/止盈
+ # 自选股不做止盈宏观调整(无持仓)
+ # 手动策略不做宏观偏差调整(尊重用户设定)
+ if macro_bias != 1.0 and not is_wl and old_entry.get("source") != "manual":
+ old_stop = result.get("stop_loss", 0)
+ old_target = result.get("take_profit", 0)
+ if macro_bias < 1.0 and old_stop > 0: # 宏观偏弱 → 收紧止损
+ # 止损上移(但保留最小3%间距)
+ adjusted_stop = round(old_stop * (1 + (1 - macro_bias) * 0.3), 2)
+ min_stop = round(price * 0.97, 2)
+ result["stop_loss"] = min(adjusted_stop, min_stop)
+ if old_target > 0:
+ result["take_profit"] = round(old_target * (1 - (1 - macro_bias) * 0.2), 2)
+ elif macro_bias > 1.0 and old_target > 0: # 宏观偏强 → 止盈上调让利润跑
+ result["take_profit"] = round(old_target * (1 + (macro_bias - 1) * 0.3), 2)
+
+ # 行业偏差调整:根据个股所在行业的市场表现微调止损/止盈
+ # 手动策略不做行业调整(尊重用户设定)
+ sector_adj = compute_sector_adjustment(code, market_ctx, stock_sector_map)
+ sector_note = sector_adj.get("note", "")
+ if sector_note and old_entry.get("source") != "manual":
+ old_stop = result.get("stop_loss", 0)
+ old_target = result.get("take_profit", 0)
+ stop_bias = sector_adj.get("stop_bias", 1.0)
+ target_bias = sector_adj.get("target_bias", 1.0)
+ if stop_bias != 1.0 and old_stop > 0:
+ # 行业偏差调整(在宏观调整之后叠加)
+ adjusted = round(old_stop * stop_bias, 2)
+ # 保留最小3%间距
+ min_stop = round(price * 0.97, 2)
+ result["stop_loss"] = min(adjusted, min_stop)
+ if target_bias != 1.0 and old_target > 0 and not is_wl:
+ result["take_profit"] = round(old_target * target_bias, 2)
+
+ # 加载消息面+基本面(逐个股)
+ news_sentiment = load_stock_news_sentiment(code)
+ fund = load_fundamentals(code)
+
+ # 多因子合成 timing_signal:大盘+行业+消息+基本面+技术
+ if old_entry.get("source") != "manual":
+ enriched, _ = enrich_timing_signal(
+ base_signal=result.get("timing_signal", ""),
+ macro_desc=macro_desc,
+ sector_note=sector_note,
+ profit_pct=profit_pct,
+ stock_category=result.get("stock_category", ""),
+ is_new_entry=(source == "watchlist"),
+ fundamentals=fund,
+ news_sentiment=news_sentiment,
+ rr_ratio=result.get("rr_ratio", 0),
+ )
+ result["timing_signal"] = enriched
+
+ # 在宏观/行业/多因子调整后重建 action 文本(同步调整后的止损/止盈数字)
+ if new_action_needs_refresh(result, old_entry, price):
+ _refresh_action_text(result, price, name)
+
+ extra = {
+ "rr_ratio": result.get("rr_ratio"),
+ "action_note": result.get("action_note", ""),
+ "timing_signal": result.get("timing_signal", ""),
+ }
+ analysis = {
+ "stop_loss": result["stop_loss"],
+ "take_profit": result["take_profit"],
+ "entry_low": result["entry_low"],
+ "entry_high": result["entry_high"],
+ "action": result["action"],
+ "tech_snapshot": result.get("tech_snapshot", ""),
+ "multi_tf_context": result.get("multi_tf_context", ""),
+ "reassessed_at": result["reassessed_at"],
+ "status": result["status"],
+ **extra,
+ }
+ stock["analysis"] = analysis
+ # 同步 top-level 字段 → zone_breach/price_monitor 依赖这些字段
+ # (2026-06-24 bugfix: analysis 子对象有但顶层没有,导致新持仓的止损检测盲区)
+ stock["stop_loss"] = result.get("stop_loss", 0)
+ stock["take_profit"] = result.get("take_profit", 0)
+ stock["entry_low"] = result.get("entry_low", 0)
+ stock["entry_high"] = result.get("entry_high", 0)
+ # 同步 trigger 字段 -> price_monitor 依赖
+ sl = result.get("stop_loss", 0)
+ tp = result.get("take_profit", 0)
+ el = result.get("entry_low", 0)
+ eh = result.get("entry_high", 0)
+ trig = {}
+ if sl and float(sl) > 0:
+ trig["stop_loss"] = float(sl)
+ if el and eh and float(el) > 0 and float(eh) > 0:
+ trig["entry_zone"] = f"{float(el)}~{float(eh)}"
+ if tp and float(tp) > 0:
+ trig["take_profit_zone"] = f"0~{float(tp)}"
+ stock["trigger"] = trig
+ results.append({
+ "code": code, "name": name,
+ "price": price, "cost": cost,
+ "action": result["action"],
+ "stop_loss": result["stop_loss"],
+ "take_profit": result["take_profit"],
+ "rr_ratio": result["rr_ratio"],
+ })
+ ok += 1
+ if stdout:
+ rr_str = f" RR={result['rr_ratio']}" if "rr_ratio" in result else ""
+ print(f" ✅ {name}({code}) {price} {result['action']}{rr_str}")
+
+ # 记录所有股票的决策日志(含变更追踪)
+ status_display = result.get("status", "active")
+ # 构建行业上下文
+ sector_ctx_str = ""
+ sec_name = sector_adj.get("sector_name", "")
+ sec_chg = sector_adj.get("sector_change", 0)
+ if sec_name:
+ sector_ctx_str = f"行业{sec_name}{sec_chg:+.1f}%"
+ if sector_adj.get("note"):
+ # note 已包含大盘宽度信息
+ sector_ctx_str = sector_adj["note"]
+ elif market_breadth < 40:
+ # 无行业映射时至少记录大盘宽度
+ sector_ctx_str = f"大盘上涨比{market_breadth}%"
+ new_entry = {
+ "code": code, "name": name, "price": price,
+ "cost": old_entry.get("cost", cost) if old_entry else cost, # 优先保留旧成本(holding.xls权威)
+ "shares": shares, # 当前实际持仓股数(不继承旧决策的可能为0的值)
+ "avg_price": old_entry.get("avg_price", 0), # 保留持仓均价
+ "currency": "HKD" if is_hk_stock(str(code)) else "CNY",
+ "action": result["action"],
+ "stop_loss": result.get("stop_loss"),
+ "entry_low": result["entry_low"],
+ "entry_high": result["entry_high"],
+ "tech_snapshot": result.get("tech_snapshot", ""),
+ "timing_signal": result.get("timing_signal", ""),
+ "rr_ratio": result.get("rr_ratio", 0),
+ "status": status_display,
+ "note": result.get("action_note", ""),
+ "timestamp": result["reassessed_at"],
+ "updated_at": result["reassessed_at"],
+ "type": "自选策略" if is_wl else "持仓策略",
+ "source": old_entry.get("source", "auto"), # manual/auto,继承旧标记
+ "sector_context": sector_ctx_str, # 市场上下文:行业表现+大盘宽度
+ "stock_category": result.get("stock_category", "中短线"), # 组合监测用
+ "position_advice": result.get("position_advice", "中等仓位"),
+ "time_horizon": result.get("time_horizon", "2周~3月"),
+ }
+ new_entry["trigger"] = trig
+ # created_at: 首次创建时设置,后续 preserve
+ old_entry = existing_decisions.get(code, {})
+ if old_entry.get("created_at"):
+ new_entry["created_at"] = old_entry["created_at"]
+ else:
+ new_entry["created_at"] = result["reassessed_at"]
+ # 保留 last_reassessed_price(per_stock_reassess 维护的防抖字段)
+ if old_entry.get("last_reassessed_price"):
+ new_entry["last_reassessed_price"] = old_entry["last_reassessed_price"]
+ # 自选股也写止盈位(用于RR校验),但标签用"目标参考"非"止盈"
+ new_entry["take_profit"] = result.get("take_profit")
+
+ # --- 变更追踪 ---
+ old_action = old_entry.get("action", "")
+ old_stop = old_entry.get("stop_loss")
+ old_target = old_entry.get("take_profit")
+
+ # 构建旧策略摘要和变更理由
+ update_reason = ""
+ changelog_entry = None
+
+ if old_action and old_action != result["action"]:
+ # 策略有变化 → 记录变更
+ old_summary = old_action
+ new_summary = result["action"]
+
+ # 判断触发原因
+ if abs(price - old_entry.get("price", price)) / max(price, 0.01) > 0.03:
+ trigger = f"价格变动({old_entry.get('price','?')}→{price})"
+ elif result.get("timing_signal") and result["timing_signal"] != old_entry.get("timing_signal", ""):
+ trigger = f"技术信号变化: {result['timing_signal']}"
+ else:
+ trigger = "技术面重评"
+
+ # 格式化的变更理由(自选股只看止损,不看止盈)
+ diff_parts = []
+ if old_stop and result["stop_loss"] != old_stop:
+ diff_parts.append(f"止损{old_stop}→{result['stop_loss']}")
+ if not is_wl and old_target and result.get("take_profit") and result["take_profit"] != old_target:
+ diff_parts.append(f"止盈{old_target}→{result['take_profit']}")
+ if diff_parts:
+ update_reason = f"{trigger}: {', '.join(diff_parts)} | {result.get('tech_snapshot','')[:60]}"
+ else:
+ update_reason = f"{trigger}: 策略文字调整"
+
+ changelog_entry = {
+ "date": result["reassessed_at"],
+ "old_action": old_action,
+ "new_action": result["action"],
+ "reason": update_reason,
+ "trigger": trigger,
+ }
+ new_entry["updated_reason"] = update_reason
+
+ elif not old_action:
+ # 首次创建策略
+ update_reason = f"初始策略创建 | {result.get('tech_snapshot','')[:60]}"
+ changelog_entry = {
+ "date": result["reassessed_at"],
+ "old_action": "",
+ "new_action": result["action"],
+ "reason": update_reason,
+ "trigger": "初始创建",
+ }
+
+ # 合并changelog
+ old_changelog = old_entry.get("changelog", []) if old_entry else []
+ if changelog_entry:
+ new_entry["changelog"] = old_changelog + [changelog_entry]
+ else:
+ new_entry["changelog"] = old_changelog
+
+ # 保留执行记录
+ if old_entry and old_entry.get("execution"):
+ new_entry["execution"] = old_entry["execution"]
+ elif stock.get("analysis", {}).get("status") == "executing":
+ new_entry["execution"] = {
+ "status": "executing",
+ "entry_price": cost if cost else 0,
+ "shares": shares,
+ "notes": "",
+ }
+
+ # --- 自动标记 current_recommend ---
+ # 只在真正执行中的持仓才自动推荐:execution.status 为 executing 或 partial_exit
+ exec_status = old_entry.get("execution", {}).get("status", "") if old_entry else ""
+ is_active = exec_status in ("executing", "partial_exit")
+
+ profit_pct = (price - cost) / cost * 100 if cost else 0
+ is_deep_loss_stock = profit_pct < -20
+ rr = result.get("rr_ratio", 0)
+ ts = result.get("timing_signal", "")
+ note = result.get("action_note", "")
+
+ # 计算是否在/接近买入区
+ entry_low_val = result.get("entry_low", 0)
+ entry_high_val = result.get("entry_high", 0)
+ in_buy_zone = (entry_low_val > 0 and entry_high_val > 0 and
+ entry_low_val <= price <= entry_high_val)
+ near_buy_zone_low = (entry_low_val > 0 and
+ price >= entry_low_val * 0.98 and
+ price <= entry_high_val)
+
+ # 推荐条件:必须是执行中的持仓 + 基本面条件达标
+ is_recommendable = (
+ is_active
+ and not is_deep_loss_stock
+ and rr >= 1.5
+ and ts != "neutral"
+ and "不建议" not in note
+ )
+ if is_recommendable:
+ new_entry["tag"] = "current_recommend"
+ else:
+ # 不清除 active_manual(用户手动标记),只清除自动推荐的
+ old_tag = old_entry.get("tag", "") if old_entry else ""
+ if old_tag != "active_manual":
+ new_entry.pop("tag", None)
+
+ decisions.append(new_entry)
+
+ except Exception as e:
+ results.append({"code": code, "name": name, "error": str(e)})
+ errors += 1
+ if stdout:
+ print(f" ❌ {name}({code}): {e}")
+
+ # 写回数据文件 — 保留现有字段(现金、总资产等)不丢
+ try:
+ existing_pf = read_portfolio()
+ except Exception:
+ existing_pf = {}
+ # 保留 price/change_pct — price_monitor 维护的实时价,regenerate_all 不应清除
+ _existing_holdings_map = {}
+ for _h in existing_pf.get('holdings', []):
+ if _h.get('code'):
+ _existing_holdings_map[_h['code']] = _h
+ _new_holdings = pf.get("holdings", [])
+ for _h in _new_holdings:
+ _code = _h.get('code')
+ if _code and _code in _existing_holdings_map:
+ _old = _existing_holdings_map[_code]
+ _h['price'] = _old.get('price', 0)
+ _h['change_pct'] = _old.get('change_pct', 0)
+ existing_pf["holdings"] = _new_holdings
+ existing_pf["updated_at"] = datetime.now().strftime('%Y-%m-%d %H:%M')
+
+ # ── Watchlist ↔ Holdings 双向自动迁移(2026-06-27 Dad要求)──
+ # ① 持仓已有 → 从自选移除(买入自动清除)
+ wl_codes = {s.get("code") for s in wl.get("stocks", []) if s.get("code")}
+ pf_codes = {h.get("code") for h in _new_holdings if h.get("code") and h.get("shares", 0) > 0}
+ removed_from_wl = []
+ for h_code in wl_codes & pf_codes:
+ # 持仓>0且量够 → 自选移除
+ wl["stocks"] = [s for s in wl.get("stocks", []) if s.get("code") != h_code]
+ removed_from_wl.append(h_code)
+ if removed_from_wl and stdout:
+ print(f" 自选→持仓自动移除: {', '.join(removed_from_wl)}")
+
+ # ② 清仓/卖光 → 加回自选(只要仍有关注价值)
+ added_to_wl = []
+ old_pf_codes = {_h.get("code") for _h in existing_pf.get("holdings", []) if _h.get("code")}
+ sold_codes = old_pf_codes - pf_codes # 曾持仓但现在没有(或不在了)
+ for sc in sold_codes:
+ # 已有自选就不重复加
+ if sc in wl_codes:
+ continue
+ # 从现有decisions看是否有关注价值
+ for d in decisions:
+ if d.get("code") == sc and d.get("entry_low") and d.get("entry_high"):
+ wl["stocks"].append({
+ "code": sc, "name": d.get("name", sc),
+ "entry_low": d.get("entry_low"), "entry_high": d.get("entry_high"),
+ "stop_loss": d.get("stop_loss", 0),
+ "analysis": {"action": d.get("action", ""), "tech_snapshot": d.get("tech_snapshot", "")}
+ })
+ added_to_wl.append(sc)
+ break
+ if added_to_wl and stdout:
+ print(f" 清仓→自选自动加入: {', '.join(added_to_wl)}")
+
+ # 重新计算 portfolio 汇总(保留已存在的 cash,用最新价格算市值)
+ try:
+ total_mv = 0.0
+ total_cost = 0.0
+ for h in existing_pf.get('holdings', []):
+ p = h.get('price') or 0
+ s = h.get('shares') or 0
+ c = h.get('cost') or 0
+ total_mv += p * s
+ total_cost += c * s
+ if p and s and total_mv > 0:
+ h['market_value'] = round(p * s, 2)
+ old_cash = existing_pf.get('cash') or 80476 # fallback 6/23 backup
+ frozen_cash = existing_pf.get('frozen_cash') or 0
+ existing_pf['cash'] = old_cash
+ existing_pf['total_mv'] = round(total_mv, 2)
+ existing_pf['total_assets'] = round(total_mv + old_cash + frozen_cash, 2)
+ existing_pf['total_pnl'] = round(total_mv - total_cost, 2)
+ existing_pf['position_pct'] = round(total_mv / (total_mv + old_cash + frozen_cash) * 100, 2) if (total_mv + old_cash + frozen_cash) > 0 else 0
+ except Exception as e:
+ print(f" [汇总计算失败] {e}", flush=True)
+
+ # DB 写入(替代 JSON dump — 强制币种约束)
+ try:
+ from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_watchlist_stock, write_holding_strategy
+ conn = get_conn()
+ write_holdings_batch(conn, existing_pf.get('holdings', []))
+ write_portfolio_summary(conn, existing_pf)
+ for s in wl.get('stocks', []):
+ s.setdefault('currency', 'CNY')
+ write_watchlist_stock(conn, s)
+ for d in decisions:
+ # ── 策略质量门禁 ──
+ code = d.get('code', '')
+ name = d.get('name', '')
+ enforce_strategy_quality(code, name, d)
+ write_holding_strategy(conn, code, name, d)
+ conn.close()
+ except Exception as e:
+ print(f" [DB写入失败] {e}", flush=True)
+
+ # 记录策略→提示词版本关联
+ if HAS_PROMPT_TRACKING:
+ try:
+ for d in decisions:
+ if d.get("code") and d.get("action"):
+ record_strategy_generation(
+ d["code"], d.get("name", ""), d.get("action", "")
+ )
+ except Exception as e:
+ if stdout:
+ print(f" ⚠️ 提示词版本追踪失败: {e}", file=sys.stderr)
+
+ # 刷新多周期缓存到磁盘
+ try:
+ import multi_timeframe as _mtf
+ _mtf.flush_mtf_cache()
+ except Exception:
+ pass
+
+ summary = {"total": total, "ok": ok, "errors": errors}
+ if stdout:
+ print(f"\n✅ 全量重评完成: {ok}/{total}成功, {errors}错误")
+ return summary
+
+
+if __name__ == "__main__":
+ regenerate_all()
diff --git a/system_health_check.py b/system_health_check.py
index 64ade3da..880f883d 100644
--- a/system_health_check.py
+++ b/system_health_check.py
@@ -90,13 +90,13 @@ def run():
try:
from mo_data import read_portfolio, read_decisions, read_watchlist
pf = read_portfolio()
- lines.append(check(len(pf.get("holdings", [])) > 0, f"portfolio.json DB记录: {len(pf.get('holdings', []))}条"))
+ lines.append(check(len(pf.get("holdings", [])) > 0, f"DB持仓记录: {len(pf.get('holdings', []))}条"))
ok_count += 1
wl = read_watchlist()
- lines.append(check(len(wl.get("stocks", [])) > 0, f"watchlist.json DB记录: {len(wl.get('stocks', []))}条"))
+ lines.append(check(len(wl.get("stocks", [])) > 0, f"DB自选股记录: {len(wl.get('stocks', []))}条"))
ok_count += 1
dec = read_decisions()
- lines.append(check(len(dec.get("decisions", [])) > 0, f"decisions.json DB记录: {len(dec.get('decisions', []))}条"))
+ lines.append(check(len(dec.get("decisions", [])) > 0, f"DB策略记录: {len(dec.get('decisions', []))}条"))
ok_count += 1
except Exception:
lines.append(check(False, "MoFin DB 数据读取失败"))
diff --git a/technical_analysis.py.bak.1783484685 b/technical_analysis.py.bak.1783484685
new file mode 100644
index 00000000..b5128578
--- /dev/null
+++ b/technical_analysis.py.bak.1783484685
@@ -0,0 +1,422 @@
+#!/usr/bin/env python3
+"""technical_analysis.py — 技术面分析模块 v2
+
+基于多日价格数据计算支撑位/压力位:
+1. 缓存每日 HLC 到 price_history.json
+2. 使用 5 日最高/最低计算枢轴点
+3. 结合振幅自动调整区间宽度
+
+使用方式:
+ from technical_analysis import full_analysis
+ result = full_analysis("603259") # 自动识别A股/港股
+"""
+
+import json
+import os
+import urllib.request
+from datetime import datetime, date
+
+# 腾讯API字段索引
+F = {
+ "name": 1, "code": 2, "price": 3, "close_yest": 4, "open": 5,
+ "volume": 6, "timestamp": 30, "change": 31, "change_pct": 32,
+ "high": 33, "low": 34, "amplitude": 43,
+ "turnover": 38, "pe": 39, "pb": 46,
+ "limit_up": 47, "limit_down": 48,
+ "avg_price": 51, "inner_vol": 52, "outer_vol": 53,
+}
+
+HISTORY_PATH = "/home/hmo/web-dashboard/data/price_history.json"
+HISTORY_DAYS = 60 # 使用最近 N 天的 HLC 数据
+
+
+def _load_history():
+ """读取价格历史缓存"""
+ try:
+ return json.load(open(HISTORY_PATH))
+ except (FileNotFoundError, json.JSONDecodeError):
+ return {}
+
+
+def _save_history(h):
+ json.dump(h, open(HISTORY_PATH, "w"), ensure_ascii=False, indent=2)
+
+
+def _market_prefix(code):
+ """根据代码确定腾讯API前缀"""
+ if code.startswith("sh") or code.startswith("sz") or code.startswith("hk"):
+ code = code[2:] if code[2:].isdigit() else code
+ raw = str(code).split("_")[0]
+ if len(raw) == 5 and raw.isdigit():
+ return "hk"
+ if raw.startswith("6") or raw.startswith("5"):
+ return "sh"
+ return "sz"
+
+
+def get_quote(code):
+ """获取行情数据。DB 优先(price_monitor 维护),腾讯 API fallback"""
+ # DB 优先
+ try:
+ from mofin_db import get_price_from_db
+ p, chg = get_price_from_db(code)
+ if p: return {"code": code, "price": p, "change_pct": chg or 0}
+ except: pass
+ # Fallback: 腾讯 API
+ import time
+ _cache = get_quote.__dict__.get("_cache", {})
+ now = time.time()
+ cached = _cache.get(code)
+ if cached and (now - cached["ts"]) < 60:
+ return cached["data"]
+
+ raw = str(code).split("_")[0]
+ prefix = _market_prefix(code)
+ url = f"http://qt.gtimg.cn/q={prefix}{raw}"
+ try:
+ r = urllib.request.urlopen(url, timeout=5)
+ fields = r.read().decode("gbk").split('"')[1].split("~")
+ except Exception as e:
+ return {"code": code, "error": str(e)}
+
+ def get(i):
+ try:
+ return float(fields[i]) if fields[i].strip() else None
+ except (IndexError, ValueError):
+ return None
+
+ today_str = date.today().isoformat()
+ q = {
+ "code": raw,
+ "market": prefix,
+ "name": fields[F["name"]] if len(fields) > F["name"] else code,
+ "price": get(3),
+ "close_yest": get(4),
+ "open": get(5),
+ "high": get(33),
+ "low": get(34),
+ "volume": get(6),
+ "amount": get(37),
+ "change": get(31),
+ "change_pct": get(32),
+ "amplitude": get(43),
+ "turnover_rate": get(38),
+ "pe": get(39),
+ "pb": get(46),
+ "limit_up": get(47),
+ "limit_down": get(48),
+ "avg_price": get(51),
+ "inner_vol": get(52),
+ "outer_vol": get(53),
+ "timestamp": fields[F["timestamp"]] if len(fields) > F["timestamp"] else "",
+ "_date": today_str,
+ }
+
+ # 写入价格历史缓存(每日一次)
+ h = get(33) # high
+ l = get(34) # low
+ c = get(3) # price / close
+ if h and l and c:
+ history = _load_history()
+ if raw not in history:
+ history[raw] = []
+ days = history[raw]
+ # 如果今天已有记录,更新(盘中数据更精确)
+ if days and len(days) > 0 and days[-1].get("date") == today_str:
+ days[-1]["high"] = max(days[-1]["high"], h)
+ days[-1]["low"] = min(days[-1]["low"], l)
+ days[-1]["close"] = c # 盘中用最新价,收盘后是收盘价
+ else:
+ days.append({"date": today_str, "high": h, "low": l, "close": c})
+ # 只保留最近 HISTORY_DAYS 天
+ history[raw] = days[-HISTORY_DAYS:]
+ _save_history(history)
+
+ # 写入60秒缓存
+ get_quote.__dict__["_cache"] = {**get_quote.__dict__.get("_cache", {}), code: {"ts": now, "data": q}}
+
+ return q
+
+
+def calc_support_resistance(q):
+ """计算技术支撑位和压力位 — 多日枢轴点算法
+
+ 使用多个数据源确定有效区间:
+ 1. 当日波幅(H-L)
+ 2. 最近 N 日的最高/最低(从 price_history.json 读取)
+ 3. 价格基数的百分比(对大市值低波动股票有效)
+ """
+ h = q.get("high")
+ l = q.get("low")
+ c = q.get("price")
+ yc = q.get("close_yest")
+ amplitude = q.get("amplitude") # 当日振幅%
+ code = q.get("code", "")
+
+ if not all([h, l, c]):
+ return {"error": "数据不足"}
+
+ # 多日最高/最低(从历史缓存读取)
+ history = _load_history()
+ hist_days = history.get(code, [])
+ multi_high = max(d["high"] for d in hist_days) if hist_days else h
+ multi_low = min(d["low"] for d in hist_days) if hist_days else l
+
+ # 有效区间 = max(当日波幅, 多日波幅, 价格×5%)
+ daily_range = h - l
+ multi_range = multi_high - multi_low
+ min_range = c * 0.05 # 5%价格基数
+
+ effective_range = max(daily_range, multi_range, min_range)
+
+ # 如果股价接近多日高点(>80%分位),说明在上升趋势中,扩大区间
+ trend_position = (c - multi_low) / (multi_high - multi_low) if multi_high > multi_low else 0.5
+ if trend_position > 0.8:
+ # 高位运行,扩大有效区间到价格的8%确保合理空间
+ effective_range = max(effective_range, c * 0.08)
+ elif trend_position < 0.2:
+ # 低位运行,同样扩大
+ effective_range = max(effective_range, c * 0.08)
+
+ # 如果振幅数据可用且振幅较小(<3%),进一步扩大区间确保有效性
+ if amplitude and amplitude > 0 and amplitude < 3:
+ # 低波动股票用 振幅×3 作为最小范围
+ amp_based = c * amplitude / 100 * 3
+ effective_range = max(effective_range, amp_based)
+
+ # 枢轴点 (Pivot Point)
+ pp = (h + l + c) / 3
+
+ # 支撑位
+ s1 = 2 * pp - h # 弱支撑
+ s2 = pp - effective_range # 强支撑
+
+ # 压力位
+ r1 = 2 * pp - l # 弱压力
+ r2 = pp + effective_range # 强压力
+
+ # 参考昨收调整
+ if yc:
+ if yc < s1:
+ s1 = yc
+ if yc > r1:
+ r1 = yc
+
+ # A股涨停/跌停价作为极端边界
+ limit_up = q.get("limit_up")
+ limit_down = q.get("limit_down")
+ market = q.get("market", "hk")
+ if market != "hk" and limit_up and limit_down:
+ # 注意:当现价逼近涨停/跌停时,limit不再是有效边界
+ # 用有效区间判断:如果自然计算的r2/s2在合理范围内不截断
+ natural_r2 = r2
+ natural_s2 = s2
+ # 涨停限制只对距离现价超过2%的强压位生效
+ if limit_up < r2 and (limit_up - c) / c < 0.02:
+ # 涨停价离现价<2%,说明可能封板,不截断
+ pass # 使用自然计算的r2
+ elif limit_up < r2:
+ r2 = limit_up
+ if limit_down > s2 and (c - limit_down) / c < 0.02:
+ pass # 接近跌停,不截断
+ elif limit_down > s2:
+ s2 = limit_down
+
+ return {
+ "strong_support": round(s2, 2),
+ "weak_support": round(s1, 2),
+ "pivot": round(pp, 2),
+ "weak_resist": round(r1, 2),
+ "strong_resist": round(r2, 2),
+ "today_high": h,
+ "today_low": l,
+ "multi_high": multi_high,
+ "multi_low": multi_low,
+ "effective_range": round(effective_range, 2),
+ }
+
+
+def analyze_candlestick(q):
+ """判断K线形态"""
+ o = q.get("open")
+ c = q.get("price")
+ h = q.get("high")
+ l = q.get("low")
+ yc = q.get("close_yest")
+
+ if not all([o, c, h, l]):
+ return {"pattern": "unknown", "sentiment": "neutral"}
+
+ if c >= o:
+ body = c - o
+ upper = h - c
+ lower = o - l
+ is_green = True
+ else:
+ body = o - c
+ upper = h - o
+ lower = c - l
+ is_green = False
+
+ total_range = h - l
+ if total_range == 0:
+ return {"pattern": "平盘", "sentiment": "neutral"}
+
+ body_pct = body / total_range * 100
+ upper_pct = upper / total_range * 100
+ lower_pct = lower / total_range * 100
+
+ if body_pct < 5:
+ if upper_pct > 60:
+ pattern = "倒T线/射击之星"
+ sentiment = "bearish"
+ elif lower_pct > 60:
+ pattern = "锤子线/T字线"
+ sentiment = "bullish"
+ else:
+ pattern = "十字星"
+ sentiment = "neutral"
+ elif body_pct < 30:
+ if upper_pct > 40 and lower_pct > 40:
+ pattern = "长影星线"
+ sentiment = "neutral"
+ elif upper_pct > 40:
+ pattern = "倒T线/射击之星"
+ sentiment = "bearish" if is_green else "bearish"
+ elif lower_pct > 40:
+ pattern = "锤子线/T字线"
+ sentiment = "bullish" if is_green else "bullish"
+ else:
+ pattern = "小阳线" if is_green else "小阴线"
+ sentiment = "bullish" if is_green else "bearish"
+ else:
+ if upper_pct > 30:
+ pattern = "带上影阳线" if is_green else "带上影阴线"
+ sentiment = "neutral" if is_green else "bearish"
+ elif lower_pct > 30:
+ pattern = "带下影阳线" if is_green else "带下影阴线"
+ sentiment = "bullish" if is_green else "neutral"
+ else:
+ pattern = "光头光脚阳线" if is_green else "光头光脚阴线"
+ sentiment = "bullish" if is_green else "bearish"
+
+ gap_up = ""
+ gap_down = ""
+ if yc:
+ if o > yc * 1.01:
+ gap_up = "跳空高开"
+ if not is_green:
+ sentiment = "neutral"
+ elif o < yc * 0.99:
+ gap_down = "跳空低开"
+ if is_green:
+ sentiment = "neutral"
+
+ return {
+ "pattern": pattern,
+ "sentiment": sentiment,
+ "body_pct": round(body_pct, 1),
+ "upper_shadow_pct": round(upper_pct, 1),
+ "lower_shadow_pct": round(lower_pct, 1),
+ "is_green": is_green,
+ "gap": gap_up or gap_down or "无跳空",
+ }
+
+
+def analyze_volume(q):
+ """量价分析"""
+ outer = q.get("outer_vol")
+ inner = q.get("inner_vol")
+ turnover = q.get("turnover_rate")
+
+ result = {}
+ if outer and inner and (outer + inner) > 0:
+ ratio = outer / (outer + inner)
+ result["buy_sell_ratio"] = round(ratio, 2)
+ if ratio > 0.55:
+ result["volume_signal"] = "主动买盘占优"
+ elif ratio < 0.45:
+ result["volume_signal"] = "主动卖盘占优"
+ else:
+ result["volume_signal"] = "买卖均衡"
+ else:
+ result["volume_signal"] = "数据不足"
+
+ if turnover:
+ result["turnover_rate"] = turnover
+
+ return result
+
+
+def full_analysis(code):
+ """完整技术分析(带30秒缓存,避免分钟级波动)"""
+ import time
+ _cache = full_analysis.__dict__.get("_cache", {})
+ now = time.time()
+ cached = _cache.get(code)
+ if cached and (now - cached["ts"]) < 30:
+ return cached["data"]
+
+ q = get_quote(code)
+ if not q or "error" in q:
+ return q
+
+ sr = calc_support_resistance(q)
+ candle = analyze_candlestick(q)
+ vol = analyze_volume(q)
+
+ # 多周期+均线分析(整合 multi_timeframe)
+ mtf = {}
+ try:
+ from multi_timeframe import full_multi_tf_analysis as _mtf
+ mtf_raw = _mtf(code)
+ if mtf_raw and 'daily' in mtf_raw:
+ d = mtf_raw['daily']
+ mtf = {
+ 'mas': d.get('mas', {}),
+ 'multi_tf_sr': d.get('support_resistance', {}),
+ 'trend': d.get('trend', {}),
+ }
+ # 周线弱压/弱撑作为中周期参考
+ if 'weekly' in mtf_raw:
+ w = mtf_raw['weekly']
+ ws = w.get('support_resistance', {})
+ mtf['weekly_sr'] = {
+ 'weak_resist': ws.get('weak_resist'),
+ 'weak_support': ws.get('weak_support'),
+ }
+ except Exception:
+ pass # non-critical, graceful degradation
+
+ result = {
+ "quote": {
+ "name": q.get("name", code),
+ "price": q["price"],
+ "change_pct": q.get("change_pct", 0),
+ "open": q.get("open", 0),
+ "high": q.get("high", 0),
+ "low": q.get("low", 0),
+ "close_yest": q.get("close_yest", 0),
+ "volume": q.get("volume", 0),
+ "amplitude": q.get("amplitude", 0),
+ },
+ "support_resistance": sr,
+ "candlestick": candle,
+ "volume": vol,
+ "multi_tf": mtf,
+ "analyzed_at": datetime.now().strftime("%H:%M"),
+ }
+
+ # 写入缓存
+ _cache[code] = {"ts": now, "data": result}
+ full_analysis.__dict__["_cache"] = _cache
+ return result
+
+
+if __name__ == "__main__":
+ import sys
+ codes = sys.argv[1:] or ["603259", "002594", "00700"]
+ for c in codes:
+ r = full_analysis(c)
+ print(json.dumps(r, ensure_ascii=False, indent=2))
+ print()