diff --git a/__pycache__/multi_timeframe.cpython-312.pyc b/__pycache__/multi_timeframe.cpython-312.pyc index 6b98303..82ad25e 100644 Binary files a/__pycache__/multi_timeframe.cpython-312.pyc and b/__pycache__/multi_timeframe.cpython-312.pyc differ diff --git a/__pycache__/strategy_lifecycle.cpython-312.pyc b/__pycache__/strategy_lifecycle.cpython-312.pyc index e5ca9c8..d15b460 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 7c02ef5..1b26bd1 100644 Binary files a/__pycache__/technical_analysis.cpython-312.pyc and b/__pycache__/technical_analysis.cpython-312.pyc differ diff --git a/data/decisions.json b/data/decisions.json index 4edb796..cf79046 100644 --- a/data/decisions.json +++ b/data/decisions.json @@ -1,1247 +1,893 @@ { "decisions": [ - { - "code": "000657", - "name": "中钨高新", - "price": 91.88, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | 目标105.72 | 止损87.66 | 买入区90.04~93.72", - "stop_loss": 87.66, - "entry_low": 90.04, - "entry_high": 93.72, - "tech_snapshot": "形态:带下影阴线/neutral 量价:主动买盘占优 强撑:87.33 弱撑:87.66 弱压:97.03 强压:105.72 | MA5=96.75 MA10=99.88 MA20=89.04 MA60=68.13", - "timing_signal": "信号不充分", - "rr_ratio": 3.28, - "status": "updated", - "note": "", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "自选策略", - "source": "auto", - "sector_context": "", - "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 87.66, - "entry_zone": "90.04~93.72", - "take_profit_zone": "0~105.72" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 105.72, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利持有 | 目标105.72 | 止损87.66 | 买入区90.04~93.72", - "reason": "初始策略创建 | 形态:带下影阴线/neutral 量价:主动买盘占优 强撑:87.33 弱撑:87.66 弱压:97.03 强压:105", - "trigger": "初始创建" - } - ] - }, { "code": "000700", "name": "模塑科技", - "price": 17.37, + "version": 1, + "price": 17.22, "cost": 14.83, - "shares": 1400, - "avg_price": 0, + "shares": 0, + "stop_loss": 16.7, + "take_profit": 18.6, + "entry_low": 15.5, + "entry_high": 18.08, "currency": "CNY", - "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损16.85 | 目标0 | 买入区15.63~18.24 | 信号:持有", - "stop_loss": 16.85, - "entry_low": 15.63, - "entry_high": 18.24, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=14.99 MA10=14.9 MA20=15.45 MA60=13.81", + "strategy_type": "自选策略", + "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损16.7 | 目标0 | 买入区15.5~18.08 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业汽车零部件大涨+4.0%,可适度积极", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=14.99 MA10=14.9 MA20=15.45 MA60=13.81", "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 16.85, - "entry_zone": "15.63~18.24" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损16.85 | 目标0 | 买入区15.63~18.24 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] - }, - { - "code": "000711", - "name": "ST京蓝", - "price": 5.26, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标5.63 | 止损5.01 | 买入区5.15~5.26 | 信号:观望", - "stop_loss": 5.01, - "entry_low": 5.15, - "entry_high": 5.26, - "tech_snapshot": "形态:带下影阳线/bullish 量价:主动卖盘占优 强撑:4.79 弱撑:5.01 弱压:5.31 强压:5.63 | MA5=4.64 MA10=4.87 MA20=5.25 MA60=5.15", - "timing_signal": "观望", - "rr_ratio": 1.48, - "status": "review", - "note": "⚠️盈亏比不足1:1.5,不建议买入", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "自选策略", + "sector_context": "行业汽车零部件大涨+350.0%,可适度积极", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "行业房地产+0.7%,中性", - "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 5.01, - "entry_zone": "5.15~5.26", - "take_profit_zone": "0~5.63" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 5.63, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标5.63 | 止损5.01 | 买入区5.15~5.26 | 信号:观望", - "reason": "初始策略创建 | 形态:带下影阳线/bullish 量价:主动卖盘占优 强撑:4.79 弱撑:5.01 弱压:5.31 强压:5.63 |", - "trigger": "初始创建" - } - ] - }, - { - "code": "001309", - "name": "德明利", - "price": 874.23, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标892.1 | 止损811.0 | 买入区856.75~843.44 | 信号:买入", - "stop_loss": 811.0, - "entry_low": 856.75, - "entry_high": 843.44, - "tech_snapshot": "形态:光头光脚阳线/bullish 量价:主动买盘占优 强撑:763.08 弱撑:811.0 弱压:906.15 强压:892.1 | MA5=913.62 MA10=847.32 MA20=744.16 MA60=628.45", - "timing_signal": "信号不充分", - "rr_ratio": 0.28, - "status": "review", - "note": "⚠️盈亏比不足1:1.5,不建议买入", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "自选策略", - "source": "auto", - "sector_context": "行业半导体-0.9%,中性", - "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 811.0, - "entry_zone": "856.75~843.44", - "take_profit_zone": "0~892.1" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 892.1, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标892.1 | 止损811.0 | 买入区856.75~843.44 | 信号:买入", - "reason": "初始策略创建 | 形态:光头光脚阳线/bullish 量价:主动买盘占优 强撑:763.08 弱撑:811.0 弱压:906.15 强压:", - "trigger": "初始创建" - } - ] - }, - { - "code": "002594", - "name": "比亚迪", - "price": 86.56, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | ⚠️盈亏比偏低(1:1.8),谨慎买入 | 目标91.93 | 止损83.96 | 买入区84.83~86.91 | 信号:观望", - "stop_loss": 83.96, - "entry_low": 84.83, - "entry_high": 86.91, - "tech_snapshot": "形态:光头光脚阳线/bullish 量价:主动卖盘占优 强撑:78.89 弱撑:83.57 弱压:88.03 强压:91.93 | MA5=98.63 MA10=97.78 MA20=96.0 MA60=96.51", - "timing_signal": "观望", - "rr_ratio": 1.8, - "status": "updated", - "note": "⚠️盈亏比偏低(1:1.8),谨慎买入", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "自选策略", - "source": "auto", - "sector_context": "行业汽车整车上涨+1.6%,正常", - "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 83.96, - "entry_zone": "84.83~86.91", - "take_profit_zone": "0~91.93" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 91.93, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利持有 | ⚠️盈亏比偏低(1:1.8),谨慎买入 | 目标91.93 | 止损83.96 | 买入区84.83~86.91 | 信号:观望", - "reason": "初始策略创建 | 形态:光头光脚阳线/bullish 量价:主动卖盘占优 强撑:78.89 弱撑:83.57 弱压:88.03 强压:91", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:16:41", + "updated_at": "2026-07-06 11:07:37", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:41", + "type": "自选策略" }, { "code": "00700", "name": "腾讯", - "price": 437.4, - "cost": 0, + "version": 1, + "price": 497.35, + "cost": 443.13, "shares": 100, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 损419.9 | 盈0 | 买428.65~446.15", - "stop_loss": 424.28, - "entry_low": 428.65, - "entry_high": 446.15, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=624.1 MA10=623.7 MA20=615.5 MA60=559.52", + "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, - "status": "review", - "note": "⚠️盈亏比不足1:1.5,不建议买入", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业软件开发+0.4%,中性", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=624.1 MA10=623.7 MA20=615.5 MA60=559.52", "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 424.28, - "entry_zone": "428.65~446.15" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 损419.9 | 盈0 | 买428.65~446.15", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "行业软件开发大涨+21.0%,可适度积极", + "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", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:48", + "type": "holding" }, { "code": "00981", "name": "中芯国际", - "price": 79.8, - "cost": 65.84, - "shares": 500, - "avg_price": 0, - "currency": "CNY", - "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损77.41 | 目标0 | 买入区71.82~83.79 | 信号:持有", - "stop_loss": 77.41, - "entry_low": 71.82, - "entry_high": 83.79, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=84.12 MA10=81.44 MA20=78.5 MA60=71.93", + "version": 1, + "price": 89.5, + "cost": 75.94, + "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, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业半导体-0.9%,中性", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=84.12 MA10=81.44 MA20=78.5 MA60=71.93", "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 77.41, - "entry_zone": "71.82~83.79" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损77.41 | 目标0 | 买入区71.82~83.79 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "半导体", + "status": "active", + "trigger_json": null, + "changelog_json": null, + "source": "auto", + "reason": null, + "created_at": "2026-07-03 11:16:48", + "updated_at": "2026-07-06 11:01:50", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:48", + "type": "自选策略" }, { "code": "01088", "name": "中国神华", - "price": 40.22, - "cost": 39.79, + "version": 1, + "price": 46.14, + "cost": 45.89, "shares": 500, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | ⚠️盈亏比极低,关注 | 止损37.51 | 目标0 | 买入区36.2~42.23 | 信号:持有", - "stop_loss": 37.51, - "entry_low": 36.2, - "entry_high": 42.23, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=39.82 MA10=40.55 MA20=41.11 MA60=43.93", + "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, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业煤炭开采加工+1.0%,中性", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=39.82 MA10=40.55 MA20=41.11 MA60=43.93", "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 37.51, - "entry_zone": "36.2~42.23" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利持有 | ⚠️盈亏比极低,关注 | 止损37.51 | 目标0 | 买入区36.2~42.23 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "行业煤炭大涨+75.0%,可适度积极", + "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", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:48", + "type": "holding" }, { "code": "01211", "name": "比亚迪股份", - "price": 82.6, - "cost": 90.92, + "version": 1, + "price": 97.0, + "cost": 104.87, "shares": 600, - "avg_price": 0, - "currency": "CNY", - "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损78.59 | 目标0 | 买入区74.34~86.73 | 信号:持有", - "stop_loss": 78.59, - "entry_low": 74.34, - "entry_high": 86.73, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=96.74 MA10=96.79 MA20=98.23 MA60=98.39", + "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, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=96.74 MA10=96.79 MA20=98.23 MA60=98.39", + "stock_category": "深套", + "sector_context": "汽车整车", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "行业汽车整车上涨+1.6%,正常", - "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 78.59, - "entry_zone": "74.34~86.73" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "持有观察 | ⚠️盈亏比极低,关注 | 止损78.59 | 目标0 | 买入区74.34~86.73 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:16:48", + "updated_at": "2026-07-03 11:16:48", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:48", + "type": "holding" }, { "code": "01478", "name": "丘钛科技", - "price": 7.05, - "cost": 11.68, + "version": 1, + "price": 6.73, + "cost": 13.47, "shares": 11000, - "avg_price": 0, - "currency": "CNY", - "action": "深套持有 | 深套持有 | 止损6.48 | 目标0 | 买入区6.34~7.4 | 信号:持有", - "stop_loss": 6.48, - "entry_low": 6.34, - "entry_high": 7.4, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=9.11 MA10=9.29 MA20=9.5 MA60=8.86", + "stop_loss": 6.33, + "take_profit": 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, - "status": "updated", - "note": "深套持有", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业消费电子上涨+2.5%,正常", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=9.18 MA10=9.4 MA20=9.53 MA60=8.85", "stock_category": "深套", - "position_advice": "不补不割", - "time_horizon": "长期", - "trigger": { - "stop_loss": 6.48, - "entry_zone": "6.34~7.4" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "深套持有 | 深套持有 | 止损6.48 | 目标0 | 买入区6.34~7.4 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "消费电子", + "status": "updated", + "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", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:48", + "type": "holding", + "last_reassessed_price": 6.73, + "strategy_tree": { + "branches": [ + { + "id": "01478_stop_loss", + "condition": { + "price": "<6.33" + }, + "action": { + "type": "sell", + "amount": "all", + "reason": "止损" + }, + "priority": 0, + "rationale": "止损保护本金", + "trigger_count": 0, + "success_rate": null, + "last_triggered": null + }, + { + "id": "01478_buy_dip", + "condition": { + "scenario": "weak_consolidation", + "price": "<=7.07", + "price_lower": ">=6.06" + }, + "action": { + "type": "buy", + "amount": "normal", + "limit": 6.06, + "reason": "回调支撑买入" + }, + "priority": 1, + "rationale": "价格回调到支撑区,弱势市场低吸", + "trigger_count": 0, + "success_rate": null, + "last_triggered": null + }, + { + "id": "01478_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": "01478_hold", + "condition": {}, + "action": { + "type": "hold", + "reason": "无明确信号,继续持有" + }, + "priority": 99, + "rationale": "没有分支匹配时的默认动作", + "trigger_count": 0, + "success_rate": null, + "last_triggered": null + } + ] + } }, { "code": "01888", "name": "建滔积层板", - "price": 85.6, - "cost": 76.5, - "shares": 500, - "avg_price": 0, - "currency": "CNY", - "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损83.03 | 目标0 | 买入区77.04~89.88 | 信号:持有", - "stop_loss": 83.03, - "entry_low": 77.04, - "entry_high": 89.88, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=96.4 MA10=93.52 MA20=78.64 MA60=51.2", + "version": 1, + "price": 73.3, + "cost": 88.24, + "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, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业元件上涨+2.0%,正常", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=96.4 MA10=93.52 MA20=78.64 MA60=51.2", "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 83.03, - "entry_zone": "77.04~89.88" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损83.03 | 目标0 | 买入区77.04~89.88 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "行业元件大跌-4.0%,收紧止损", + "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", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:49", + "type": "自选策略" }, { "code": "02202", "name": "万科企业", - "price": 2.32, - "cost": 4.05, + "version": 1, + "price": 2.7, + "cost": 4.67, "shares": 19700, - "avg_price": 0, - "currency": "CNY", - "action": "深套持有 | 深套持有 | 止损2.09 | 目标0 | 买入区2.09~2.44 | 信号:持有", - "stop_loss": 2.09, - "entry_low": 2.09, - "entry_high": 2.44, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=3.41 MA10=3.42 MA20=3.53 MA60=3.51", + "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, - "status": "updated", - "note": "深套持有", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业房地产+0.7%,中性", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=3.41 MA10=3.42 MA20=3.53 MA60=3.51", "stock_category": "深套", - "position_advice": "不补不割", - "time_horizon": "长期", - "trigger": { - "stop_loss": 2.09, - "entry_zone": "2.09~2.44" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "深套持有 | 深套持有 | 止损2.09 | 目标0 | 买入区2.09~2.44 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "行业房地产开发大涨+71.0%,可适度积极", + "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", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:49", + "type": "holding" }, { "code": "06869", "name": "长飞光纤光缆", - "price": 205.4, - "cost": 228.65, - "shares": 500, - "avg_price": 0, - "currency": "CNY", - "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损195.03 | 目标0 | 买入区184.86~215.67 | 信号:持有", - "stop_loss": 195.03, - "entry_low": 184.86, - "entry_high": 215.67, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=246.26 MA10=248.81 MA20=244.56 MA60=231.58", + "version": 1, + "price": 232.06, + "cost": 263.73, + "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, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", + "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%,可适度积极", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "行业通信设备上涨+1.6%,正常", - "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 195.03, - "entry_zone": "184.86~215.67" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "持有观察 | ⚠️盈亏比极低,关注 | 止损195.03 | 目标0 | 买入区184.86~215.67 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:16:49", + "updated_at": "2026-07-06 11:01:50", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:49", + "type": "自选策略" }, { "code": "300035", "name": "中科电气", - "price": 14.3, + "version": 1, + "price": 14.31, "cost": 22.29, "shares": 1400, - "avg_price": 0, + "stop_loss": 13.53, + "take_profit": 15.45, + "entry_low": 12.88, + "entry_high": 15.03, "currency": "CNY", - "action": "深套持有 | 深套持有 | 止损12.89 | 目标0 | 买入区12.87~15.02 | 信号:持有", - "stop_loss": 12.89, - "entry_low": 12.87, - "entry_high": 15.02, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=21.7 MA10=21.88 MA20=22.15 MA60=21.44", + "strategy_type": "holding", + "action": "深套持有 | 深套持有 | 止损13.53 | 目标0 | 买入区12.88~15.03 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "深套持有", - "timestamp": "2026-07-03 12:06", - "updated_at": "2026-07-03 12:06", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业电池+0.4%,中性", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=21.7 MA10=21.88 MA20=22.15 MA60=21.44", "stock_category": "深套", - "position_advice": "不补不割", - "time_horizon": "长期", - "trigger": { - "stop_loss": 12.89, - "entry_zone": "12.87~15.02" - }, - "created_at": "2026-07-03 12:06", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:06", - "old_action": "", - "new_action": "深套持有 | 深套持有 | 止损12.89 | 目标0 | 买入区12.87~15.02 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] - }, - { - "code": "300124", - "name": "汇川技术", - "price": 72.29, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标76.47 | 止损70.12 | 买入区70.84~71.63 | 信号:观望", - "stop_loss": 70.12, - "entry_low": 70.84, - "entry_high": 71.63, - "tech_snapshot": "形态:光头光脚阳线/neutral 量价:主动卖盘占优 强撑:64.91 弱撑:68.4 弱压:73.98 强压:76.47 | MA5=76.83 MA10=77.47 MA20=78.08 MA60=73.46", - "timing_signal": "观望", - "rr_ratio": 1.07, - "status": "review", - "note": "⚠️盈亏比不足1:1.5,不建议买入", - "timestamp": "2026-07-03 12:07", - "updated_at": "2026-07-03 12:07", - "type": "自选策略", + "sector_context": "行业能源金属大涨+3.0%,可适度积极", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "行业自动化设备大涨+4.0%,可适度积极", - "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 70.12, - "entry_zone": "70.84~71.63", - "take_profit_zone": "0~76.47" - }, - "created_at": "2026-07-03 12:07", - "take_profit": 76.47, - "changelog": [ - { - "date": "2026-07-03 12:07", - "old_action": "", - "new_action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标76.47 | 止损70.12 | 买入区70.84~71.63 | 信号:观望", - "reason": "初始策略创建 | 形态:光头光脚阳线/neutral 量价:主动卖盘占优 强撑:64.91 弱撑:68.4 弱压:73.98 强压:76.", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:16:50", + "updated_at": "2026-07-03 11:16:50", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:50", + "type": "holding" }, { "code": "300308", "name": "中际旭创", - "price": 1157.97, + "version": 1, + "price": 1168.95, "cost": 1316.53, "shares": 100, - "avg_price": 0, + "stop_loss": 1090.4, + "take_profit": 1262.47, + "entry_low": 1052.06, + "entry_high": 1227.4, "currency": "CNY", - "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损1080.15 | 目标0 | 买入区1042.17~1215.87 | 信号:持有", - "stop_loss": 1080.15, - "entry_low": 1042.17, - "entry_high": 1215.87, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=1222.01 MA10=1280.59 MA20=1239.5 MA60=1033.3", + "strategy_type": "holding", + "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损1090.4 | 目标0 | 买入区1052.06~1227.4 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:07", - "updated_at": "2026-07-03 12:07", - "type": "持仓策略", - "source": "auto", - "sector_context": "", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=1222.01 MA10=1280.59 MA20=1239.5 MA60=1033.3", "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 1080.15, - "entry_zone": "1042.17~1215.87" - }, - "created_at": "2026-07-03 12:07", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:07", - "old_action": "", - "new_action": "持有观察 | ⚠️盈亏比极低,关注 | 止损1080.15 | 目标0 | 买入区1042.17~1215.87 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "中际旭创所属行业(待补充)", + "status": "active", + "trigger_json": null, + "changelog_json": null, + "source": "auto", + "reason": null, + "created_at": "2026-07-03 11:16:55", + "updated_at": "2026-07-03 11:16:55", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:55", + "type": "holding" }, { "code": "300548", "name": "长芯博创", - "price": 227.0, + "version": 1, + "price": 228.23, "cost": 231.46, - "shares": 100, - "avg_price": 0, + "shares": 0, + "stop_loss": 221.38, + "take_profit": 246.49, + "entry_low": 205.41, + "entry_high": 239.64, "currency": "CNY", - "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损203.28 | 目标0 | 买入区204.3~238.35 | 信号:持有", - "stop_loss": 203.28, - "entry_low": 204.3, - "entry_high": 238.35, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=253.97 MA10=270.01 MA20=255.06 MA60=248.13", + "strategy_type": "自选策略", + "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损221.38 | 目标0 | 买入区205.41~239.64 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:07", - "updated_at": "2026-07-03 12:07", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业光学光电子下跌-1.0%,适度防御", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=253.97 MA10=270.01 MA20=255.06 MA60=248.13", "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 203.28, - "entry_zone": "204.3~238.35" - }, - "created_at": "2026-07-03 12:07", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:07", - "old_action": "", - "new_action": "持有观察 | ⚠️盈亏比极低,关注 | 止损203.28 | 目标0 | 买入区204.3~238.35 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "行业通信设备大涨+100.0%,可适度积极", + "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", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:56", + "type": "自选策略" }, { "code": "300750", "name": "宁德时代", - "price": 384.16, + "version": 1, + "price": 384.06, "cost": 401.78, "shares": 100, - "avg_price": 0, + "stop_loss": 358.25, + "take_profit": 414.78, + "entry_low": 345.65, + "entry_high": 403.26, "currency": "CNY", - "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损358.34 | 目标0 | 买入区345.74~403.37 | 信号:持有", - "stop_loss": 358.34, - "entry_low": 345.74, - "entry_high": 403.37, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=386.51 MA10=392.29 MA20=394.64 MA60=413.88", + "strategy_type": "holding", + "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损358.25 | 目标0 | 买入区345.65~403.26 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:07", - "updated_at": "2026-07-03 12:07", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业电池+0.4%,中性", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=386.51 MA10=392.29 MA20=394.64 MA60=413.88", "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 358.34, - "entry_zone": "345.74~403.37" - }, - "created_at": "2026-07-03 12:07", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:07", - "old_action": "", - "new_action": "持有观察 | ⚠️盈亏比极低,关注 | 止损358.34 | 目标0 | 买入区345.74~403.37 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] - }, - { - "code": "301308", - "name": "江波龙", - "price": 620.0, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标666.01 | 止损585.55 | 买入区607.6~617.73", - "stop_loss": 585.55, - "entry_low": 607.6, - "entry_high": 617.73, - "tech_snapshot": "形态:光头光脚阳线/neutral 量价:主动买盘占优 强撑:551.09 弱撑:585.55 弱压:643.01 强压:666.01 | MA5=674.71 MA10=646.08 MA20=586.34 MA60=501.02", - "timing_signal": "信号不充分", - "rr_ratio": 1.34, - "status": "review", - "note": "⚠️盈亏比不足1:1.5,不建议买入", - "timestamp": "2026-07-03 12:07", - "updated_at": "2026-07-03 12:07", - "type": "自选策略", + "sector_context": "电池", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "", - "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 585.55, - "entry_zone": "607.6~617.73", - "take_profit_zone": "0~666.01" - }, - "created_at": "2026-07-03 12:07", - "take_profit": 666.01, - "changelog": [ - { - "date": "2026-07-03 12:07", - "old_action": "", - "new_action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标666.01 | 止损585.55 | 买入区607.6~617.73", - "reason": "初始策略创建 | 形态:光头光脚阳线/neutral 量价:主动买盘占优 强撑:551.09 弱撑:585.55 弱压:643.01 强压", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:16:56", + "updated_at": "2026-07-03 11:16:56", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:16:56", + "type": "holding" }, { "code": "518880", "name": "黄金ETF华安", - "price": 8.686, + "version": 1, + "price": 8.688, "cost": 12.19, "shares": 2400, - "avg_price": 0, - "currency": "CNY", - "action": "深套持有 | 深套持有 | 止损8.21 | 目标0 | 买入区7.82~9.12 | 信号:持有", "stop_loss": 8.21, + "take_profit": 9.38, "entry_low": 7.82, "entry_high": 9.12, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=9.45 MA10=9.58 MA20=10.01 MA60=10.34", + "currency": "CNY", + "strategy_type": "holding", + "action": "深套持有 | 深套持有 | 止损8.21 | 目标0 | 买入区7.82~9.12 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "深套持有", - "timestamp": "2026-07-03 12:07", - "updated_at": "2026-07-03 12:07", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业贵金属大涨+6.9%,可适度积极", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=9.45 MA10=9.58 MA20=10.01 MA60=10.34", "stock_category": "深套", - "position_advice": "不补不割", - "time_horizon": "长期", - "trigger": { - "stop_loss": 8.21, - "entry_zone": "7.82~9.12" - }, - "created_at": "2026-07-03 12:07", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:07", - "old_action": "", - "new_action": "深套持有 | 深套持有 | 止损8.21 | 目标0 | 买入区7.82~9.12 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] - }, - { - "code": "600519", - "name": "贵州茅台", - "price": 1191.31, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | 目标1292.62 | 止损1155.57 | 买入区1167.48~1210.39 | 信号:关注", - "stop_loss": 1155.57, - "entry_low": 1167.48, - "entry_high": 1210.39, - "tech_snapshot": "形态:光头光脚阴线/bearish 量价:主动买盘占优 强撑:1102.01 弱撑:1184.49 弱压:1204.13 强压:1292.62 | MA5=1380.83 MA10=1382.77 MA20=1355.23 MA60=1398.7", - "timing_signal": "关注", - "rr_ratio": 2.83, - "status": "updated", - "note": "", - "timestamp": "2026-07-03 12:08", - "updated_at": "2026-07-03 12:08", - "type": "自选策略", + "sector_context": "行业贵金属大涨+723.0%,可适度积极", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "", - "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 1155.57, - "entry_zone": "1167.48~1210.39", - "take_profit_zone": "0~1292.62" - }, - "created_at": "2026-07-03 12:08", - "take_profit": 1292.62, - "changelog": [ - { - "date": "2026-07-03 12:08", - "old_action": "", - "new_action": "盈利持有 | 目标1292.62 | 止损1155.57 | 买入区1167.48~1210.39 | 信号:关注", - "reason": "初始策略创建 | 形态:光头光脚阴线/bearish 量价:主动买盘占优 强撑:1102.01 弱撑:1184.49 弱压:1204.13", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:17:01", + "updated_at": "2026-07-03 11:17:01", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:17:01", + "type": "holding" }, { "code": "600563", "name": "法拉电子", - "price": 161.71, + "version": 1, + "price": 162.41, "cost": 147.18, "shares": 100, - "avg_price": 0, + "stop_loss": 144.94, + "take_profit": 175.4, + "entry_low": 146.17, + "entry_high": 170.53, "currency": "CNY", - "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损156.86 | 目标0 | 买入区145.54~169.8 | 信号:持有", - "stop_loss": 156.86, - "entry_low": 145.54, - "entry_high": 169.8, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=183.16 MA10=178.82 MA20=169.12 MA60=145.24", + "strategy_type": "holding", + "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损144.94 | 目标0 | 买入区146.17~170.53 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:08", - "updated_at": "2026-07-03 12:08", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业元件上涨+2.0%,正常", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=183.16 MA10=178.82 MA20=169.12 MA60=145.24", "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 156.86, - "entry_zone": "145.54~169.8" - }, - "created_at": "2026-07-03 12:08", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:08", - "old_action": "", - "new_action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损156.86 | 目标0 | 买入区145.54~169.8 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] - }, - { - "code": "601318", - "name": "中国平安", - "price": 48.7, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | 目标52.92 | 止损47.24 | 买入区47.73~49.51 | 信号:观望", - "stop_loss": 47.24, - "entry_low": 47.73, - "entry_high": 49.51, - "tech_snapshot": "形态:光头光脚阴线/neutral 量价:主动卖盘占优 强撑:45.13 弱撑:48.27 弱压:49.45 强压:52.92 | MA5=69.72 MA10=68.2 MA20=65.99 MA60=62.55", - "timing_signal": "观望", - "rr_ratio": 2.89, - "status": "updated", - "note": "", - "timestamp": "2026-07-03 12:08", - "updated_at": "2026-07-03 12:08", - "type": "自选策略", + "sector_context": "行业元件大跌-4.0%,收紧止损", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "", - "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 47.24, - "entry_zone": "47.73~49.51", - "take_profit_zone": "0~52.92" - }, - "created_at": "2026-07-03 12:08", - "take_profit": 52.92, - "changelog": [ - { - "date": "2026-07-03 12:08", - "old_action": "", - "new_action": "盈利持有 | 目标52.92 | 止损47.24 | 买入区47.73~49.51 | 信号:观望", - "reason": "初始策略创建 | 形态:光头光脚阴线/neutral 量价:主动卖盘占优 强撑:45.13 弱撑:48.27 弱压:49.45 强压:52", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:17:08", + "updated_at": "2026-07-03 11:17:08", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:17:08", + "type": "holding" }, { "code": "601899", "name": "紫金矿业", + "version": 1, "price": 28.04, "cost": 39.89, "shares": 2400, - "avg_price": 0, - "currency": "CNY", - "action": "深套持有 | 深套持有 | 止损25.77 | 目标0 | 买入区25.24~29.44 | 信号:持有", - "stop_loss": 25.77, + "stop_loss": 25.26, + "take_profit": 30.28, "entry_low": 25.24, "entry_high": 29.44, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=35.14 MA10=36.13 MA20=37.18 MA60=36.52", + "currency": "CNY", + "strategy_type": "holding", + "action": "深套持有 | 深套持有 | 止损25.26 | 目标0 | 买入区25.24~29.44 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "深套持有", - "timestamp": "2026-07-03 12:08", - "updated_at": "2026-07-03 12:08", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业工业金属上涨+2.2%,正常", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=35.14 MA10=36.13 MA20=37.18 MA60=36.52", "stock_category": "深套", - "position_advice": "不补不割", - "time_horizon": "长期", - "trigger": { - "stop_loss": 25.77, - "entry_zone": "25.24~29.44" - }, - "created_at": "2026-07-03 12:08", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:08", - "old_action": "", - "new_action": "深套持有 | 深套持有 | 止损25.77 | 目标0 | 买入区25.24~29.44 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "工业金属", + "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", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:17:14", + "type": "holding" }, { "code": "688411", "name": "海博思创", - "price": 257.52, + "version": 1, + "price": 257.02, "cost": 266.95, "shares": 200, - "avg_price": 0, + "stop_loss": 220.57, + "take_profit": 277.58, + "entry_low": 231.32, + "entry_high": 269.87, "currency": "CNY", - "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损240.22 | 目标0 | 买入区231.77~270.4 | 信号:持有", - "stop_loss": 240.22, - "entry_low": 231.77, - "entry_high": 270.4, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=270.94 MA10=273.7 MA20=265.98 MA60=254.7", + "strategy_type": "holding", + "action": "持有观察 | ⚠️盈亏比极低,关注 | 止损220.57 | 目标0 | 买入区231.32~269.87 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:08", - "updated_at": "2026-07-03 12:08", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业光伏设备-0.5%,中性", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=270.94 MA10=273.7 MA20=265.98 MA60=254.7", "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 240.22, - "entry_zone": "231.77~270.4" - }, - "created_at": "2026-07-03 12:08", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:08", - "old_action": "", - "new_action": "持有观察 | ⚠️盈亏比极低,关注 | 止损240.22 | 目标0 | 买入区231.77~270.4 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] - }, - { - "code": "688630", - "name": "芯碁微装", - "price": 495.95, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标538.28 | 止损468.86 | 买入区486.03~491.11 | 信号:买入", - "stop_loss": 468.86, - "entry_low": 486.03, - "entry_high": 491.11, - "tech_snapshot": "形态:带下影阳线/bullish 量价:主动买盘占优 强撑:423.38 弱撑:459.67 弱压:517.12 强压:538.28 | MA5=518.14 MA10=511.3 MA20=462.1 MA60=333.6", - "timing_signal": "信号不充分", - "rr_ratio": 1.17, - "status": "review", - "note": "⚠️盈亏比不足1:1.5,不建议买入", - "timestamp": "2026-07-03 12:08", - "updated_at": "2026-07-03 12:08", - "type": "自选策略", + "sector_context": "行业光伏设备大跌-103.0%,收紧止损", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "行业专用设备上涨+2.0%,正常", - "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 468.86, - "entry_zone": "486.03~491.11", - "take_profit_zone": "0~538.28" - }, - "created_at": "2026-07-03 12:08", - "take_profit": 538.28, - "changelog": [ - { - "date": "2026-07-03 12:08", - "old_action": "", - "new_action": "盈利持有 | ⚠️盈亏比不足1:1.5,不建议买入 | 目标538.28 | 止损468.86 | 买入区486.03~491.11 | 信号:买入", - "reason": "初始策略创建 | 形态:带下影阳线/bullish 量价:主动买盘占优 强撑:423.38 弱撑:459.67 弱压:517.12 强压:", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:17:22", + "updated_at": "2026-07-03 11:17:22", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:17:22", + "type": "holding" }, { "code": "688639", "name": "华恒生物", - "price": 16.69, + "version": 1, + "price": 16.62, "cost": 21.51, "shares": 2800, - "avg_price": 0, + "stop_loss": 13.78, + "take_profit": 17.95, + "entry_low": 14.96, + "entry_high": 17.45, "currency": "CNY", - "action": "深套持有 | 深套持有 | 止损14.44 | 目标0 | 买入区15.02~17.52 | 信号:持有", - "stop_loss": 14.44, - "entry_low": 15.02, - "entry_high": 17.52, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=33.68 MA10=35.7 MA20=36.69 MA60=34.28", + "strategy_type": "holding", + "action": "深套持有 | 深套持有 | 止损13.78 | 目标0 | 买入区14.96~17.45 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "深套持有", - "timestamp": "2026-07-03 12:08", - "updated_at": "2026-07-03 12:08", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业化学制品下跌-1.4%,适度防御", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=33.68 MA10=35.7 MA20=36.69 MA60=34.28", "stock_category": "深套", - "position_advice": "不补不割", - "time_horizon": "长期", - "trigger": { - "stop_loss": 14.44, - "entry_zone": "15.02~17.52" - }, - "created_at": "2026-07-03 12:08", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:08", - "old_action": "", - "new_action": "深套持有 | 深套持有 | 止损14.44 | 目标0 | 买入区15.02~17.52 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] - }, - { - "code": "688795", - "name": "摩尔线程-U", - "price": 654.0, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | 目标704.53 | 止损634.38 | 买入区640.92~662.44 | 信号:观望", - "stop_loss": 634.38, - "entry_low": 640.92, - "entry_high": 662.44, - "tech_snapshot": "形态:带上影阳线/neutral 量价:主动卖盘占优 强撑:596.47 弱撑:636.51 弱压:667.98 强压:704.53 | MA5=681.57 MA10=685.62 MA20=656.21 MA60=660.88", - "timing_signal": "观望", - "rr_ratio": 2.58, - "status": "updated", - "note": "", - "timestamp": "2026-07-03 12:09", - "updated_at": "2026-07-03 12:09", - "type": "自选策略", + "sector_context": "行业化学制品大跌-145.0%,收紧止损", + "status": "active", + "trigger_json": null, + "changelog_json": null, "source": "auto", - "sector_context": "行业半导体-0.9%,中性", - "stock_category": "弱势", - "position_advice": "减仓或观望", - "time_horizon": "观望", - "trigger": { - "stop_loss": 634.38, - "entry_zone": "640.92~662.44", - "take_profit_zone": "0~704.53" - }, - "created_at": "2026-07-03 12:09", - "take_profit": 704.53, - "changelog": [ - { - "date": "2026-07-03 12:09", - "old_action": "", - "new_action": "盈利持有 | 目标704.53 | 止损634.38 | 买入区640.92~662.44 | 信号:观望", - "reason": "初始策略创建 | 形态:带上影阳线/neutral 量价:主动卖盘占优 强撑:596.47 弱撑:636.51 弱压:667.98 强压:", - "trigger": "初始创建" - } - ] - }, - { - "code": "688802", - "name": "沐曦股份-U", - "price": 744.0, - "cost": 0, - "shares": 0, - "avg_price": 0, - "currency": "CNY", - "action": "盈利持有 | 目标807.96 | 止损720.04 | 买入区729.12~755.21 | 信号:买入", - "stop_loss": 720.04, - "entry_low": 729.12, - "entry_high": 755.21, - "tech_snapshot": "形态:带下影阳线/bullish 量价:数据不足 强撑:665.96 弱撑:720.04 弱压:760.92 强压:807.96 | MA5=771.28 MA10=769.4 MA20=740.96 MA60=719.77", - "timing_signal": "买入", - "rr_ratio": 2.67, - "status": "updated", - "note": "", - "timestamp": "2026-07-03 12:09", - "updated_at": "2026-07-03 12:09", - "type": "自选策略", - "source": "auto", - "sector_context": "行业半导体-0.9%,中性", - "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 720.04, - "entry_zone": "729.12~755.21", - "take_profit_zone": "0~807.96" - }, - "created_at": "2026-07-03 12:09", - "take_profit": 807.96, - "changelog": [ - { - "date": "2026-07-03 12:09", - "old_action": "", - "new_action": "盈利持有 | 目标807.96 | 止损720.04 | 买入区729.12~755.21 | 信号:买入", - "reason": "初始策略创建 | 形态:带下影阳线/bullish 量价:数据不足 强撑:665.96 弱撑:720.04 弱压:760.92 强压:80", - "trigger": "初始创建" - } - ] + "reason": null, + "created_at": "2026-07-03 11:17:30", + "updated_at": "2026-07-03 11:17:30", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:17:30", + "type": "holding" }, { "code": "688981", "name": "中芯国际", - "price": 143.65, + "version": 1, + "price": 145.3, "cost": 126.07, "shares": 300, - "avg_price": 0, + "stop_loss": 140.94, + "take_profit": 156.92, + "entry_low": 130.77, + "entry_high": 152.57, "currency": "CNY", - "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损139.34 | 目标0 | 买入区129.28~150.83 | 信号:持有", - "stop_loss": 139.34, - "entry_low": 129.28, - "entry_high": 150.83, - "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=151.43 MA10=149.35 MA20=138.9 MA60=125.49", + "strategy_type": "holding", + "action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损140.94 | 目标0 | 买入区130.77~152.57 | 信号:持有", "timing_signal": "持有", "rr_ratio": 0.0, - "status": "updated", - "note": "⚠️盈亏比极低,关注", - "timestamp": "2026-07-03 12:09", - "updated_at": "2026-07-03 12:09", - "type": "持仓策略", - "source": "auto", - "sector_context": "行业半导体-0.9%,中性", + "tech_snapshot": "形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None | MA5=151.43 MA10=149.35 MA20=138.9 MA60=125.49", "stock_category": "中短线", - "position_advice": "中等仓位", - "time_horizon": "2~6周", - "trigger": { - "stop_loss": 139.34, - "entry_zone": "129.28~150.83" - }, - "created_at": "2026-07-03 12:09", - "take_profit": 0, - "changelog": [ - { - "date": "2026-07-03 12:09", - "old_action": "", - "new_action": "盈利良好 | ⚠️盈亏比极低,关注 | 止损139.34 | 目标0 | 买入区129.28~150.83 | 信号:持有", - "reason": "初始策略创建 | 形态:unknown/neutral 量价:数据不足 强撑:None 弱撑:None 弱压:None 强压:None |", - "trigger": "初始创建" - } - ] + "sector_context": "半导体", + "status": "active", + "trigger_json": null, + "changelog_json": null, + "source": "auto", + "reason": null, + "created_at": "2026-07-03 11:17:37", + "updated_at": "2026-07-03 11:17:37", + "avg_price": null, + "decision_timestamp": null, + "note": null, + "quality_check": null, + "quality_checked_at": null, + "quality_issues_json": null, + "position_advice": null, + "signal_factors_json": null, + "time_horizon": null, + "decision_type": null, + "trigger": {}, + "changelog": [], + "quality_issues": {}, + "signal_factors": [], + "timestamp": "2026-07-03 11:17:37", + "type": "holding" } ], - "total": 30, - "regenerated_at": "2026-07-03 12:09" + "total": 19, + "regenerated_at": "2026-07-06 11:48" } \ No newline at end of file diff --git a/data/mofin.db-shm b/data/mofin.db-shm new file mode 100644 index 0000000..2115847 Binary files /dev/null and b/data/mofin.db-shm differ diff --git a/data/mofin.db-wal b/data/mofin.db-wal new file mode 100644 index 0000000..816a4be Binary files /dev/null and b/data/mofin.db-wal differ diff --git a/data/portfolio.json b/data/portfolio.json index 7295a25..d511e98 100644 --- a/data/portfolio.json +++ b/data/portfolio.json @@ -1,240 +1,166 @@ { "holdings": [ - { - "code": "300308", - "name": "中际旭创", - "shares": 100, - "cost": 1316.53, - "price": 1116.0, - "market_value": 111600.0, - "change_pct": -2.36, - "currency": "CNY", - "position_pct": 15.27, - "_currency": "CNY" - }, - { - "code": "06869", - "name": "长飞光纤光缆", - "shares": 500, - "cost": 263.73, - "price": 201.2, - "market_value": 87220.0, - "change_pct": 1.64, - "currency": "HKD", - "position_pct": 13.47, - "_currency": "HKD" - }, - { - "code": "01478", - "name": "丘钛科技", - "shares": 11000, - "cost": 13.47, - "price": 6.99, - "market_value": 66660.0, - "change_pct": 4.02, - "currency": "HKD", - "position_pct": 7.97, - "_currency": "HKD" - }, - { - "code": "601899", - "name": "紫金矿业", - "shares": 2400, - "cost": 39.89, - "price": 27.82, - "market_value": 66768.0, - "change_pct": 5.78, - "currency": "CNY", - "position_pct": 7.34, - "_currency": "CNY" - }, - { - "code": "688411", - "name": "海博思创", - "shares": 200, - "cost": 266.95, - "price": 251.15, - "market_value": 50230.0, - "change_pct": -1.78, - "currency": "CNY", - "position_pct": 6.31, - "_currency": "CNY" - }, - { - "code": "688981", - "name": "中芯国际", - "shares": 300, - "cost": 126.07, - "price": 140.31, - "market_value": 42093.0, - "change_pct": -2.63, - "currency": "CNY", - "position_pct": 5.44, - "_currency": "CNY" - }, - { - "code": "01888", - "name": "建滔积层板", - "shares": 500, - "cost": 88.24, - "price": 84.45, - "market_value": 36545.0, - "change_pct": 0.78, - "currency": "HKD", - "position_pct": 5.28, - "_currency": "HKD" - }, - { - "code": "688639", - "name": "华恒生物", - "shares": 2800, - "cost": 21.51, - "price": 16.66, - "market_value": 46648.0, - "change_pct": -1.71, - "currency": "CNY", - "position_pct": 5.25, - "_currency": "CNY" - }, - { - "code": "300750", - "name": "宁德时代", - "shares": 100, - "cost": 401.78, - "price": 380.0, - "market_value": 38000.0, - "change_pct": -0.61, - "currency": "CNY", - "position_pct": 4.64, - "_currency": "CNY" - }, - { - "code": "01211", - "name": "比亚迪股份", - "shares": 600, - "cost": 104.87, - "price": 84.1, - "market_value": 43932.0, - "change_pct": 7.41, - "currency": "HKD", - "position_pct": 4.62, - "_currency": "HKD" - }, - { - "code": "02202", - "name": "万科企业", - "shares": 19700, - "cost": 4.67, - "price": 2.34, - "market_value": 40188.0, - "change_pct": 4.93, - "currency": "HKD", - "position_pct": 4.6, - "_currency": "HKD" - }, { "code": "00700", "name": "腾讯", "shares": 100, "cost": 443.13, - "price": 431.2, - "market_value": 37437.0, - "change_pct": 0.23, + "price": 447.0, + "market_value": 39063.0, + "change_pct": 3.66, "currency": "HKD", - "position_pct": null, - "_currency": "HKD" - }, - { - "code": "00981", - "name": "中芯国际", - "shares": 500, - "cost": 75.94, - "price": 77.6, - "market_value": 33530.0, - "change_pct": -3.48, - "currency": "HKD", - "position_pct": 4.2, - "_currency": "HKD" - }, - { - "code": "300548", - "name": "长芯博创", - "shares": 100, - "cost": 231.46, - "price": 221.01, - "market_value": 22101.0, - "change_pct": -0.45, - "currency": "CNY", - "position_pct": 3.2, - "_currency": "CNY" - }, - { - "code": "518880", - "name": "黄金ETF华安", - "shares": 2400, - "cost": 12.19, - "price": 8.67, - "market_value": 20808.0, - "change_pct": 2.32, - "currency": "CNY", - "position_pct": 2.45, - "_currency": "CNY" - }, - { - "code": "300035", - "name": "中科电气", - "shares": 1400, - "cost": 22.29, - "price": 14.29, - "market_value": 20006.0, - "change_pct": 0.85, - "currency": "CNY", - "position_pct": 2.42, - "_currency": "CNY" - }, - { - "code": "000700", - "name": "模塑科技", - "shares": 1400, - "cost": 14.83, - "price": 17.6, - "market_value": 24640.0, - "change_pct": 4.33, - "currency": "CNY", - "position_pct": 2.41, - "_currency": "CNY" - }, - { - "code": "600563", - "name": "法拉电子", - "shares": 100, - "cost": 147.18, - "price": 157.06, - "market_value": 15706.0, - "change_pct": -4.41, - "currency": "CNY", - "position_pct": 2.3, - "_currency": "CNY" + "position_pct": null }, { "code": "01088", "name": "中国神华", "shares": 500, "cost": 45.89, - "price": 40.0, - "market_value": 17355.0, - "change_pct": 1.01, + "price": 40.68, + "market_value": 17575.0, + "change_pct": 1.7, "currency": "HKD", - "position_pct": 2.14, - "_currency": "HKD" + "position_pct": 2.14 + }, + { + "code": "01211", + "name": "比亚迪股份", + "shares": 600, + "cost": 104.87, + "price": 84.2, + "market_value": 44376.0, + "change_pct": 0.12, + "currency": "HKD", + "position_pct": 4.62 + }, + { + "code": "01478", + "name": "丘钛科技", + "shares": 11000, + "cost": 13.47, + "price": 6.77, + "market_value": 64460.0, + "change_pct": -3.15, + "currency": "HKD", + "position_pct": 7.97 + }, + { + "code": "02202", + "name": "万科企业", + "shares": 19700, + "cost": 4.67, + "price": 2.33, + "market_value": 40385.0, + "change_pct": -0.43, + "currency": "HKD", + "position_pct": 4.6 + }, + { + "code": "300035", + "name": "中科电气", + "shares": 1400, + "cost": 22.29, + "price": 14.02, + "market_value": 19712.0, + "change_pct": -1.89, + "currency": "CNY", + "position_pct": 2.42 + }, + { + "code": "300308", + "name": "中际旭创", + "shares": 100, + "cost": 1316.53, + "price": 1116.03, + "market_value": 106181.0, + "change_pct": 0.0, + "currency": "CNY", + "position_pct": 15.27 + }, + { + "code": "300750", + "name": "宁德时代", + "shares": 100, + "cost": 401.78, + "price": 377.6, + "market_value": 37726.0, + "change_pct": -0.63, + "currency": "CNY", + "position_pct": 4.64 + }, + { + "code": "518880", + "name": "黄金ETF华安", + "shares": 2400, + "cost": 12.19, + "price": 8.66, + "market_value": 20832.0, + "change_pct": -0.08, + "currency": "CNY", + "position_pct": 2.45 + }, + { + "code": "600563", + "name": "法拉电子", + "shares": 100, + "cost": 147.18, + "price": 153.42, + "market_value": 15021.0, + "change_pct": -2.32, + "currency": "CNY", + "position_pct": 2.3 + }, + { + "code": "601899", + "name": "紫金矿业", + "shares": 2400, + "cost": 39.89, + "price": 28.41, + "market_value": 68784.0, + "change_pct": 2.12, + "currency": "CNY", + "position_pct": 7.34 + }, + { + "code": "688411", + "name": "海博思创", + "shares": 200, + "cost": 266.95, + "price": 262.98, + "market_value": 52064.0, + "change_pct": 4.71, + "currency": "CNY", + "position_pct": 6.31 + }, + { + "code": "688639", + "name": "华恒生物", + "shares": 2800, + "cost": 21.51, + "price": 16.61, + "market_value": 46480.0, + "change_pct": -0.3, + "currency": "CNY", + "position_pct": 5.25 + }, + { + "code": "688981", + "name": "中芯国际", + "shares": 300, + "cost": 126.07, + "price": 147.18, + "market_value": 40599.0, + "change_pct": 4.9, + "currency": "CNY", + "position_pct": 5.44 } ], - "total_assets": 952877.07, - "total_mv": 820755.14, - "stock_value": 820755.14, - "cash": 132121.93, - "frozen_cash": 0, - "position_pct": 86.13, + "cash": 289196.0, + "frozen_cash": 0.0, + "total_mv": 619713.76, + "total_assets": 908909.76, + "position_pct": 68.18, "currency": "CNY", - "updated_at": "2026-07-04 09:51" + "updated_at": "2026-07-06 11:35:19", + "stock_value": 619713.76 } \ No newline at end of file diff --git a/data/price_history.json b/data/price_history.json index bede509..7d1311c 100644 --- a/data/price_history.json +++ b/data/price_history.json @@ -27,6 +27,12 @@ "high": 1215.52, "low": 1185.0, "close": 1194.45 + }, + { + "date": "2026-07-06", + "high": 1215.0, + "low": 1180.0, + "close": 1203.23 } ], "02202": [ @@ -63,6 +69,12 @@ "high": 50.2, "low": 48.31, "close": 49.09 + }, + { + "date": "2026-07-06", + "high": 49.96, + "low": 48.81, + "close": 49.38 } ], "02359": [ @@ -119,6 +131,12 @@ "high": 502.0, "low": 444.55, "close": 480.32 + }, + { + "date": "2026-07-06", + "high": 499.0, + "low": 448.0, + "close": 476.72 } ], "06160": [ @@ -155,6 +173,12 @@ "high": 687.04, "low": 633.01, "close": 643.81 + }, + { + "date": "2026-07-06", + "high": 649.88, + "low": 615.0, + "close": 649.01 } ], "09868": [ @@ -197,6 +221,12 @@ "high": 757.88, "low": 713.0, "close": 738.38 + }, + { + "date": "2026-07-06", + "high": 745.0, + "low": 690.11, + "close": 743.0 } ], "300124": [ @@ -211,6 +241,12 @@ "high": 74.63, "low": 67.31, "close": 72.15 + }, + { + "date": "2026-07-06", + "high": 71.94, + "low": 68.6, + "close": 69.55 } ], "000657": [ @@ -225,6 +261,12 @@ "high": 101.5, "low": 87.88, "close": 89.63 + }, + { + "date": "2026-07-06", + "high": 90.58, + "low": 80.46, + "close": 81.83 } ], "000711": [ @@ -239,6 +281,12 @@ "high": 5.26, "low": 4.87, "close": 5.26 + }, + { + "date": "2026-07-06", + "high": 5.65, + "low": 5.0, + "close": 5.29 } ], "001309": [ @@ -253,6 +301,12 @@ "high": 892.1, "low": 795.0, "close": 881.91 + }, + { + "date": "2026-07-06", + "high": 918.98, + "low": 860.0, + "close": 916.6 } ], "002594": [ @@ -267,6 +321,12 @@ "high": 88.88, "low": 81.9, "close": 88.47 + }, + { + "date": "2026-07-06", + "high": 88.95, + "low": 86.61, + "close": 87.68 } ], "00700": [ @@ -335,6 +395,12 @@ "high": 646.85, "low": 574.1, "close": 618.02 + }, + { + "date": "2026-07-06", + "high": 710.0, + "low": 659.16, + "close": 695.26 } ] } \ No newline at end of file diff --git a/data/stocks/00981.json b/data/stocks/00981.json index a93c0e8..eac2576 100644 --- a/data/stocks/00981.json +++ b/data/stocks/00981.json @@ -165,6 +165,11 @@ "time": "2026-07-01T08:55:24.130605", "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-06T08:55:01.761812", + "content": "| **中芯国际双持(688981+00981)** | A+H 双持合计约 9.64% 仓位,两市场同步净多头,若港股中芯继续走弱(注意 00981 止损 ¥77.55 高于成本 ¥75.94 的异", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" } ] } \ No newline at end of file diff --git a/data/stocks/01211.json b/data/stocks/01211.json index 8c0af4d..843aa70 100644 --- a/data/stocks/01211.json +++ b/data/stocks/01211.json @@ -616,6 +616,21 @@ "content": "- **比亚迪(01211)** — 现价72.65,止损63.99(距12%)。港股汽车板块整体承压,持续下跌趋势未改。建议:重新审视持股逻辑——如果中线看多理由充分,可考虑将止损下移至60附近;如", "report_id": "cron_e02b8bde74f8_2026-06-28_22-11-21" }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "3. **0714止盈策略严重过期** — 当前 19 个持仓中有约 13 个(68%)的成本价高于止盈价,说明策略数据已随股价下跌严重偏离,大量止损/止盈位明显陈旧(如万科 02202 成本 ¥4.", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "3. **策略数据翻新** — 针对成本远高于止盈的 13 个持仓(如 万科02202、比亚迪01211、华恒生物688639、中科电气300035、黄金ETF518880 等),建议逐个审核是否需要", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "| **比亚迪(01211)止损临近** | 此前 advice 反复提示止损 ¥81.72、现价距仅 3%。港股比亚迪已是 4.62% 仓位且浮亏 -20.1%,若下周港股继续弱势,止损单需提前挂好", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, { "time": "2026-06-01T10:25:54.503460", "content": "比亚迪股份(01211) 仓位4.56% +2.27%→ 持有,连涨", diff --git a/data/stocks/01478.json b/data/stocks/01478.json index 78259ea..c9779a7 100644 --- a/data/stocks/01478.json +++ b/data/stocks/01478.json @@ -456,6 +456,16 @@ "content": "③ 丘钛科技(01478) 距止损6.48仅+7.3%!仓位7.97%", "report_id": "cron_d42f2ce3b479_2026-07-03_20-03-44" }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "2. **收紧输出质量** — 使用\"关注/观望/留意\"时须紧随具体价格、止损位和触发条件。满 3 次模糊表述自动标记为低质量输出。对 01478 和 688795 做一次明确的持仓策略复核:继续持有", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "| **丘钛科技(01478)** | 7.97% 第三大仓位,成本 ¥13.47 远超止盈 ¥7.59(浮亏约 -43%),150 次提及却无追踪结论。无论继续持有还是止损,需 Dad 明确决策 |", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, { "time": "2026-06-01T10:25:54.503460", "content": "丘钛科技(01478) 仓位8.58% +4.10%→ 持有,走势健康", diff --git a/data/stocks/02202.json b/data/stocks/02202.json index a6a84ec..1ff52be 100644 --- a/data/stocks/02202.json +++ b/data/stocks/02202.json @@ -621,6 +621,16 @@ "content": "- **万科(02202)** — 现价2.20,止损2.00(距9%)。周五港股继续弱,若跌破2.00港币将触发止损。建议:提前制定应对方案,而不是等到触发了再慌。", "report_id": "cron_e02b8bde74f8_2026-06-28_22-11-21" }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "3. **0714止盈策略严重过期** — 当前 19 个持仓中有约 13 个(68%)的成本价高于止盈价,说明策略数据已随股价下跌严重偏离,大量止损/止盈位明显陈旧(如万科 02202 成本 ¥4.", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "3. **策略数据翻新** — 针对成本远高于止盈的 13 个持仓(如 万科02202、比亚迪01211、华恒生物688639、中科电气300035、黄金ETF518880 等),建议逐个审核是否需要", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, { "time": "2026-06-04T09:55:40.300986", "content": "- 万科(02202): 补仓区间2.5~2.6,昨收2.70,距+3.8%,未进入。", diff --git a/data/stocks/02628.json b/data/stocks/02628.json index cdbbec3..b0d83bf 100644 --- a/data/stocks/02628.json +++ b/data/stocks/02628.json @@ -20,6 +20,11 @@ "time": "2026-06-28T08:55:52.587605", "content": "6. 🟢 **中国人寿** (02628) — 距买入区-2%,保险板块虽弱但估值低", "report_id": "cron_watchlist_health_weekly_2026-06-27_20-04-22" + }, + { + "time": "2026-07-05T08:55:15.087154", + "content": "6. 中国人寿(02628) — 接近止损位28.26,保险板块承压", + "report_id": "cron_watchlist_health_weekly_2026-07-04_20-13-24" } ] } \ No newline at end of file diff --git a/data/stocks/09868.json b/data/stocks/09868.json index 4e63251..5a437b6 100644 --- a/data/stocks/09868.json +++ b/data/stocks/09868.json @@ -725,6 +725,11 @@ "time": "2026-06-14T08:55:34.460610", "content": "- 中国人寿(02628) & 小鹏汽车(09868) — 盈亏比过低,不建议买入", "report_id": "cron_watchlist_health_weekly_2026-06-13_20-07-41" + }, + { + "time": "2026-07-05T08:55:15.087154", + "content": "4. 小鹏汽车(09868) — 跌破止损位,汽车行业竞争格局", + "report_id": "cron_watchlist_health_weekly_2026-07-04_20-13-24" } ] } \ No newline at end of file diff --git a/data/stocks/688411.json b/data/stocks/688411.json index 546161c..c4aaec7 100644 --- a/data/stocks/688411.json +++ b/data/stocks/688411.json @@ -861,6 +861,11 @@ "content": "{\"type\":\"周复盘\",\"time\":\"周日\",\"summary\":\"A股先跌后弹分化格局,全市场普跌后暴力反弹\",\"key_holdings\":[{\"code\":\"600110\",\"name\":", "report_id": "cron_e02b8bde74f8_2026-06-14_22-04-57" }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "2. **海博思创(688411)追踪较好** — 该股在持仓中占比 6.31%,成本 ¥266.95 处于买入区内(¥231.32~¥269.87),参与价合理,后续追踪记录清晰,止盈 ¥277.5", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, { "time": "2026-06-03T09:56:06.723399", "content": "| 688411 | 海博思创 | 295.00 | 299.17 | -1.39% | 追踪止盈290 | 正常 |", diff --git a/data/stocks/688639.json b/data/stocks/688639.json index 3059447..6b10f91 100644 --- a/data/stocks/688639.json +++ b/data/stocks/688639.json @@ -91,6 +91,11 @@ "content": "- 华恒生物(688639) ¥21.2,买入区20.78~21.4 ✅ 在区间内,RR:2.28", "report_id": "cron_e02b8bde74f8_2026-06-14_22-04-57" }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "3. **策略数据翻新** — 针对成本远高于止盈的 13 个持仓(如 万科02202、比亚迪01211、华恒生物688639、中科电气300035、黄金ETF518880 等),建议逐个审核是否需要", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, { "time": "2026-06-01T10:25:54.503460", "content": "华恒生物(688639) 仓位0.47% -1.71%→ 持有,低位整理", diff --git a/data/stocks/688795.json b/data/stocks/688795.json index e4da539..7f5b88e 100644 --- a/data/stocks/688795.json +++ b/data/stocks/688795.json @@ -26,6 +26,11 @@ "content": "• **摩尔线程(688795) 616.38** +2.73% | 买入区580-600,偏高→等回调至600以下", "report_id": "cron_d3797d924ddc_2026-06-02_16-33-11" }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "2. **收紧输出质量** — 使用\"关注/观望/留意\"时须紧随具体价格、止损位和触发条件。满 3 次模糊表述自动标记为低质量输出。对 01478 和 688795 做一次明确的持仓策略复核:继续持有", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, { "time": "2026-06-14T08:55:34.460610", "content": "**🟢 摩尔线程-U(688795) ¥610.55 | 买入区598.34~622.76 ✅ | RR:3.44**", diff --git a/data/stocks/688981.json b/data/stocks/688981.json index bf3f274..53d8284 100644 --- a/data/stocks/688981.json +++ b/data/stocks/688981.json @@ -151,6 +151,11 @@ "content": "② 中芯国际A(688981) 已跌破止损位!仓位5.44%", "report_id": "cron_d42f2ce3b479_2026-07-03_20-03-44" }, + { + "time": "2026-07-06T08:55:01.761812", + "content": "| **中芯国际双持(688981+00981)** | A+H 双持合计约 9.64% 仓位,两市场同步净多头,若港股中芯继续走弱(注意 00981 止损 ¥77.55 高于成本 ¥75.94 的异", + "report_id": "cron_e02b8bde74f8_2026-07-05_22-05-42" + }, { "time": "2026-06-11T08:55:23.441938", "content": "• 中芯国际(688981) 竞价125.00(-1.81%),策略买入区116~136内", diff --git a/docs/archive/strategy-review-loop.md b/docs/archive/strategy-review-loop.md new file mode 100644 index 0000000..8d6400b --- /dev/null +++ b/docs/archive/strategy-review-loop.md @@ -0,0 +1,97 @@ +# 策略复盘闭环系统设计 + +> 版本: v1 | 最后更新: 2026-06-24 +> 核心理念:每条策略建议都必须有回头检查,用实际结果驱动策略逻辑进化。 + +## 一、现状 + +| 环节 | 状态 | +|------|------| +| 策略生成 | ✅ `strategy_lifecycle.py` 按规则生成买入区/止损/止盈 | +| 策略评估 | ✅ 日评估六维分析每条策略的当前状况 | +| 策略重评 | ✅ 过期/偏离时自动触发重评 | +| **成功率追踪** | ❌ 有 `accuracy_stats` 空表,从未写入 | +| **复盘归因** | ❌ 没有"回头看"机制 | +| **策略逻辑修正** | ❌ 评估结果从不反馈到生成规则 | +| **验证测试** | ❌ 改完规则没有验证环节 | + +## 二、闭环设计 + +``` +生成策略 → 执行/等待 → 回头看(复盘) → 归因分析 → 修正策略逻辑 → 验证 → 部署 + ↑ | + └──── 迭代循环 ───────┘ +``` + +### 环节1:回头看(复盘) + +每条策略/建议在生成后 T+5、T+20、T+60 三个时间点回头检查: + +| 检查点 | 时机 | 判断标准 | +|--------|------|---------| +| T+5 | 5个交易日后 | 止损触发?止盈触发?价格走向是否正确? | +| T+20 | 一个月后 | 中期趋势验证。买入区是否有效? | +| T+60 | 三个月后 | 长期逻辑验证。大方向判断是否正确? | + +判断分类: +- ✅ **正确**:价格朝预期方向走了 > 止损/止盈间距的 50% +- ⚠️ **部分正确**:方向对了但幅度不够,或方向对但时机差 +- ❌ **错误**:方向错了,或止损被打后价格反转了 +- ⏳ **待定**:还在运行中,未到判断时点 + +### 环节2:归因分析 + +对 ❌ 错误 和 ⚠️ 部分正确 的做根因分类: + +| 失败模式 | 判断条件 | 修复方向 | +|----------|---------|---------| +| 止损过紧 | 价格跌破止损 < 3天后回到买入区 | 放宽止损到强支撑 × 0.95 | +| 入场过早 | 买入后继续跌 > 入场点 10% 才反弹 | 买入区下移,等缩量确认 | +| 止盈过近 | 价格突破止盈 < 5天后继续涨 > 15% | 止盈放到更高阻力位 | +| 方向看错 | 价格持续朝反方向走 | 检查多周期趋势判断逻辑 | +| 情景错配 | 策略假设的情景与实际不符 | 加入情景过滤条件 | +| 信号误判 | timing_signal 信号错误 | 修正信号合成逻辑 | +| 行业拖累 | 个股选对了但行业暴跌 | 加入行业动量过滤 | + +### 环节3:策略逻辑修正 + +归因结果反馈到策略生成规则的几个层面: + +1. **prompt_manager 规则更新** — 止损/止盈/买入区的生成规则 +2. **timing_signal 合成权重调整** — 各因子的权重 +3. **股票分类规则调整** — 什么情况归为弱势/深套/短炒 +4. **情景判定阈值调整** — detect_scenario 的参数 + +### 环节4:回测验证 + +修正后的规则用历史数据跑模拟,对比新旧规则的成功率: + +``` +模拟方式:取过去60天的数据 +1. 用旧规则生成每条策略 → 计算成功率 +2. 用新规则生成同样股票的策略 → 计算成功率 +3. 对比:新规则是否 > 旧规则 +5%? +4. 如果是 → 部署新规则 +5. 如果不是 → 继续调整 +``` + +## 三、参考来源 + +知识库中有多篇量化分析文章可以参考: +- 止损/止盈的统计学最优位置 +- 多因子信号合成的权重分配方法 +- 不同市场环境下的策略参数调优 +- 回测验证的方法论和陷阱(过拟合/幸存者偏差) + +## 四、实施路线 + +### Phase 1(本session) +- 策略复盘脚本:遍历 active 策略,检查实际结果,写入 accuracy_stats +- 归因分析:对失败策略分类失败模式 +- 初步报告:当前策略整体成功率 + 常见失败模式 + +### Phase 2(后续) +- 策略逻辑修正:根据归因调整 prompt_manager 规则 +- 回测验证:用历史数据验证新规则 +- 知识库文章萃取:从量化分析文章中提取可用因子 +- 持续迭代:每周跑一次复盘,持续优化 diff --git a/mo_data.py b/mo_data.py index d6a4e79..50ab7ff 100644 --- a/mo_data.py +++ b/mo_data.py @@ -20,15 +20,22 @@ DB_PATH = '/home/hmo/web-dashboard/data/mofin.db' def _get_db(): - db = sqlite3.connect(DB_PATH) + """获取数据库连接(WAL 模式,15秒超时防并发锁)""" + db = sqlite3.connect(DB_PATH, timeout=15) db.row_factory = sqlite3.Row + db.execute("PRAGMA journal_mode=WAL") + db.execute("PRAGMA busy_timeout=15000") return db # ── portfolio ───────────────────────────────────────────────────── def read_portfolio(): - """返回 portfolio.json 等价 dict。纯 DB。""" + """返回 portfolio.json 等价 dict。纯 DB。 + + 总市值从 holdings 实时计算(shares × price,港股 × 汇率), + 不信任 portfolio_summary 的存储值,因为可能未及时更新。 + """ db = _get_db() rows = db.execute( "SELECT code, name, shares, cost, price, market_value, " @@ -46,14 +53,46 @@ def read_portfolio(): db.close() + # ── 总市值从 holdings 实时计算 ──────────────────── + cash = float(summary.get('cash', 0) or 0) + frozen = float(summary.get('frozen_cash', 0) or 0) + + # 获取港股汇率 + import subprocess, os as _os + rate = 0.865 # 默认值 + try: + hk_rate_py = _os.path.join(_os.path.dirname(_os.path.dirname(__file__)), 'hk_rate.py') + if _os.path.exists(hk_rate_py): + r = subprocess.run( + [_os.path.join(_os.path.dirname(_os.path.dirname(__file__)), 'venv', 'bin', 'python3'), + hk_rate_py, '--rate'], + capture_output=True, text=True, timeout=10 + ) + if r.returncode == 0 and r.stdout.strip(): + rate = float(r.stdout.strip()) + except Exception: + pass + + total_mv = 0.0 + for h in holdings: + p = float(h.get('price', 0) or 0) + s = float(h.get('shares', 0) or 0) + if h.get('currency') == 'HKD' or (len(str(h.get('code',''))) == 5 and str(h.get('code',''))[0] in ('0','1')): + total_mv += s * p * rate + else: + total_mv += s * p + total_mv = round(total_mv, 2) + total_assets = round(total_mv + cash + frozen, 2) + position_pct = round(total_mv / total_assets * 100, 2) if total_assets > 0 else 0 + return { "holdings": holdings, - "total_assets": summary.get("total_assets", 0), - "total_mv": summary.get("total_mv", 0), - "stock_value": summary.get("stock_value", summary.get("total_mv", 0)), - "cash": summary.get("cash", 0), - "frozen_cash": summary.get("frozen_cash", 0), - "position_pct": summary.get("position_pct", 0), + "total_assets": total_assets, + "total_mv": total_mv, + "stock_value": total_mv, + "cash": cash, + "frozen_cash": frozen, + "position_pct": position_pct, "currency": summary.get("currency", "CNY"), "updated_at": summary.get("updated_at", ""), } diff --git a/mofin_db.py b/mofin_db.py index 7cfb488..7f705ec 100644 --- a/mofin_db.py +++ b/mofin_db.py @@ -15,9 +15,11 @@ import sqlite3 import json +import time +import functools from datetime import datetime from pathlib import Path -from typing import Optional +from typing import Optional, Callable DATA_DIR = Path(__file__).parent / "data" DB_PATH = DATA_DIR / "mofin.db" @@ -27,15 +29,87 @@ DB_PATH = DATA_DIR / "mofin.db" # ═══════════════════════════════════════════════════════════ def get_conn() -> sqlite3.Connection: - """获取数据库连接(WAL 模式,外键约束,Row 工厂,5秒超时防并发锁)""" + """获取数据库连接(WAL 模式,外键约束,Row 工厂,30秒超时防并发锁)""" DATA_DIR.mkdir(parents=True, exist_ok=True) - conn = sqlite3.connect(str(DB_PATH), timeout=5) + conn = sqlite3.connect(str(DB_PATH), timeout=30) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA foreign_keys=ON") + conn.execute("PRAGMA busy_timeout=30000") + conn.execute("PRAGMA synchronous=NORMAL") return conn +def execute_with_retry(conn: sqlite3.Connection, sql: str, params: tuple = (), + max_retries: int = 3, base_delay: float = 1.0) -> sqlite3.Cursor: + """执行SQL并自动重试(捕获 database is locked)""" + last_err = None + for attempt in range(max_retries + 1): + try: + return conn.execute(sql, params) + except sqlite3.OperationalError as e: + if "database is locked" not in str(e) and "cannot commit" not in str(e): + raise # 非锁错误直接抛 + last_err = e + if attempt < max_retries: + delay = base_delay * (2 ** attempt) # 指数退避: 1s, 2s, 4s + time.sleep(delay) + else: + raise sqlite3.OperationalError( + f"DB锁重试{max_retries}次仍失败: {e}" + ) + # unreachable -- both paths in loop either return or raise + if last_err: + raise last_err # type: ignore[misc] + + +def commit_with_retry(conn: sqlite3.Connection, max_retries: int = 3, + base_delay: float = 1.0) -> None: + """提交事务并自动重试""" + last_err = None + for attempt in range(max_retries + 1): + try: + conn.commit() + return + except sqlite3.OperationalError as e: + if "database is locked" not in str(e) and "cannot commit" not in str(e): + raise + last_err = e + if attempt < max_retries: + delay = base_delay * (2 ** attempt) + time.sleep(delay) + else: + raise sqlite3.OperationalError( + f"DB提交重试{max_retries}次仍失败: {e}" + ) + raise last_err + + +def retry_db_write(func: Callable) -> Callable: + """装饰器:为 DB 写函数自动添加重试""" + @functools.wraps(func) + def wrapper(*args, **kwargs): + max_retries = 3 + base_delay = 1.0 + last_err = None + for attempt in range(max_retries + 1): + try: + return func(*args, **kwargs) + except sqlite3.OperationalError as e: + if "database is locked" not in str(e) and "cannot commit" not in str(e): + raise + last_err = e + if attempt < max_retries: + delay = base_delay * (2 ** attempt) + time.sleep(delay) + else: + raise sqlite3.OperationalError( + f"DB写重试{max_retries}次仍失败({func.__name__}): {e}" + ) + raise last_err + return wrapper + + # ═══════════════════════════════════════════════════════════ # 建表(幂等) # ═══════════════════════════════════════════════════════════ @@ -1058,6 +1132,7 @@ def write_holding_strategy(conn, code: str, name: str, data: dict) -> tuple[bool def write_holdings_batch(conn, holdings: list[dict]) -> tuple[bool, str]: """批量写入持仓(替代 portfolio.json holdings[])""" try: + conn.execute("BEGIN IMMEDIATE") for h in holdings: currency = str(h.get('currency', 'CNY')).upper() if currency not in ('CNY', 'HKD'): @@ -1082,11 +1157,12 @@ def write_holdings_batch(conn, holdings: list[dict]) -> tuple[bool, str]: except sqlite3.IntegrityError as e: conn.rollback() return False, f"币种约束: {e}" - - + except sqlite3.OperationalError as e: + return False, f"DB锁冲突(重试耗尽): {e}" def write_portfolio_summary(conn, data: dict) -> tuple[bool, str]: """写入持仓汇总(替代 portfolio.json 顶层)""" try: + conn.execute("BEGIN IMMEDIATE") conn.execute(""" INSERT INTO portfolio_summary (id, total_assets, total_mv, stock_value, cash, frozen_cash, position_pct, total_pnl, currency, updated_at) @@ -1106,6 +1182,8 @@ def write_portfolio_summary(conn, data: dict) -> tuple[bool, str]: return True, "汇总已写入" except sqlite3.IntegrityError as e: return False, f"约束: {e}" + except sqlite3.OperationalError as e: + return False, f"DB锁冲突: {e}" def write_watchlist_stock(conn, stock: dict) -> tuple[bool, str]: diff --git a/multi_timeframe.py b/multi_timeframe.py index 69f4291..43c9b79 100644 --- a/multi_timeframe.py +++ b/multi_timeframe.py @@ -17,6 +17,8 @@ import urllib.error from datetime import datetime, date, timedelta from typing import Optional +from mofin_db import get_conn + DATA_DIR = "/home/hmo/web-dashboard/data" HISTORY_PATH = os.path.join(DATA_DIR, "price_history.json") # multi_tf_cache.json 已迁移到 DB (mtf_cache 表) @@ -91,8 +93,7 @@ def _load_mtf_cache(): if _MTF_CACHE_DATA is not None: return _MTF_CACHE_DATA try: - import sqlite3 - db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') + db = get_conn() rows = db.execute("SELECT code, cache_json FROM mtf_cache").fetchall() _MTF_CACHE_DATA = {} for code, json_str in rows: @@ -112,8 +113,7 @@ def _save_mtf_cache(): if _MTF_CACHE_DATA is None: return try: - import sqlite3 - db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') + db = get_conn() 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'))", diff --git a/price_monitor.py b/price_monitor.py index 1dfa554..6c93889 100644 --- a/price_monitor.py +++ b/price_monitor.py @@ -1,727 +1,509 @@ -#!/usr/bin/env python3 -"""price_monitor.py — 高频价格监控脚本(批量版) -规则:进入区间报一次,离开区间报一次,中间不重复。 -每次运行时一次性刷新所有持仓+自选股的实时价。 -""" -import json -import urllib.request -import os -import sys -import time -from datetime import datetime - -# ── MoFin unified model ────────────────────────────────────────────── -from mo_models import is_hk_stock, get_hk_rate, calc_total_assets, calc_total_mv, calc_position_pct -from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_price_event, write_watchlist_stock, write_live_prices, read_capital_flow_cache, write_holding_strategy -from mo_data import read_portfolio, read_decisions, read_watchlist - -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" - -# 策略重评依赖(技术面驱动,非机械百分比) -sys.path.insert(0, "/home/hmo/web-dashboard") -try: - from strategy_lifecycle import reassess_strategy - HAS_REASSESS = True -except ImportError: - HAS_REASSESS = False - -try: - HK_RATE = get_hk_rate() -except Exception: - HK_RATE = 0.87 # ultimate fallback - -# 分支系统与情景检测 -try: - sys.path.insert(0, '/home/hmo/MoFin') - from strategy_tree import detect_scenario, evaluate_branches - HAS_TREE = True -except Exception: - HAS_TREE = False - def detect_scenario(): return {} - def evaluate_branches(*a, **kw): return [] - -# 情景缓存(每次run_once刷新) -_SCENARIO_CACHE = {} -_BRANCH_CACHE = {} # code -> branches list - -UA = "Mozilla/5.0" - -# ── 批量拉取价格 ────────────────────────────────────────────────────────── - -def fetch_all_prices(codes): - """腾讯批量行情API:仅用于A股(沪市/深市) - A股:sh600110 / sz000001 - 港股已迁移至 fetch_hk_eastmoney()(东方财富实时行情) - 返回 {code: (price, change, change_pct)} - """ - if not codes: - return {} - - # 只处理A股(6位代码),港股走东方财富 - a_codes = [c for c in codes if len(str(c).strip()) == 6] - if not a_codes: - return {} - - symbols = [] - code_map = {} - for code in a_codes: - code_s = str(code).strip() - if code_s.startswith(('5', '6', '9')): - sym = f"sh{code_s}" - else: - sym = f"sz{code_s}" - symbols.append(sym) - code_map[sym] = code_s - - url = f"http://qt.gtimg.cn/q={','.join(symbols)}" - try: - req = urllib.request.Request(url, headers={"User-Agent": UA}) - with urllib.request.urlopen(req, timeout=10) as r: - text = r.read().decode("gbk") - except Exception as e: - print(f"⚠️ 腾讯A股拉取失败: {e}", file=sys.stderr) - return {} - - results = {} - for line in text.strip().split("\n"): - line = line.strip() - if not line or "=" not in line: - continue - try: - raw_value = line.split("=", 1)[1].strip().strip('"').strip(";") - fields = raw_value.split("~") - if len(fields) < 6: - continue - sym = line.split("=", 1)[0].strip().lstrip("v_") - orig_code = code_map.get(sym) - if not orig_code: - continue - price = float(fields[3]) if fields[3] else 0 - prev_close = float(fields[4]) if fields[4] else 0 - change = price - prev_close if prev_close > 0 else 0 - change_pct = fields[32] if len(fields) > 32 and fields[32] else "0" - results[orig_code] = (price, change, change_pct) - except (ValueError, IndexError): - continue - - return results - - -# ── 港股实时行情(东方财富,无15分钟延迟)────────────────────────────── - -def fetch_hk_eastmoney(codes): - """东方财富港股实时行情 API — 免费、实时、无15分钟延迟。 - - API: push2.eastmoney.com - 市场代码: 116 (港交所) - 格式: 116.00700 - - 返回 {code: (price, change, change_pct)} - - Fallback: 失败时回退到腾讯 qt.gtimg.cn(15分钟延迟) - """ - if not codes: - return {} - - hk_codes = [str(c).strip() for c in codes if len(str(c).strip()) <= 5] - if not hk_codes: - return {} - - results = {} - - # 主通道:东方财富实时行情。单个请求5s超时,失败立刻切腾讯。 - # 实测限流阈值约30秒,间隔2秒可稳定运行(15只港股30秒内完成) - for code in hk_codes: - try: - url = (f"https://push2.eastmoney.com/api/qt/stock/get" - f"?secid=116.{code}" - f"&fields=f43,f170,f60,f57,f58" - f"&fltt=2") - req = urllib.request.Request(url, headers={"User-Agent": UA}) - with urllib.request.urlopen(req, timeout=5) as r: - resp = json.loads(r.read().decode("utf-8")) - - if resp.get("rc") != 0: - break # 东财不可用,切腾讯 - item = resp.get("data", {}) - if not item: - break - price = float(item.get("f43", 0)) if item.get("f43") else 0 - prev_close = float(item.get("f60", 0)) if item.get("f60") else 0 - change = round(price - prev_close, 2) if prev_close > 0 else 0 - change_pct = str(item.get("f170", "0")) - if price > 0: - results[code] = (price, change, change_pct) - time.sleep(2) # 防止触发东财限流(实测阈值~30s) - except Exception: - break # 东财不可用,立刻切腾讯 - - # Fallback: 腾讯 qt.gtimg.cn(15分钟延迟) - missing = [c for c in hk_codes if c not in results] - if missing: - try: - fallback = _fetch_hk_tencent_fallback(missing) - results.update(fallback) - except Exception: - pass - - return results - - -def _fetch_hk_tencent_fallback(codes): - """腾讯港股行情(15分钟延迟,仅作 fallback)""" - symbols = [f"hk{c}" for c in codes] - url = f"http://qt.gtimg.cn/q={','.join(symbols)}" - req = urllib.request.Request(url, headers={"User-Agent": UA}) - with urllib.request.urlopen(req, timeout=10) as r: - text = r.read().decode("gbk") - - code_map = {f"hk{c}": c for c in codes} - results = {} - for line in text.strip().split("\n"): - if "=" not in line: - continue - try: - raw = line.split("=", 1)[1].strip().strip('"').strip(";") - fields = raw.split("~") - if len(fields) < 6: - continue - sym = line.split("=", 1)[0].strip().lstrip("v_") - orig = code_map.get(sym) - if not orig: - continue - price = float(fields[3]) if fields[3] else 0 - prev_close = float(fields[4]) if fields[4] else 0 - change = price - prev_close if prev_close > 0 else 0 - change_pct = fields[32] if len(fields) > 32 and fields[32] else "0" - results[orig] = (price, change, change_pct) - except (ValueError, IndexError): - continue - return results - - -def refresh_data_prices(): - """一次性刷新portfolio.json和watchlist.json的所有实时价""" - all_codes = set() - - # 收集所有需要拉取的代码 - try: - pf = read_portfolio() - for s in pf.get('holdings', []): - all_codes.add(s['code']) - except: - pf = {"holdings": []} - - try: - wl = read_watchlist() - for s in wl.get('stocks', []): - all_codes.add(s['code']) - except: - wl = {"stocks": []} - - if not all_codes: - return 0 - - # 分批拉取:A股走腾讯(实时) + 港股走东方财富(实时) - all_list = list(all_codes) - prices = fetch_all_prices(all_list) # A股(腾讯,实时) - hk_prices = fetch_hk_eastmoney(all_list) # 港股(东方财富,实时) - prices.update(hk_prices) - updated = 0 - - # 保存全量实时价快照到 DB(替代 live_prices.json) - try: - live = {} - for code in all_codes: - if code in prices: - p, c, chg = prices[code] - live[code] = {"price": p, "change_pct": chg} - conn = get_conn() - write_live_prices(conn, live) - conn.close() - except Exception: - pass - - # 更新portfolio(只在价格变化时写入,避免触发文件变更通知) - changed = False - for s in pf.get('holdings', []): - if s['code'] in prices: - price, _, change_pct = prices[s['code']] - if price > 0: - # 港股存 HKD 原值(跟股软一致),总资产汇总时由 calc_total_assets 统一转 CNY - old = s.get('price') or 0 - if abs(old - price) > 0.001: - s['price'] = round(price, 2) - s['change_pct'] = float(change_pct) if change_pct else 0 - s['currency'] = 'HKD' if is_hk_stock(s['code']) else 'CNY' - updated += 1 - changed = True - if changed: - pf['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M') - pf['total_mv'] = calc_total_mv(pf.get('holdings', [])) - pf['total_assets'] = calc_total_assets(pf) - pf['position_pct'] = calc_position_pct(pf) - # DB 写入(替代 json.dump,强制币种约束) - for attempt in range(3): - try: - conn = get_conn() - conn.execute("PRAGMA busy_timeout=5000") # 等待5秒而非立即放弃 - write_holdings_batch(conn, pf['holdings']) - write_portfolio_summary(conn, pf) - conn.close() - if attempt > 0: - print(f" [DB写入 OK after {attempt+1} retries]", flush=True) - break - except Exception as e: - if attempt < 2: - import time; time.sleep((attempt+1)*1) - else: - print(f" [DB写入失败 3次重试后放弃] {e}", flush=True) - elif pf.get('updated_at'): - try: - last_ts = datetime.strptime(pf['updated_at'], '%Y-%m-%d %H:%M') - if (datetime.now() - last_ts).total_seconds() > 600: - pf['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M') - try: - conn = get_conn() - write_portfolio_summary(conn, pf) - conn.close() - except Exception: - pass - except: - pass - - # 更新watchlist(只在价格变化时写入) - changed = False - for s in wl.get('stocks', []): - if s['code'] in prices: - price, _, change_pct = prices[s['code']] - if price > 0: - # 港股存 HKD 原值(跟股软一致) - old = s.get('price') or 0 - if abs(old - price) > 0.001: - s['price'] = round(price, 2) - s['change_pct'] = float(change_pct) if change_pct else 0 - updated += 1 - changed = True - if changed: - wl['updated_at'] = datetime.now().isoformat() - # DB 写入(替代 json.dump) - try: - conn = get_conn() - for s in wl.get('stocks', []): - s['currency'] = 'CNY' # 自选股价格统一CNY - write_watchlist_stock(conn, s) - conn.close() - except Exception as e: - print(f" [DB watchlist写入失败] {e}", flush=True) - - # --- 汇总值重算(使用 mo_models 唯一公式)--- - try: - live_market_value = calc_total_mv(pf.get('holdings', [])) - old_mv = pf.get('total_mv', 0) - - if abs(old_mv - live_market_value) > 0.01: - pf['total_mv'] = round(live_market_value, 2) - - pf['total_assets'] = calc_total_assets(pf) - if pf['total_assets'] > 0: - pf['position_pct'] = calc_position_pct(pf) - pf['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M') - # DB 写入 - try: - conn = get_conn() - write_portfolio_summary(conn, pf) - conn.close() - except Exception as e: - print(f" [DB汇总写入失败] {e}", flush=True) - except Exception as e: - print(f" [汇总重算失败] {e}", flush=True) - # --- 结束汇总重算 --- - - return updated - - -# ── 分支系统辅助函数 ────────────────────────────────────────────────────── - -def _branch_alert_suffix(code, price, shares=0, cost=0): - """返回分支信息后缀:「 | 情景→动作」""" - if not HAS_TREE or not _SCENARIO_CACHE.get('id'): - return "" - try: - sc_id = _SCENARIO_CACHE['id'] - results = evaluate_branches(code, sc_id, price, shares, cost) - for r in results: - if r.get('applicable'): - _record_branch_trigger(code, r.get('branch_id',''), price) - branch_action = r.get('action_type', r.get('action', 'hold')) - return f" | {sc_id}→{branch_action}" - except Exception: - pass - return "" - - -def _record_branch_trigger(code, branch_id, price): - """记录分支触发事件(自成长:trigger_count+1)""" - try: - raw = read_decisions() - for d in raw.get('decisions', []): - if d.get('code') == code and d.get('strategy_tree',{}).get('branches'): - for b in d['strategy_tree']['branches']: - if b['id'] == branch_id: - b.setdefault('trigger_count', 0) - b['trigger_count'] += 1 - b['last_trigger_price'] = round(price, 2) - b['last_triggered'] = datetime.now().isoformat() - break - try: - conn = get_conn() - for d in raw.get('decisions', []): - write_holding_strategy(conn, d['code'], d.get('name', ''), d) - conn.close() - except Exception: - pass - except Exception: - pass - - -# ── 区间偏离检测 ────────────────────────────────────────────────────────── - -def load_state(): - try: - with open(STATE_PATH) as f: - return json.load(f) - except: - return {} - -def save_state(state): - os.makedirs(os.path.dirname(STATE_PATH), exist_ok=True) - with open(STATE_PATH, 'w') as f: - json.dump(state, f, ensure_ascii=False, indent=2) - -def load_breaches(): - try: - with open(BREACH_PATH) as f: - return json.load(f) - except: - return {} - -def save_breaches(data): - os.makedirs(os.path.dirname(BREACH_PATH), exist_ok=True) - with open(BREACH_PATH, 'w') as f: - json.dump(data, f, ensure_ascii=False, indent=2) - - -def load_events(): - try: - with open(EVENTS_PATH) as f: - return json.load(f) - except: - return {"events": []} - - -def save_events(events): - os.makedirs(os.path.dirname(EVENTS_PATH), exist_ok=True) - with open(EVENTS_PATH, 'w') as f: - json.dump(events, f, ensure_ascii=False, indent=2) - - -def record_event(code, name, event_type, price, trigger_value, event_label=""): - """记录一次价格触发事件到 price_events.json + SQLite""" - events = load_events() - now = datetime.now().isoformat() - events["events"].append({ - "code": code, - "name": name, - "event_type": event_type, # entry_zone, stop_loss, take_profit, exit_zone - "price": round(price, 2), - "trigger_value": trigger_value, - "event_label": event_label, - "timestamp": now, - "date": datetime.now().strftime("%Y-%m-%d"), - }) - # 保留最近10000条 - events["events"] = events["events"][-10000:] - save_events(events) - - # ── SQLite 双写 ── - try: - from mofin_db import get_conn, init_all_tables, write_price_event - conn = get_conn() - init_all_tables(conn) - write_price_event(conn, code, name, event_type, price, trigger_value, event_label) - conn.close() - except Exception: - pass # SQLite 写入失败不影响主流程 - - -def get_trigger_zones(d): - """返回该decision所有可监控的区间列表,从顶层字段读取""" - zones = [] - is_holding = d.get('shares', 0) > 0 - # 买入区间(自选和持仓都监控) - el = d.get("entry_low", 0) - eh = d.get("entry_high", 0) - if el and eh and float(el) > 0 and float(eh) > 0: - try: - zones.append(("entry_zone", "买入区间", float(el), float(eh))) - except: - pass - # 止损+止盈(只有持仓才监控,自选无意义) - if is_holding: - sl = d.get("stop_loss", 0) - if sl and float(sl) > 0: - try: - zones.append(("stop_loss", "止损", 0, float(sl))) - except: - pass - tp = d.get("take_profit", 0) - if tp and float(tp) > 0: - try: - zones.append(("take_profit_zone", "止盈区间", 0, float(tp))) - except: - pass - return zones - - -def run_once(round_label=""): - """执行一轮完整的监控流程""" - global _SCENARIO_CACHE, _BRANCH_CACHE - label = f" [{round_label}]" if round_label else "" - start = time.time() - - # 刷新情景与分支缓存(每轮更新) - _SCENARIO_CACHE = detect_scenario() if HAS_TREE else {} - _BRANCH_CACHE = {} - try: - raw = read_decisions() - for d in raw.get('decisions', []): - tree = d.get('strategy_tree', {}) - if tree and tree.get('branches'): - _BRANCH_CACHE[d['code']] = tree['branches'] - except Exception: - pass - - # === 第一步:一次性刷新所有价格 === - refreshed = refresh_data_prices() - - # === 第二步:检查触发条件 === - try: - dec = read_decisions() - except: - print(f"❌{label} 无法读取决策数据", file=sys.stderr) - return - - active = [d for d in dec.get("decisions", []) if d.get("status") in ("active", "updated")] - state = load_state() - outputs = [] - state_updated = False - - # 收集所有需要检查的代码 - check_codes = set() - for d in active: - if get_trigger_zones(d): - check_codes.add(d["code"]) - - # 批量拉取这些股票的价格(A股走腾讯 + 港股走东方财富) - all_codes = list(check_codes) - prices = fetch_all_prices(all_codes) - hk_codes = [c for c in all_codes if is_hk_stock(str(c))] - if hk_codes: - hk_prices = fetch_hk_eastmoney(hk_codes) - prices.update(hk_prices) - - for d in active: - code = d["code"] - - zones = get_trigger_zones(d) - if not zones: - continue - - price_info = prices.get(code) - if not price_info: - continue - price, _, _ = price_info - if price == 0: - continue - - name = d.get("name", code) - if code not in state: - state[code] = {} - - for key, label, lo, hi in zones: - in_zone = lo <= price <= hi - prev_in_zone = state[code].get(key, None) - - if in_zone and prev_in_zone != True: - if key == "stop_loss": - branch_sfx = _branch_alert_suffix(code, price, d.get('shares',0), d.get('cost',0)) - outputs.append(f"⚠️ {name}({code}) {price} → 跌破止损{hi}!{branch_sfx}") - record_event(code, name, "stop_loss", price, str(hi)) - else: - extra = "" - if "_price" in key: - batch_shares = d.get(key.replace("_price", "_shares"), "") - action = d.get(key.replace("_price", "_action"), "") - if batch_shares: - extra = f" {action}{batch_shares}股" if action else f" {batch_shares}股" - elif key in ("take_profit_zone",): - act = d.get("take_profit_action", "") - if act: - extra = f"({act})" - branch_sfx = _branch_alert_suffix(code, price, d.get('shares',0), d.get('cost',0)) - outputs.append(f"⚡ {name}({code}) {price} → 进入{label}{lo}~{hi}{extra}{branch_sfx}") - record_event(code, name, "entry_zone", price, f"{lo}~{hi}", label) - state[code][key] = True - state_updated = True - - elif not in_zone and prev_in_zone == True: - if key != "stop_loss": - outputs.append(f"📌 {name}({code}) {price} → 离开{label}{lo}~{hi}") - state[code][key] = False - state_updated = True - - # === 第三步:买入区偏离检测 + 自动重评 === - reassesed_codes = [] - for d in active: - code = d["code"] - name = d.get("name", code) - price_info = prices.get(code) - if not price_info: - continue - price, _, _ = price_info - if price == 0: - continue - - # 从 decisions.json 中读取 analysis 的买入区 - entry_low = d.get("entry_low", 0) - entry_high = d.get("entry_high", 0) - if not entry_low or not entry_high: - continue - - in_buy_zone = entry_low <= price <= entry_high - prev_in_buy_zone = state.get(code, {}).get("__buy_zone", None) - - # 状态变化时才触发 - if in_buy_zone and prev_in_buy_zone == False: - # 重新进入买入区 → 重评确认区间是否仍然有效 - outputs.append(f"🔄 {name}({code}) {price} → 重新进入买入区{entry_low}~{entry_high},触发技术面重评") - do_reassess = True - elif not in_buy_zone and prev_in_buy_zone == True: - # 离开买入区 → 立即重评,更新止损/止盈/区间 - outputs.append(f"🔄 {name}({code}) {price} → 离开买入区{entry_low}~{entry_high},立即技术面重评") - do_reassess = True - else: - do_reassess = False - - if do_reassess and HAS_REASSESS: - try: - cost = d.get("cost", 0) or 0 - shares = d.get("shares", 0) or 0 - profit_pct = (price - cost) / cost * 100 if cost else 0 - is_deep_loss = profit_pct < -20 - sentiment = "neutral" - if d.get("tech_snapshot"): - if "bearish" in d["tech_snapshot"]: - sentiment = "bearish" - elif "bullish" in d["tech_snapshot"]: - sentiment = "bullish" - - # 调用技术面驱动重评(非机械百分比) - result = reassess_strategy( - code, name, price, cost, shares, - current_action=d.get("action", ""), - volume_signal="中性", sentiment=sentiment, - ) - outputs.append(f" 📊 新策略: 损{result['stop_loss']} 盈{result['take_profit']} 区{result['entry_low']}~{result['entry_high']} RR={result['rr_ratio']}") - reassesed_codes.append(code) - except Exception as e: - outputs.append(f" ⚠️ 重评失败: {e}") - - # 更新买入区状态 - if "__buy_zone" not in state.get(code, {}): - if code not in state: - state[code] = {} - state[code]["__buy_zone"] = in_buy_zone - state_updated = True - - # 如果有重评过的股票,更新 decisions.json - if reassesed_codes and HAS_REASSESS: - try: - # 重新 regenerate_all 只针对受影响的股票效率太低 - # 直接全量重评(regenerate_all 内部会批量拉价格、做技术分析) - from strategy_lifecycle import regenerate_all - r = regenerate_all(stdout=False) - outputs.append(f" ✅ 策略已全量重评: {r.get('ok',0)}/{r.get('total',0)}成功") - outputs.append(f" 📌 触发股票: {', '.join(reassesed_codes)}") - except Exception as e: - outputs.append(f" ⚠️ 全量重评失败: {e}") - - # === 3.5 资金流异常检测(2026-06-27 新增)=== - try: - cf = read_capital_flow_cache(get_conn()) - # 检查所有 active decision 中的资金流异常 - for d in active: - code = d["code"] - stock_cf = cf.get("stocks", {}).get(code, {}) - analysis = stock_cf.get("analysis", {}) - alerts = analysis.get("alerts", []) - if alerts: - name = d.get("name", code) - for a in alerts: - outputs.append(f" 💰 {name}({code}) {a}") - except Exception: - pass - - # === 第四步:情景变化检测 + 输出 → 直接推XMPP === - now_str = datetime.now().strftime("%H:%M:%S") - elapsed = time.time() - start - - # 情景变化检测(跨轮对比) - if HAS_TREE and _SCENARIO_CACHE.get('id'): - prev_scenario = state.get('_system', {}).get('last_scenario', '') - curr_scenario = _SCENARIO_CACHE['id'] - if prev_scenario and curr_scenario != prev_scenario: - combo = _SCENARIO_CACHE.get('combo_action', '') - outputs.insert(0, f"🌀 情景切换: {prev_scenario}→{curr_scenario} | {combo}") - if outputs: - state.setdefault('_system', {})['last_scenario'] = curr_scenario - state_updated = True - elif not prev_scenario: - state.setdefault('_system', {})['last_scenario'] = curr_scenario - state_updated = True - - if outputs: - # 简短一行一个触发 - for o in outputs: - print(o) - # 推送XMPP(只推关键事件:止损跌破+情景切换+资金流异动,不推买入区进出/重评等操作细节) - critical = [o for o in outputs if o.startswith(("⚠️", "🌀", "💰"))] - if critical: - try: - body = "\n".join([f"{now_str}"] + critical) - payload = json.dumps({ - "to": "hmo@yoin.fun", "body": body, "type": "chat", - }).encode("utf-8") - req = urllib.request.Request( - "http://127.0.0.1:5805/", data=payload, - headers={"Content-Type": "application/json"}, - ) - urllib.request.urlopen(req, timeout=5) - except Exception: - pass - # else: SILENT — 无触发,无输出,不推 - - if state_updated: - save_state(state) - - -def main(): - """每cron触发跑一轮""" - run_once() - - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +"""price_monitor.py — 高频价格监控脚本(批量版) +规则:进入区间报一次,离开区间报一次,中间不重复。 +每次运行时一次性刷新所有持仓+自选股的实时价。 +""" +import json +import urllib.request +import os +import sys +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" +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" + +# DB 模块(同步实时价到 mofin.db) +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 + HAS_DB = True +except ImportError: + HAS_DB = False + +# 策略重评依赖(技术面驱动,非机械百分比) +sys.path.insert(0, "/home/hmo/web-dashboard") +try: + from strategy_lifecycle import reassess_strategy + HAS_REASSESS = True +except ImportError: + HAS_REASSESS = False + +UA = "Mozilla/5.0" + +# ── 批量拉取价格 ────────────────────────────────────────────────────────── + +def fetch_all_prices(codes): + """腾讯批量行情API:一次请求拉取所有股票(A股+港股) + A股:sh600110 / sz000001 + 港股:hk00700 + 返回 {code: (price, change, change_pct)} + """ + if not codes: + return {} + + # 构建批量查询串 + symbols = [] + code_map = {} # symbol -> original_code + for code in codes: + code_s = str(code).strip() + if len(code_s) == 6: + # A股:沪市以5/6/9开头,深市以0/3开头 + if code_s.startswith(('5', '6', '9')): + sym = f"sh{code_s}" + else: + sym = f"sz{code_s}" + else: + sym = f"hk{code_s}" + symbols.append(sym) + code_map[sym] = code_s + + url = f"http://qt.gtimg.cn/q={','.join(symbols)}" + try: + req = urllib.request.Request(url, headers={"User-Agent": UA}) + with urllib.request.urlopen(req, timeout=10) as r: + text = r.read().decode("gbk") + except Exception as e: + print(f"⚠️ 批量拉取失败: {e}", file=sys.stderr) + return {} + + results = {} + for line in text.strip().split("\n"): + line = line.strip() + if not line or "=" not in line: + continue + try: + # 格式: v_sh600110="1~诺德股份~600110~11.84~11.90~..." + raw_value = line.split("=", 1)[1].strip().strip('"').strip(";") + fields = raw_value.split("~") + if len(fields) < 6: + continue + sym = line.split("=", 1)[0].strip().lstrip("v_") + orig_code = code_map.get(sym) + if not orig_code: + continue + price = float(fields[3]) if fields[3] else 0 + prev_close = float(fields[4]) if fields[4] else 0 + change = price - prev_close if prev_close > 0 else 0 + change_pct = fields[32] if len(fields) > 32 and fields[32] else "0" + results[orig_code] = (price, change, change_pct) + except (ValueError, IndexError): + continue + + return results + + +def refresh_data_prices(): + """一次性刷新portfolio.json和watchlist.json的所有实时价""" + all_codes = set() + + # 收集所有需要拉取的代码 + try: + pf = json.load(open(PORTFOLIO_PATH)) + for s in pf.get('holdings', []): + all_codes.add(s['code']) + except: + pf = {"holdings": []} + + try: + wl = json.load(open(WATCHLIST_PATH)) + for s in wl.get('stocks', []): + all_codes.add(s['code']) + except: + wl = {"stocks": []} + + if not all_codes: + return 0 + + # 一次性批量拉取 + prices = fetch_all_prices(list(all_codes)) + updated = 0 + + # 更新portfolio(只在价格变化时写入,避免触发文件变更通知) + changed = False + for s in pf.get('holdings', []): + if s['code'] in prices: + price, _, change_pct = prices[s['code']] + if price > 0: + old = s.get('price', 0) + if abs(old - price) > 0.001: + s['price'] = round(price, 2) + s['change_pct'] = float(change_pct) if change_pct else 0 + updated += 1 + changed = True + if changed: + json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2) + + # 更新watchlist(只在价格变化时写入) + changed = False + for s in wl.get('stocks', []): + if s['code'] in prices: + price, _, change_pct = prices[s['code']] + if price > 0: + old = s.get('price', 0) + if abs(old - price) > 0.001: + s['price'] = round(price, 2) + s['change_pct'] = float(change_pct) if change_pct else 0 + updated += 1 + changed = True + if changed: + wl['updated_at'] = datetime.now().isoformat() + json.dump(wl, open(WATCHLIST_PATH, 'w'), ensure_ascii=False, indent=2) + + # === 同步实时价到 mofin.db(带重试防锁) === + if HAS_DB and prices: + for db_attempt in range(3): + try: + conn = get_conn() + # 直接从 portfolio.json 构建更新数据(保留已有的 market_value/currency) + db_holdings = [] + for s in pf.get('holdings', []): + code = str(s.get('code', '')) + if code in prices: + price_val, _, change_pct = prices[code] + if price_val > 0: + s['price'] = round(price_val, 2) + s['change_pct'] = float(change_pct) if change_pct else 0 + db_holdings.append(s) + + # 写入DB持仓价格(write_holdings_batch 用 ON CONFLICT UPDATE 只改价格字段) + ok, msg = write_holdings_batch(conn, db_holdings) + if not ok: + conn.close() + if db_attempt < 2: + wait = (db_attempt + 1) * 2 + print(f"⏳ DB写持仓失败: {msg} → {wait}s后重试", file=sys.stderr) + time.sleep(wait) + continue + else: + print(f"❌ DB写持仓失败(3次重试耗尽): {msg}", file=sys.stderr) + break + + # 重新计算市值(不变现金——DB的cash是权威) + mv = calc_total_mv(db_holdings) + # 读取DB当前的现金和冻结,不覆盖 + existing = conn.execute( + 'SELECT cash, frozen_cash FROM portfolio_summary WHERE id=1' + ).fetchone() + db_cash = existing['cash'] if existing else 0.0 + db_frozen = existing['frozen_cash'] if existing else 0.0 + assets = calc_total_assets({'holdings': db_holdings, 'cash': db_cash, 'frozen_cash': db_frozen}) + position_pct = round(mv / assets * 100, 2) if assets > 0 else 0 + write_portfolio_summary(conn, { + 'total_mv': mv, + 'total_assets': assets, + 'stock_value': mv, + 'cash': db_cash, + 'frozen_cash': db_frozen, + 'position_pct': position_pct, + 'currency': 'CNY', + }) + # 写实时价格表(供 read_live_prices 消费) + live = {h['code']: {'price': h.get('price',0), 'change_pct': h.get('change_pct',0)} + for h in db_holdings if h.get('code')} + write_live_prices(conn, live) + conn.commit() + conn.close() + if db_attempt > 0: + print(f"DB同步成功(第{db_attempt+1}次重试)") + break # success + except sqlite3.OperationalError as e: + conn.close() + if db_attempt < 2: + wait = (db_attempt + 1) * 2 + print(f"⏳ DB锁等待(第{db_attempt+1}次): {e} → {wait}s后重试", file=sys.stderr) + time.sleep(wait) + else: + print(f"❌ DB同步失败(3次重试耗尽): {e}", file=sys.stderr) + except Exception as e: + conn.close() + print(f"⚠️ DB同步异常: {e}", file=sys.stderr) + break + + return updated + + +# ── 区间偏离检测 ────────────────────────────────────────────────────────── + +def load_state(): + try: + with open(STATE_PATH) as f: + return json.load(f) + except: + return {} + +def save_state(state): + os.makedirs(os.path.dirname(STATE_PATH), exist_ok=True) + with open(STATE_PATH, 'w') as f: + json.dump(state, f, ensure_ascii=False, indent=2) + +def load_breaches(): + try: + with open(BREACH_PATH) as f: + return json.load(f) + except: + return {} + +def save_breaches(data): + os.makedirs(os.path.dirname(BREACH_PATH), exist_ok=True) + with open(BREACH_PATH, 'w') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + +def load_events(): + try: + with open(EVENTS_PATH) as f: + return json.load(f) + except: + return {"events": []} + + +def save_events(events): + os.makedirs(os.path.dirname(EVENTS_PATH), exist_ok=True) + with open(EVENTS_PATH, 'w') as f: + json.dump(events, f, ensure_ascii=False, indent=2) + + +def record_event(code, name, event_type, price, trigger_value, event_label=""): + """记录一次价格触发事件到 price_events.json""" + events = load_events() + now = datetime.now().isoformat() + events["events"].append({ + "code": code, + "name": name, + "event_type": event_type, # entry_zone, stop_loss, take_profit, exit_zone + "price": round(price, 2), + "trigger_value": trigger_value, + "event_label": event_label, + "timestamp": now, + "date": datetime.now().strftime("%Y-%m-%d"), + }) + # 保留最近10000条 + events["events"] = events["events"][-10000:] + save_events(events) + + +def get_trigger_zones(trigger): + """返回该trigger所有可监控的区间列表,跳过已执行的batch""" + zones = [] + for key, label in [ + ("entry_zone", "加仓区间"), + ("batch1_price", "试仓区间"), + ("batch2_price", "加仓区间"), + ("take_profit_zone", "止盈区间"), + ("watch_low", "关注区间"), + ("watch_high", "减仓区间"), + ("watch_break", "止损区间") + ]: + status_key = key.replace("_price", "_status") + if status_key in trigger and trigger[status_key] == "executed": + continue + val = trigger.get(key, "") + if val and "~" in val: + try: + parts = val.split("~") + lo, hi = float(parts[0]), float(parts[1]) + zones.append((key, label, lo, hi)) + except: + pass + sl = trigger.get("stop_loss", "") + if sl: + try: + sl_price = float(sl) if isinstance(sl, (int, float)) else float(sl) + zones.append(("stop_loss", "止损", 0, sl_price)) + except: + pass + return zones + + +def run_once(round_label=""): + """执行一轮完整的监控流程""" + label = f" [{round_label}]" if round_label else "" + start = time.time() + + # === 第一步:一次性刷新所有价格 === + refreshed = refresh_data_prices() + + # === 第二步:检查触发条件 === + try: + with open(DECISIONS_PATH) as f: + dec = json.load(f) + except: + print(f"❌{label} 无法读取decisions.json", file=sys.stderr) + return + + active = [d for d in dec.get("decisions", []) if d.get("status") == "active"] + state = load_state() + outputs = [] + state_updated = False + + # 收集所有需要检查的代码 + check_codes = set() + for d in active: + trig = d.get("trigger", {}) + if trig: + check_codes.add(d["code"]) + + # 批量拉取这些股票的价格 + prices = fetch_all_prices(list(check_codes)) + + for d in active: + code = d["code"] + trig = d.get("trigger", {}) + if not trig: + continue + + zones = get_trigger_zones(trig) + if not zones: + continue + + price_info = prices.get(code) + if not price_info: + continue + price, _ = price_info + if price == 0: + continue + + name = d.get("name", code) + if code not in state: + state[code] = {} + + for key, label, lo, hi in zones: + in_zone = lo <= price <= hi + prev_in_zone = state[code].get(key, None) + + if in_zone and prev_in_zone != True: + if key == "stop_loss": + outputs.append(f"⚠️ {name}({code}) {price} → 跌破止损{hi}!") + record_event(code, name, "stop_loss", price, str(hi)) + else: + extra = "" + if "_price" in key: + batch_shares = trig.get(key.replace("_price", "_shares"), "") + action = trig.get(key.replace("_price", "_action"), "") + if batch_shares: + extra = f" {action}{batch_shares}股" if action else f" {batch_shares}股" + elif key in ("take_profit_zone",): + act = trig.get("take_profit_action", "") + if act: + extra = f"({act})" + outputs.append(f"⚡ {name}({code}) {price} → 进入{label}{lo}~{hi}{extra}") + record_event(code, name, "entry_zone", price, f"{lo}~{hi}", label) + state[code][key] = True + state_updated = True + + elif not in_zone and prev_in_zone == True: + if key != "stop_loss": + outputs.append(f"📌 {name}({code}) {price} → 离开{label}{lo}~{hi}") + state[code][key] = False + state_updated = True + + # === 第三步:买入区偏离检测 + 自动重评 === + reassesed_codes = [] + for d in active: + code = d["code"] + name = d.get("name", code) + price_info = prices.get(code) + if not price_info: + continue + price, _ = price_info + if price == 0: + continue + + # 从 decisions.json 中读取 analysis 的买入区 + entry_low = d.get("entry_low", 0) + entry_high = d.get("entry_high", 0) + if not entry_low or not entry_high: + continue + + in_buy_zone = entry_low <= price <= entry_high + prev_in_buy_zone = state.get(code, {}).get("__buy_zone", None) + + # 状态变化时才触发 + if in_buy_zone and prev_in_buy_zone == False: + # 重新进入买入区 → 重评确认区间是否仍然有效 + outputs.append(f"🔄 {name}({code}) {price} → 重新进入买入区{entry_low}~{entry_high},触发技术面重评") + do_reassess = True + elif not in_buy_zone and prev_in_buy_zone == True: + # 离开买入区 → 立即重评,更新止损/止盈/区间 + outputs.append(f"🔄 {name}({code}) {price} → 离开买入区{entry_low}~{entry_high},立即技术面重评") + do_reassess = True + else: + do_reassess = False + + if do_reassess and HAS_REASSESS: + try: + cost = d.get("cost", 0) or 0 + shares = d.get("shares", 0) or 0 + profit_pct = (price - cost) / cost * 100 if cost else 0 + is_deep_loss = profit_pct < -20 + sentiment = "neutral" + if d.get("tech_snapshot"): + if "bearish" in d["tech_snapshot"]: + sentiment = "bearish" + elif "bullish" in d["tech_snapshot"]: + sentiment = "bullish" + + # 调用技术面驱动重评(非机械百分比) + result = reassess_strategy( + code, name, price, cost, shares, + current_action=d.get("action", ""), + volume_signal="中性", sentiment=sentiment, + ) + outputs.append(f" 📊 新策略: 损{result['stop_loss']} 盈{result['take_profit']} 区{result['entry_low']}~{result['entry_high']} RR={result['rr_ratio']}") + reassesed_codes.append(code) + except Exception as e: + outputs.append(f" ⚠️ 重评失败: {e}") + + # 更新买入区状态 + if "__buy_zone" not in state.get(code, {}): + if code not in state: + state[code] = {} + state[code]["__buy_zone"] = in_buy_zone + state_updated = True + + # 如果有重评过的股票,更新 decisions.json + if reassesed_codes and HAS_REASSESS: + try: + # 重新 regenerate_all 只针对受影响的股票效率太低 + # 直接全量重评(regenerate_all 内部会批量拉价格、做技术分析) + from strategy_lifecycle import regenerate_all + r = regenerate_all(stdout=False) + outputs.append(f" ✅ 策略已全量重评: {r.get('ok',0)}/{r.get('total',0)}成功") + outputs.append(f" 📌 触发股票: {', '.join(reassesed_codes)}") + except Exception as e: + outputs.append(f" ⚠️ 全量重评失败: {e}") + + # === 第四步:输出 === + now_str = datetime.now().strftime("%H:%M:%S") + elapsed = time.time() - start + + if outputs: + print(f"\n🔔 {now_str}{label}") + for o in outputs: + print(o) + print(f"\n{json.dumps({'type':'价格监控','time':now_str,'triggers':outputs}, ensure_ascii=False)}") + else: + # 无触发时 SILENT(中继不推送) + print(f"[SILENT]{label} 价格正常 | {refreshed}只已刷新 | {elapsed:.1f}s") + + if state_updated: + save_state(state) + + # 输出耗时 + print(f"⏱{label} {elapsed:.1f}s", flush=True) + + +def main(): + """每cron触发跑一轮""" + run_once() + + +if __name__ == "__main__": + main() diff --git a/scripts/check_db_paths.py b/scripts/check_db_paths.py new file mode 100644 index 0000000..29a00f4 --- /dev/null +++ b/scripts/check_db_paths.py @@ -0,0 +1,22 @@ +import sqlite3 + +# Script connects to THIS db +proj_db = '/home/hmo/projects/MoFin/data/mofin.db' +# Real data lives in THIS db +real_db = '/home/hmo/web-dashboard/data/mofin.db' + +for label, path in [("project", proj_db), ("real", real_db)]: + db = sqlite3.connect(path) + tables = [r[0] for r in db.execute("SELECT name FROM sqlite_master WHERE type='table'")] + print(f"{label} db ({path}): {len(tables)} tables") + for t in tables: + if t == 'todos': + sql = db.execute(f"SELECT sql FROM sqlite_master WHERE name='{t}'").fetchone() + print(f" {t}: {sql[0][:100] if sql else 'no sql'}") + elif t in ('holdings', 'holding_strategies', 'watchlist_stocks', 'portfolio_summary'): + cnt = db.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0] + print(f" {t}: {cnt} rows") + else: + cnt = db.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0] + print(f" {t}: {cnt} rows") + db.close() diff --git a/scripts/check_todos_schema.py b/scripts/check_todos_schema.py new file mode 100644 index 0000000..e3ddca0 --- /dev/null +++ b/scripts/check_todos_schema.py @@ -0,0 +1,8 @@ +import sqlite3 + +for label, path in [("project", '/home/hmo/projects/MoFin/data/mofin.db'), ("real", '/home/hmo/web-dashboard/data/mofin.db')]: + db = sqlite3.connect(path) + sql = db.execute("SELECT sql FROM sqlite_master WHERE name='todos'").fetchone() + print(f"=== {label}: {path} ===") + print(sql[0] if sql else "NOT FOUND") + db.close() diff --git a/scripts/fix_self_todo_path.py b/scripts/fix_self_todo_path.py new file mode 100644 index 0000000..9b0bec8 --- /dev/null +++ b/scripts/fix_self_todo_path.py @@ -0,0 +1,6 @@ +"""Fix DB_PATH in self_todo_executor.py""" +path = '/home/hmo/.hermes/profiles/position-analyst/scripts/self_todo_executor.py' +content = open(path).read() +content = content.replace('projects/MoFin/data', 'web-dashboard/data') +open(path, 'w').write(content) +print("DB_PATH fixed to web-dashboard/data/mofin.db") diff --git a/scripts/fix_todos_db.py b/scripts/fix_todos_db.py new file mode 100644 index 0000000..a75ece5 --- /dev/null +++ b/scripts/fix_todos_db.py @@ -0,0 +1,47 @@ +"""Fix: unify todos table schema across project and real DB""" +import sqlite3 + +project_db = '/home/hmo/projects/MoFin/data/mofin.db' +real_db = '/home/hmo/web-dashboard/data/mofin.db' + +# Zhiwei's canonical schema (from project db) +target_schema = """ + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + status TEXT DEFAULT 'pending', + priority TEXT DEFAULT 'medium', + source TEXT DEFAULT 'manual', + fix_action TEXT, + retry_count INTEGER DEFAULT 0, + note TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +""" + +def ensure_todos(db_path, label): + db = sqlite3.connect(db_path) + existing = db.execute("SELECT name FROM sqlite_master WHERE name='todos'").fetchone() + if not existing: + db.execute(f"CREATE TABLE todos ({target_schema})") + print(f"{label}: created todos table") + else: + # Ensure all columns exist + existing_cols = {r[1] for r in db.execute("PRAGMA table_info(todos)")} + needed = {'title', 'description', 'status', 'priority', 'source', 'fix_action', + 'retry_count', 'note', 'created_at', 'updated_at'} + missing = needed - existing_cols + for col in missing: + if col in ('retry_count',): + db.execute(f"ALTER TABLE todos ADD COLUMN {col} INTEGER DEFAULT 0") + elif col in ('created_at', 'updated_at'): + db.execute(f"ALTER TABLE todos ADD COLUMN {col} TIMESTAMP DEFAULT CURRENT_TIMESTAMP") + else: + db.execute(f"ALTER TABLE todos ADD COLUMN {col} TEXT") + print(f"{label}: checked, {len(missing)} missing columns added" if missing else f"{label}: schema OK") + db.commit() + db.close() + +ensure_todos(project_db, "project db") +ensure_todos(real_db, "real db") +print("\nDone. Both DBs now have matching todos schema.") diff --git a/scripts/stale_push_wlin.py b/scripts/stale_push_wlin.py index 90d1d99..e8cfe98 100644 --- a/scripts/stale_push_wlin.py +++ b/scripts/stale_push_wlin.py @@ -20,6 +20,7 @@ import threading import time from datetime import datetime, time from mo_data import read_portfolio, read_decisions +from mofin_db import get_conn # ── MoFin unified model ────────────────────────────────────────────── sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -63,9 +64,7 @@ def fetch_trend_data(code): # 价格从 DB 读取,不再自拉腾讯 API current = 0 try: - import sqlite3 - db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') - db.row_factory = sqlite3.Row + db = get_conn() row = db.execute("SELECT price FROM holdings WHERE code=? AND is_active=1", (code,)).fetchone() if not row: row = db.execute("SELECT price FROM watchlist_stocks WHERE code=? AND is_active=1", (code,)).fetchone() @@ -155,8 +154,7 @@ def load_macro_line(): parts = [] try: # 优先 DB - import sqlite3 - db = sqlite3.connect("/home/hmo/MoFin/data/mofin.db") + db = get_conn() row = db.execute( "SELECT structure FROM macro_context_log " "WHERE has_valid_data=1 ORDER BY created_at DESC LIMIT 1" diff --git a/scripts/verify_self_todo.py b/scripts/verify_self_todo.py new file mode 100644 index 0000000..62700ca --- /dev/null +++ b/scripts/verify_self_todo.py @@ -0,0 +1,32 @@ +"""Verify self_todo_executor works with real DB""" +import subprocess +script = '/home/hmo/.hermes/profiles/position-analyst/scripts/self_todo_executor.py' + +# Test 1: DB_PATH +content = open(script).read() +if 'web-dashboard/data/mofin.db' in content: + print("DB_PATH: OK") +else: + print("DB_PATH: WRONG") + exit(1) + +# Test 2: script can import and run +try: + import importlib.util + spec = importlib.util.spec_from_file_location("executor", script) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + print("Import: OK") +except Exception as e: + print(f"Import: FAIL -> {e}") + exit(1) + +# Test 3: get_pending works +try: + rows = mod.get_pending() + print(f"get_pending: OK ({len(rows)} pending)") +except Exception as e: + print(f"get_pending: FAIL -> {e}") + exit(1) + +print("\nAll checks passed.") diff --git a/strategy_lifecycle.py b/strategy_lifecycle.py index eacf924..08c844f 100644 --- a/strategy_lifecycle.py +++ b/strategy_lifecycle.py @@ -698,9 +698,8 @@ def compute_sector_adjustment(code, market_ctx, stock_sector_map): def load_macro_context(): """读取宏观上下文,返回 (bias, desc),优先 DB,回退 JSON""" try: - import sqlite3 - from pathlib import Path - conn = sqlite3.connect(str(Path(__file__).parent.parent / "data" / "mofin.db")) + 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" @@ -740,9 +739,8 @@ def batch_fetch_prices(codes): # 主通道:从 DB 读取(price_monitor 唯一价格入口) try: - import sqlite3 - db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') - db.row_factory = sqlite3.Row + 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 @@ -842,9 +840,8 @@ def get_price_tencent(code): # 主通道: DB try: - import sqlite3 - db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') - db.row_factory = sqlite3.Row + 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()