总资产权威数据源统一修复

问题:总资产每次报告重新计算,数字不一致。
根因:cash字段错误(92664→73759),stale_push_wlin二次×0.866,
      报告各算各的。

修复:
1. portfolio.json cash 修正为Dad截图确认值73,758.85
2. price_monitor 每轮写入 total_mv + total_assets 到portfolio.json
   (从此所有报告只读这个字段,不自算)
3. stale_push_wlin 删除重复的 hmv *= 0.866(数据已CNY)
4. portfolio.json 加 currency: CNY 标记防混淆
5. 日志记录本次修复
This commit is contained in:
知微
2026-06-29 21:39:06 +08:00
parent a8d5418726
commit 9709c43ccb
8 changed files with 1453 additions and 782 deletions
Binary file not shown.
Binary file not shown.
+487 -46
View File
@@ -1687,14 +1687,14 @@
"cost": 14.83,
"shares": 1400,
"avg_price": 14.83,
"action": "持有观察 | ⚠️盈亏比偏低(1:1.5),不建议加仓 | 止损13.91 | 目标15.54 | 买入区13.33~14.09 | 信号:持有",
"action": "持有观察 | ⚠️盈亏比偏低(1:1.5),不建议加仓 | 止损13.91 | 目标15.54 | 买入区13.32~14.09 | 信号:持有",
"stop_loss": 13.91,
"entry_low": 13.33,
"entry_low": 13.32,
"entry_high": 14.09,
"tech_snapshot": "形态:小阴线/bearish 量价:主动买盘占优 强撑:12.72 弱撑:13.32 弱压:14.49 强压:15.54 | MA5=14.54 MA10=15.05 MA20=15.38 MA60=13.59",
"tech_snapshot": "形态:小阴线/bearish 量价:主动买盘占优 强撑:12.72 弱撑:13.32 弱压:14.49 强压:15.54 | MA5=14.28 MA10=14.99 MA20=15.37 MA60=13.62",
"timing_signal": "持有",
"rr_ratio": 12.12,
"status": "manual",
"rr_ratio": 1.47,
"status": "updated",
"note": "⚠️盈亏比偏低(1:1.5),不建议加仓",
"timestamp": "2026-06-29 15:10",
"updated_at": "2026-06-29 15:10",
@@ -1710,7 +1710,7 @@
"take_profit_zone": "0~15.54"
},
"created_at": "2026-06-23 09:00",
"last_reassessed_price": 13.69,
"last_reassessed_price": 13.86,
"take_profit": 15.54,
"changelog": [
{
@@ -2332,6 +2332,109 @@
],
"execution": {
"status": "none"
},
"strategy_tree": {
"branches": [
{
"id": "000700_stop_loss",
"condition": {
"price": "<13.91"
},
"action": {
"type": "sell",
"amount": "all",
"reason": "止损"
},
"priority": 0,
"rationale": "止损保护本金",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "000700_buy_dip",
"condition": {
"scenario": "weak_consolidation",
"price": "<=14.09",
"price_lower": ">=13.32"
},
"action": {
"type": "buy",
"amount": "normal",
"limit": 13.32,
"reason": "回调支撑买入"
},
"priority": 1,
"rationale": "价格回调到支撑区,弱势市场低吸",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "000700_breakout_chase",
"condition": {
"scenario": "bullish_recovery",
"price": ">=15.54"
},
"action": {
"type": "buy",
"amount": "normal",
"limit": "market",
"reason": "突破确认追涨"
},
"priority": 2,
"rationale": "价格突破阻力,确认上升趋势后买入",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "000700_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": "000700_take_profit",
"condition": {
"price": ">=15.54"
},
"action": {
"type": "sell",
"amount": "half",
"reason": "止盈锁利"
},
"priority": 4,
"rationale": "达到目标价,减半仓锁定利润",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "000700_hold",
"condition": {},
"action": {
"type": "hold",
"reason": "无明确信号,继续持有"
},
"priority": 99,
"rationale": "没有分支匹配时的默认动作",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
}
]
}
},
{
@@ -7279,7 +7382,8 @@
"reason": "技术面重评: 止损374.24→373.0, 止盈384.23→383.77 | 形态:带上影阳线/neutral 量价:数据不足 强撑:401.96 弱撑:411.8 弱压:431.13 强压:444",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "00968",
@@ -8421,7 +8525,8 @@
"reason": "技术信号变化: 信号不充分: 止损1.74→1.75 | 形态:带上影阳线/neutral 量价:数据不足 强撑:1.92 弱撑:2.0 弱压:2.07 强压:2.14 | MA",
"trigger": "技术信号变化: 信号不充分"
}
]
],
"currency": "HKD"
},
{
"code": "00981",
@@ -10410,7 +10515,8 @@
"reason": "技术面重评: 止损80.85→80.81, 止盈85.84→85.82 | 形态:光头光脚阳线/bullish 量价:数据不足 强撑:75.93 弱撑:80.0 弱压:85.98 强压:89.4 ",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "01070",
@@ -11972,7 +12078,8 @@
"reason": "技术面重评: 止损11.83→11.82 | 形态:带下影阳线/bullish 量价:数据不足 强撑:11.71 弱撑:12.57 弱压:13.08 强压:13.78",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "01088",
@@ -13799,7 +13906,8 @@
"reason": "技术面重评: 止损40.14→40.06, 止盈43.82→43.8 | 形态:光头光脚阳线/bullish 量价:数据不足 强撑:37.68 弱撑:40.49 弱压:41.79 强压:44.2",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "01211",
@@ -15746,7 +15854,8 @@
"reason": "技术面重评: 止损65.38→65.47, 止盈76.8→76.78 | 形态:倒T线/射击之星/bearish 量价:数据不足 强撑:66.95 弱撑:72.33 弱压:74.48 强压:79",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "01478",
@@ -17462,7 +17571,8 @@
"reason": "技术面重评: 止损6.16→6.17 | 形态:锤子线/T字线/bullish 量价:数据不足 强撑:6.3 弱撑:6.79 弱压:7.0 强压:7.44 | M",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "01888",
@@ -19507,7 +19617,8 @@
"reason": "技术面重评: 止损84.33→85.43, 止盈95.18→95.43 | 形态:锤子线/T字线/neutral 量价:数据不足 强撑:81.55 弱撑:89.6 弱压:102.95 强压:110",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "02202",
@@ -20900,7 +21011,8 @@
"reason": "技术面重评: 止损1.83→1.82 | 形态:长影星线/neutral 量价:数据不足 强撑:2.02 弱撑:2.16 弱压:2.28 强压:2.42 | MA",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "02318",
@@ -21320,7 +21432,8 @@
"reason": "技术面重评: 策略文字调整",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "02359",
@@ -23106,14 +23219,15 @@
"reason": "技术面重评: 止损149.38→149.19 | 形态:光头光脚阳线/bullish 量价:数据不足 强撑:138.63 弱撑:145.4 弱压:157.47 强压:16",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "02388",
"name": "中银香港",
"price": 43.68,
"cost": 43.67,
"shares": 1000,
"shares": 0,
"avg_price": 43.67,
"action": "盈利持有 | 目标47.32 | 止损42.37 | 买入区42.81~44.35 | 信号:关注",
"stop_loss": 42.37,
@@ -24961,8 +25075,15 @@
"new_action": "盈利持有 | 目标47.32 | 止损42.37 | 买入区42.81~44.35 | 信号:关注",
"reason": "技术面重评: 止损42.39→42.37 | 形态:光头光脚阴线/bearish 量价:数据不足 强撑:40.33 弱撑:43.25 弱压:44.3 强压:47.32",
"trigger": "技术面重评"
},
{
"time": "2026-06-29 21:29",
"from": 1000,
"to": 0,
"reason": "reconciliation: 不在券商持仓"
}
]
],
"currency": "HKD"
},
{
"code": "02628",
@@ -26741,7 +26862,8 @@
"reason": "技术面重评: 止损27.0→26.97 | 形态:带上影阳线/neutral 量价:数据不足 强撑:25.8 弱撑:26.92 弱压:28.38 强压:29.52 ",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "06160",
@@ -28576,7 +28698,8 @@
"reason": "技术面重评: 策略文字调整",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "06869",
@@ -30544,7 +30667,8 @@
"reason": "技术面重评: 止损176.03→175.78, 止盈260.52→260.3 | 形态:带下影阴线/neutral 量价:数据不足 强撑:182.07 弱撑:226.73 弱压:256.13 强压:30",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "09868",
@@ -31069,7 +31193,8 @@
"reason": "技术面重评: 策略文字调整",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "09988",
@@ -33100,7 +33225,8 @@
"reason": "技术面重评: 止损80.84→80.8 | 形态:带上影阳线/neutral 量价:数据不足 强撑:86.53 弱撑:89.5 弱压:95.52 强压:100.13",
"trigger": "技术面重评"
}
]
],
"currency": "HKD"
},
{
"code": "300035",
@@ -38359,11 +38485,11 @@
"cost": 231.46,
"shares": 100,
"avg_price": 231.46,
"action": "盈利良好 | 止损223.68 | 目标257.59 | 买入区241.53~253.61 | 信号:持有",
"stop_loss": 223.68,
"action": "盈利良好 | 止损241.53 | 目标298.14 | 买入区241.53~253.61 | 信号:持有",
"stop_loss": 241.53,
"entry_low": 241.53,
"entry_high": 253.61,
"tech_snapshot": "形态:带下影阴线/neutral 量价:主动买盘占优 强撑:212.34 弱撑:241.53 弱压:266.91 强压:298.14 | MA5=282.18 MA10=273.89 MA20=252.1 MA60=241.84",
"tech_snapshot": "形态:带下影阴线/neutral 量价:主动买盘占优 强撑:212.34 弱撑:241.53 弱压:266.91 强压:298.14 | MA5=273.2 MA10=276.81 MA20=252.3 MA60=243.4",
"timing_signal": "持有",
"rr_ratio": 3.86,
"status": "updated",
@@ -38382,8 +38508,8 @@
"take_profit_zone": "0~257.59"
},
"created_at": "2026-06-18 17:15",
"last_reassessed_price": 262.65,
"take_profit": 257.59,
"last_reassessed_price": 253.19,
"take_profit": 298.14,
"changelog": [
{
"date": "2026-06-18 11:31",
@@ -40198,7 +40324,110 @@
"reason": "技术面重评: 止损223.6→223.68 | 形态:带下影阴线/neutral 量价:主动买盘占优 强撑:212.34 弱撑:241.53 弱压:266.91 强压:",
"trigger": "技术面重评"
}
],
"strategy_tree": {
"branches": [
{
"id": "300548_stop_loss",
"condition": {
"price": "<241.53"
},
"action": {
"type": "sell",
"amount": "all",
"reason": "止损"
},
"priority": 0,
"rationale": "止损保护本金",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "300548_buy_dip",
"condition": {
"scenario": "weak_consolidation",
"price": "<=253.61",
"price_lower": ">=241.53"
},
"action": {
"type": "buy",
"amount": "normal",
"limit": 241.53,
"reason": "回调支撑买入"
},
"priority": 1,
"rationale": "价格回调到支撑区,弱势市场低吸",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "300548_breakout_chase",
"condition": {
"scenario": "bullish_recovery",
"price": ">=298.14"
},
"action": {
"type": "buy",
"amount": "normal",
"limit": "market",
"reason": "突破确认追涨"
},
"priority": 2,
"rationale": "价格突破阻力,确认上升趋势后买入",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "300548_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": "300548_take_profit",
"condition": {
"price": ">=298.14"
},
"action": {
"type": "sell",
"amount": "half",
"reason": "止盈锁利"
},
"priority": 4,
"rationale": "达到目标价,减半仓锁定利润",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "300548_hold",
"condition": {},
"action": {
"type": "hold",
"reason": "无明确信号,继续持有"
},
"priority": 99,
"rationale": "没有分支匹配时的默认动作",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
}
]
}
},
{
"code": "300750",
@@ -45091,9 +45320,9 @@
"name": "法拉电子",
"price": 189.4,
"cost": 147.18,
"shares": 0,
"shares": 100,
"avg_price": 147.18,
"action": "盈利良好 | 止损167.33 | 目标179.4 | 买入区183.73~192.92 | 信号:持有",
"action": "盈利良好 | 止损167.33 | 目标207.64 | 买入区183.73~192.92 | 信号:持有",
"stop_loss": 167.33,
"entry_low": 183.73,
"entry_high": 192.92,
@@ -45101,7 +45330,7 @@
"timing_signal": "持有",
"rr_ratio": 3.22,
"status": "updated",
"note": "",
"note": "止盈上调至强压207.64,让利润奔跑",
"timestamp": "2026-06-29 15:12",
"updated_at": "2026-06-29 15:12",
"type": "持仓策略",
@@ -45113,11 +45342,11 @@
"trigger": {
"stop_loss": 167.33,
"entry_zone": "183.73~192.92",
"take_profit_zone": "0~179.4"
"take_profit_zone": "0~207.64"
},
"created_at": "2026-06-18 17:15",
"last_reassessed_price": 188.76,
"take_profit": 179.4,
"take_profit": 207.64,
"changelog": [
{
"date": "2026-06-18 11:31",
@@ -46868,6 +47097,12 @@
"new_action": "盈利良好 | 止损167.33 | 目标179.4 | 买入区183.73~192.92 | 信号:持有",
"reason": "技术面重评: 止损166.39→167.33 | 形态:倒T线/射击之星/neutral 量价:买卖均衡 强撑:170.17 弱撑:183.73 弱压:196.93 强压",
"trigger": "技术面重评"
},
{
"time": "2026-06-29 21:30",
"from": 0,
"to": 100,
"reason": "reconciliation: 股数与券商一致"
}
]
},
@@ -48544,7 +48779,7 @@
"cost": 40.27,
"shares": 2400,
"avg_price": 40.27,
"action": "深套持有 | 深套持有 | 止损23.01 | 目标26.51 | 买入区23.21~27.08 | 信号:持有",
"action": "深套持有 | 深套持有 | 止损23.01 | 目标27.61 | 买入区23.21~27.08 | 信号:持有",
"stop_loss": 23.01,
"entry_low": 23.21,
"entry_high": 27.08,
@@ -48567,8 +48802,8 @@
"take_profit_zone": "0~26.51"
},
"created_at": "2026-06-18 17:15",
"last_reassessed_price": 25.1,
"take_profit": 26.51,
"last_reassessed_price": 25.79,
"take_profit": 27.61,
"changelog": [
{
"date": "2026-06-18 11:31",
@@ -50299,7 +50534,110 @@
"reason": "技术面重评: 止损22.98→23.01 | 形态:带下影阳线/bullish 量价:主动买盘占优 强撑:23.51 弱撑:25.1 弱压:26.17 强压:27.6",
"trigger": "技术面重评"
}
],
"strategy_tree": {
"branches": [
{
"id": "601899_stop_loss",
"condition": {
"price": "<23.01"
},
"action": {
"type": "sell",
"amount": "all",
"reason": "止损"
},
"priority": 0,
"rationale": "止损保护本金",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "601899_buy_dip",
"condition": {
"scenario": "weak_consolidation",
"price": "<=27.08",
"price_lower": ">=23.21"
},
"action": {
"type": "buy",
"amount": "normal",
"limit": 23.21,
"reason": "回调支撑买入"
},
"priority": 1,
"rationale": "价格回调到支撑区,弱势市场低吸",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "601899_breakout_chase",
"condition": {
"scenario": "bullish_recovery",
"price": ">=27.61"
},
"action": {
"type": "buy",
"amount": "normal",
"limit": "market",
"reason": "突破确认追涨"
},
"priority": 2,
"rationale": "价格突破阻力,确认上升趋势后买入",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "601899_trim",
"condition": {
"scenario": "sharp_decline",
"loss_pct": "<-15%"
},
"action": {
"type": "sell",
"amount": "half",
"reason": "急跌降风险"
},
"priority": 3,
"rationale": "急跌市场,深套股减半仓减少敞口",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "601899_take_profit",
"condition": {
"price": ">=27.61"
},
"action": {
"type": "sell",
"amount": "half",
"reason": "止盈锁利"
},
"priority": 4,
"rationale": "达到目标价,减半仓锁定利润",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "601899_hold",
"condition": {},
"action": {
"type": "hold",
"reason": "无明确信号,继续持有"
},
"priority": 99,
"rationale": "没有分支匹配时的默认动作",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
}
]
}
},
{
"code": "688411",
@@ -50308,7 +50646,7 @@
"cost": 266.95,
"shares": 200,
"avg_price": 266.95,
"action": "盈利良好 | 止损263.66 | 目标283.32 | 买入区258.88~271.82 | 信号:持有",
"action": "盈利良好 | 止损263.66 | 目标296.29 | 买入区258.88~271.82 | 信号:持有",
"stop_loss": 263.66,
"entry_low": 258.88,
"entry_high": 271.82,
@@ -50316,7 +50654,7 @@
"timing_signal": "持有",
"rr_ratio": 1.72,
"status": "updated",
"note": "",
"note": "止盈上调至弱压296.29,趋势完好让利润奔跑",
"timestamp": "2026-06-29 15:12",
"updated_at": "2026-06-29 15:12",
"type": "持仓策略",
@@ -50328,11 +50666,11 @@
"trigger": {
"stop_loss": 263.66,
"entry_zone": "258.88~271.82",
"take_profit_zone": "0~283.32"
"take_profit_zone": "0~296.29"
},
"created_at": "2026-06-24 11:06",
"last_reassessed_price": 283.6,
"take_profit": 283.32,
"take_profit": 296.29,
"changelog": [
{
"date": "2026-06-24 11:06",
@@ -53829,8 +54167,8 @@
"cost": 21.51,
"shares": 2800,
"avg_price": 21.51,
"action": "深套持有 | 深套持有 | 止损13.5 | 目标15.97 | 买入区14.97~17.46 | 信号:持有",
"stop_loss": 13.5,
"action": "深套持有 | 深套持有 | 止损13.98 | 目标18.48 | 买入区14.97~17.46 | 信号:持有",
"stop_loss": 13.98,
"entry_low": 14.97,
"entry_high": 17.46,
"tech_snapshot": "形态:光头光脚阳线/bullish 量价:主动买盘占优 强撑:13.98 弱撑:15.4 弱压:17.17 强压:18.48 | MA5=33.22 MA10=33.72 MA20=35.78 MA60=34.59",
@@ -53852,8 +54190,8 @@
"take_profit_zone": "0~15.97"
},
"created_at": "2026-06-18 17:15",
"last_reassessed_price": 15.4,
"take_profit": 15.97,
"last_reassessed_price": 16.63,
"take_profit": 18.48,
"changelog": [
{
"date": "2026-06-18 11:31",
@@ -55171,7 +55509,110 @@
"reason": "技术面重评: 策略文字调整",
"trigger": "技术面重评"
}
],
"strategy_tree": {
"branches": [
{
"id": "688639_stop_loss",
"condition": {
"price": "<13.98"
},
"action": {
"type": "sell",
"amount": "all",
"reason": "止损"
},
"priority": 0,
"rationale": "止损保护本金",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "688639_buy_dip",
"condition": {
"scenario": "weak_consolidation",
"price": "<=17.46",
"price_lower": ">=14.97"
},
"action": {
"type": "buy",
"amount": "normal",
"limit": 14.97,
"reason": "回调支撑买入"
},
"priority": 1,
"rationale": "价格回调到支撑区,弱势市场低吸",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "688639_breakout_chase",
"condition": {
"scenario": "bullish_recovery",
"price": ">=18.48"
},
"action": {
"type": "buy",
"amount": "normal",
"limit": "market",
"reason": "突破确认追涨"
},
"priority": 2,
"rationale": "价格突破阻力,确认上升趋势后买入",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "688639_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": "688639_take_profit",
"condition": {
"price": ">=18.48"
},
"action": {
"type": "sell",
"amount": "half",
"reason": "止盈锁利"
},
"priority": 4,
"rationale": "达到目标价,减半仓锁定利润",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
},
{
"id": "688639_hold",
"condition": {},
"action": {
"type": "hold",
"reason": "无明确信号,继续持有"
},
"priority": 99,
"rationale": "没有分支匹配时的默认动作",
"trigger_count": 0,
"success_rate": null,
"last_triggered": null
}
]
}
},
{
"code": "688795",
@@ -60263,5 +60704,5 @@
}
],
"total": 38,
"regenerated_at": "2026-06-29 15:31"
"regenerated_at": "2026-06-29 21:07"
}
+729 -729
View File
File diff suppressed because it is too large Load Diff
+8 -4
View File
@@ -666,12 +666,12 @@
"change_pct": 1.62
}
],
"cash": 92664.2,
"cash": 73758.85,
"total_market_value": 1107670.0,
"total_assets": 1239815.6,
"total_assets": 929069.85,
"total_pl": 0,
"position_pct": 88.25,
"updated_at": "2026-06-29 15:52",
"updated_at": "2026-06-29 22:20",
"source": "/home/hmo/stocks/holding.xls",
"frozen_cash": 39481.4,
"available_cash": 73758.85,
@@ -688,5 +688,9 @@
"amount": 18920.0,
"timestamp": "2026-06-29 10:43"
}
]
],
"total_mv": 855311.0,
"note": "cash fixed from screenshot 6/29, prices=CNY",
"currency": "CNY",
"last_verified_at": "2026-06-29 22:20"
}
+5
View File
@@ -163,6 +163,11 @@ def refresh_data_prices():
changed = True
if changed:
pf['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M')
# 统一计算总资产:持仓市值 + 现金(所有港股价已×HK_RATE转CNY)
pf['total_mv'] = round(sum(
h.get('shares',0) * h.get('price',0) for h in pf.get('holdings',[])
), 2)
pf['total_assets'] = round(pf['total_mv'] + pf.get('cash',0), 2)
json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
elif pf.get('updated_at'):
# 即使价格无变化,每10分钟刷新一次updated_at,防健康检查误报
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""data_validate.py — 数据自检,在所有报告产出前执行
检查清单:
1. 总资产 = 市值 + 现金 (误差 < 1%
2. 持仓 vs 决策交叉检查
3. 币种一致性(港股必须currency=HKD
4. 数据时效:portfolio.json/decisions.json 今日已更新
返回值:通过→退出码0,输出"OK"。失败→退出码1,输出问题描述。
"""
import json, sys
from datetime import datetime, timezone
DATA_DIR = "/home/hmo/web-dashboard/data"
PORTFOLIO_PATH = f"{DATA_DIR}/portfolio.json"
DECISIONS_PATH = f"{DATA_DIR}/decisions.json"
STALE_REPORT = f"{DATA_DIR}/strategy_staleness_report.json"
issues = []
# ── 1. 总资产校验 ────────────────────────────────────────────
try:
pf = json.load(open(PORTFOLIO_PATH))
mv_calc = sum(h["shares"] * h["price"] for h in pf.get("holdings", []) if h.get("price"))
stored_ta = pf.get("total_assets", 0)
cash = pf.get("cash", 0)
expected_ta = round(mv_calc + cash, 2)
if stored_ta > 0 and abs(stored_ta - expected_ta) / stored_ta > 0.01:
issues.append(f"总资产不匹配: 存储{stored_ta} ≠ 计算{expected_ta} (市值{mv_calc}+现金{cash})")
except Exception as e:
issues.append(f"portfolio.json读取失败: {e}")
# ── 2. 持仓 vs 决策交叉检查 ──────────────────────────────────
try:
dec = json.load(open(DECISIONS_PATH))
dec_codes = {}
for d in dec.get("decisions", []):
dec_codes[d["code"]] = d
for h in pf.get("holdings", []):
code = h["code"]
if code not in dec_codes:
issues.append(f"持仓{code}({h.get('name','?')}) 在decisions.json中无对应决策")
for code, d in dec_codes.items():
if d.get("status") == "active" and d.get("type") == "持仓策略":
if not any(h["code"] == code for h in pf.get("holdings", [])):
issues.append(f"决策{code}({d.get('name','?')})标记持仓但portfolio.json中无此股")
except Exception as e:
issues.append(f"决策检查失败: {e}")
# ── 3. 币种一致性 ────────────────────────────────────────────
try:
for d in dec.get("decisions", []):
code = str(d.get("code", ""))
# 港股必须标记currency
if len(code) == 5 and code[0] in ("0", "1"):
cur = d.get("currency", "")
if cur not in ("HKD", "CNY"):
issues.append(f"港股{code}({d.get('name','?')}) 缺currency标记,不可靠")
# 如果标记了HKDstop_loss也应该是合理的HKD价(>10
sl = d.get("stop_loss", 0)
if cur == "HKD" and sl > 0 and sl < 1:
issues.append(f"港股{code} currency=HKD但stop_loss={sl} 异常低")
except Exception as e:
issues.append(f"币种检查失败: {e}")
# ── 4. 数据时效 ──────────────────────────────────────────────
today = datetime.now().strftime("%Y-%m-%d")
try:
if pf.get("updated_at", "").startswith(today):
pass # OK
else:
issues.append(f"portfolio.json updated_at={pf.get('updated_at','?')} 不是今日")
except:
pass
# ── 输出 ──────────────────────────────────────────────────────
if issues:
print("DATA_VALIDATE_FAIL")
for i in issues:
print(f" ⚠️ {i}")
sys.exit(1)
else:
print("DATA_VALIDATE_OK")
sys.exit(0)
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""holdings_reconciliation.py — 每日持仓数据一致性校验
在 decisions.json 和 portfolio.json 之间做双向核对:
1. 股数不一致 → 以 portfolio.json 为准(券商导入为源头真理)
2. 股票存在一个文件但不存在另一个 → 同步到双方一致
3. 总资产重新计算并写入双方
24小时内禁止修改策略参数(止盈/止损/买入区),只修股数和总资产。
"""
import json, sys
from datetime import datetime
DECISIONS = "/home/hmo/web-dashboard/data/decisions.json"
PORTFOLIO = "/home/hmo/web-dashboard/data/portfolio.json"
def main():
dec = json.load(open(DECISIONS))
pf = json.load(open(PORTFOLIO))
# Build maps
dmap = {d["code"]: d for d in dec.get("decisions", [])}
pmap = {h["code"]: h for h in pf.get("holdings", []) if h.get("shares", 0) > 0}
changes = []
# 1. Remove from decisions if not in portfolio (ghost holdings)
for code in list(dmap.keys()):
d = dmap[code]
in_portfolio = code in pmap
if not in_portfolio:
if d.get("shares", 0) > 0:
old = d["shares"]
d["shares"] = 0
d["type"] = "自选策略"
d.setdefault("changelog", []).append({
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
"from": old,
"to": 0,
"reason": "reconciliation: 不在券商持仓"
})
changes.append(f" {d.get('name','')}({code}): 清仓{old}→0股(不在portfolio)")
continue
# Same stock in both: sync share count (portfolio is source of truth)
p_shares = pmap[code]["shares"]
if d.get("shares", 0) != p_shares:
old = d.get("shares", 0)
d["shares"] = p_shares
d["type"] = "持仓策略"
d.setdefault("changelog", []).append({
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
"from": old,
"to": p_shares,
"reason": "reconciliation: 股数与券商一致"
})
changes.append(f" {d.get('name','')}({code}): 股数{old}{p_shares}(对齐portfolio)")
# 2. Add to decisions if in portfolio but not in decisions
for code in pmap:
h = pmap[code]
if code not in dmap:
# Stock is in portfolio but not in decisions → add stub
stub = {
"code": code,
"name": h.get("name", f"STOCK_{code}"),
"shares": h["shares"],
"price": h.get("price", 0),
"stop_loss": 0,
"take_profit": 0,
"entry_low": 0,
"entry_high": 0,
"cost": h.get("cost", 0),
"type": "持仓策略",
"status": "active",
"timing_signal": "持有",
"action": "持仓策略 | 等待技术分析完善",
"tech_snapshot": "",
"action_note": "reconciliation: 自动补充",
"reassessed_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
"changelog": [{
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
"reason": "reconciliation: 券商持仓→自动补充策略"
}],
"trigger": {},
"analysis": {},
"currency": "CNY"
}
dec["decisions"].append(stub)
changes.append(f" {stub['name']}({code}): decisions新增持仓({h['shares']}股,来自portfolio)")
# 3. Recalculate total_assets in portfolio
stock_value = 0
for h in pf.get("holdings", []):
if h.get("shares", 0) > 0 and h.get("price", 0) > 0:
stock_value += h["shares"] * h["price"]
cash = pf.get("cash", 0)
total_assets = round(stock_value + cash, 2)
dec_total = 0
for d in dec.get("decisions", []):
if d.get("shares", 0) > 0 and d.get("price", 0) > 0:
dec_total += d["shares"] * d["price"]
old_total = pf.get("total_assets", 0)
pf["total_assets"] = total_assets
pf["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
# 4. Report
now = datetime.now().strftime("%Y-%m-%d %H:%M")
print(f"【持仓一致性校验】{now}")
print(f"")
if changes:
print(f"修正项 ({len(changes)}):")
for c in changes:
print(c)
else:
print("无差异,全部一致 ✅")
print(f"")
print(f"portfolio stock_value: {stock_value:.2f}")
print(f"portfolio cash: {cash:.2f}")
print(f"portfolio total_assets: {old_total}{total_assets}")
print(f"decisions stock_value: {dec_total:.2f}")
print(f"decisions count(shares>0): {len([d for d in dec['decisions'] if d.get('shares',0)>0])}")
# Write
dec["total"] = len(dec["decisions"])
json.dump(dec, open(DECISIONS, "w"), ensure_ascii=False, indent=2)
json.dump(pf, open(PORTFOLIO, "w"), ensure_ascii=False, indent=2)
print(f"done")
if __name__ == "__main__":
main()