diff --git a/data/candidate_pool.json b/data/candidate_pool.json index 0506b07..2ad62c8 100644 --- a/data/candidate_pool.json +++ b/data/candidate_pool.json @@ -1,6 +1,6 @@ { - "last_updated": "2026-06-30 09:02", - "total_candidates": 0, + "last_updated": "2026-06-30 11:11", + "total_candidates": 3, "sectors_analyzed_today": [ "半导体", "金属新材料", @@ -343,6 +343,102 @@ "drop_reason": "超7天未更新", "trend_warning": false, "trend_note": "" + }, + { + "code": "688981", + "name": "中芯国际", + "sector": "半导体", + "xiaoguo_score": 8, + "xiaoguo_reason": "晶圆代工龙头,产能利用率回升,先进制程突破预期强,机构持仓集中,趋势走稳。", + "xiaoguo_strategy": { + "entry_range": "58.00-60.50", + "stop_loss": "55.00", + "target": "68.00" + }, + "verified_price": 159.52, + "verified_change": 5.64, + "added_at": "2026-06-30 11:09", + "last_updated": "2026-06-30 11:09", + "num_observations": 1, + "score_history": [ + { + "date": "2026-06-30 11:09", + "score": 8 + } + ], + "zhiwei_star": null, + "zhiwei_reviewed": false, + "zhiwei_reviewed_at": null, + "promoted": false, + "promoted_at": null, + "dropped": false, + "drop_reason": null, + "trend_warning": false, + "trend_note": "" + }, + { + "code": "002371", + "name": "北方华创", + "sector": "半导体", + "xiaoguo_score": 9, + "xiaoguo_reason": "半导体设备核心标的,订单饱满,国产替代加速,业绩高增确定性高,资金持续加仓。", + "xiaoguo_strategy": { + "entry_range": "285.00-295.00", + "stop_loss": "270.00", + "target": "330.00" + }, + "verified_price": 882.01, + "verified_change": 2.54, + "added_at": "2026-06-30 11:09", + "last_updated": "2026-06-30 11:09", + "num_observations": 1, + "score_history": [ + { + "date": "2026-06-30 11:09", + "score": 9 + } + ], + "zhiwei_star": null, + "zhiwei_reviewed": false, + "zhiwei_reviewed_at": null, + "promoted": false, + "promoted_at": null, + "dropped": false, + "drop_reason": null, + "trend_warning": false, + "trend_note": "" + }, + { + "code": "603501", + "name": "韦尔股份", + "sector": "半导体", + "xiaoguo_score": 7.5, + "xiaoguo_reason": "CIS龙头,消费电子复苏+汽车电子放量,估值处于历史中低位,弹性较大。", + "xiaoguo_strategy": { + "entry_range": "88.00-92.00", + "stop_loss": "83.00", + "target": "105.00" + }, + "verified_price": 92.38, + "verified_change": 4.62, + "added_at": "2026-06-30 11:09", + "last_updated": "2026-06-30 11:09", + "num_observations": 1, + "score_history": [ + { + "date": "2026-06-30 11:09", + "score": 7.5 + } + ], + "zhiwei_star": null, + "zhiwei_reviewed": false, + "zhiwei_reviewed_at": null, + "promoted": false, + "promoted_at": null, + "dropped": false, + "drop_reason": null, + "trend_warning": false, + "trend_note": "" } ] } \ No newline at end of file diff --git a/data/portfolio.json b/data/portfolio.json index 76263d2..6d51167 100644 --- a/data/portfolio.json +++ b/data/portfolio.json @@ -32,8 +32,8 @@ "entry_zone": "1220.0~1256.27", "take_profit_zone": "0~1328.37" }, - "price": 1272.71, - "change_pct": 4.32 + "price": 1273.99, + "change_pct": 4.43 }, { "code": "06869", @@ -67,8 +67,8 @@ "entry_zone": "239.4~251.37", "take_profit_zone": "0~309.76" }, - "price": 219.78, - "change_pct": 5.76 + "price": 219.43, + "change_pct": 5.6 }, { "code": "01478", @@ -102,8 +102,8 @@ "entry_zone": "6.2~7.23", "take_profit_zone": "0~7.44" }, - "price": 6.01, - "change_pct": 0.29 + "price": 6.0, + "change_pct": 0.14 }, { "code": "601899", @@ -137,8 +137,8 @@ "entry_zone": "22.48~26.23", "take_profit_zone": "0~24.6" }, - "price": 24.98, - "change_pct": -3.14 + "price": 24.97, + "change_pct": -3.18 }, { "code": "688411", @@ -172,8 +172,8 @@ "entry_zone": "278.2~292.11", "take_profit_zone": "0~303.96" }, - "price": 283.32, - "change_pct": -0.94 + "price": 283.06, + "change_pct": -1.03 }, { "code": "688981", @@ -207,8 +207,8 @@ "entry_zone": "151.0~158.55", "take_profit_zone": "0~173.58" }, - "price": 159.99, - "change_pct": 5.95 + "price": 160.04, + "change_pct": 5.99 }, { "code": "01888", @@ -242,8 +242,8 @@ "entry_zone": "96.3~101.11", "take_profit_zone": "0~111.71" }, - "price": 86.45, - "change_pct": 3.43 + "price": 86.97, + "change_pct": 4.05 }, { "code": "688639", @@ -277,8 +277,8 @@ "entry_zone": "14.37~16.77", "take_profit_zone": "0~18.35" }, - "price": 15.91, - "change_pct": -4.33 + "price": 15.94, + "change_pct": -4.15 }, { "code": "300750", @@ -312,8 +312,8 @@ "entry_zone": "386.75~394.56", "take_profit_zone": "0~409.12" }, - "price": 392.86, - "change_pct": 0.13 + "price": 392.8, + "change_pct": 0.11 }, { "code": "01211", @@ -347,8 +347,8 @@ "entry_zone": "64.89~75.7", "take_profit_zone": "0~71.43" }, - "price": 62.58, - "change_pct": -1.1 + "price": 62.63, + "change_pct": -1.03 }, { "code": "02202", @@ -417,8 +417,8 @@ "entry_zone": "416.13~423.07", "take_profit_zone": "0~439.59" }, - "price": 365.6, - "change_pct": 0.24 + "price": 365.25, + "change_pct": 0.14 }, { "code": "00981", @@ -452,8 +452,8 @@ "entry_zone": "84.8~89.04", "take_profit_zone": "0~97.27" }, - "price": 78.55, - "change_pct": 6.72 + "price": 78.99, + "change_pct": 7.31 }, { "code": "300548", @@ -487,8 +487,8 @@ "entry_zone": "253.19~265.85", "take_profit_zone": "0~300.43" }, - "price": 273.16, - "change_pct": 7.89 + "price": 275.91, + "change_pct": 8.97 }, { "code": "518880", @@ -557,8 +557,8 @@ "entry_zone": "12.94~15.1", "take_profit_zone": "0~15.05" }, - "price": 14.23, - "change_pct": 0.28 + "price": 14.24, + "change_pct": 0.35 }, { "code": "000700", @@ -592,8 +592,8 @@ "entry_zone": "13.32~14.09", "take_profit_zone": "0~15.54" }, - "price": 14.7, - "change_pct": 6.06 + "price": 14.67, + "change_pct": 5.84 }, { "code": "600563", @@ -627,8 +627,8 @@ "entry_zone": "183.44~192.61", "take_profit_zone": "0~206.01" }, - "price": 190.02, - "change_pct": 0.33 + "price": 189.6, + "change_pct": 0.11 }, { "code": "01088", @@ -662,16 +662,16 @@ "entry_zone": "40.09~40.79", "take_profit_zone": "0~40.33" }, - "price": 34.98, - "change_pct": -1.95 + "price": 35.03, + "change_pct": -1.8 } ], "cash": 92678.85, "total_market_value": 835552.6, - "total_assets": 1000647.25, + "total_assets": 1001212.25, "total_pl": 0, - "position_pct": 86.79, - "updated_at": "2026-06-30 11:10", + "position_pct": 86.8, + "updated_at": "2026-06-30 11:12", "source": "/home/hmo/stocks/holding.xls", "frozen_cash": 39481.4, "available_cash": 92678.85, @@ -689,7 +689,7 @@ "timestamp": "2026-06-29 10:43" } ], - "total_mv": 868487.0, + "total_mv": 869052.0, "note": "cash fixed from screenshot 6/29, prices=CNY", "currency": "CNY", "last_verified_at": "2026-06-29 22:28", diff --git a/scripts/per_stock_reassess.py b/scripts/per_stock_reassess.py index 8c8ded6..e83de4b 100644 --- a/scripts/per_stock_reassess.py +++ b/scripts/per_stock_reassess.py @@ -43,29 +43,23 @@ def main(): try: import urllib.request code_raw = entry.get("code", "") - sym_map = {"6":"sh","5":"sh","0":"sz","3":"sz"} - prefix = "" - for k, v in sym_map.items(): - if code_raw.startswith(k): - prefix = v - break + # 港股5位代码(含0开头)→ 前缀hk + if len(code_raw) == 5 and code_raw[0] in '01': + prefix = "hk" + else: + sym_map = {"6":"sh","5":"sh","0":"sz","3":"sz"} + for k, v in sym_map.items(): + if code_raw.startswith(k): + prefix = v + break if not prefix: - prefix = "hk" if len(code_raw) == 5 else "sz" + prefix = "sz" url = f"http://qt.gtimg.cn/q={prefix}{code_raw}" resp = urllib.request.urlopen(url, timeout=5) text = resp.read().decode("gbk") fields = text.split('"')[1].split("~") price = float(fields[3]) if fields[3] else 0 - # 港股:腾讯API返回HKD,统一转CNY - if len(code_raw) == 5 and code_raw[0] in '01': - try: - sys.path.insert(0, '/home/hmo/MoFin') - from hk_rate import hkd_to_cny - _hkd_rate = hkd_to_cny() - except Exception: - _hkd_rate = 0.87 - price = round(price * _hkd_rate, 2) - print(f" 实时价: {price} {'(CNY)' if len(code_raw) == 5 and code_raw[0] in '01' else ''}") + print(f" 实时价: {price} {'(港股HKD)' if len(code_raw) == 5 and code_raw[0] in '01' else ''}") except Exception as e: print(f" 实时价获取失败: {e}", file=sys.stderr) # Try portfolio.json as fallback (price_monitor keeps live prices) @@ -117,6 +111,8 @@ def main(): # 更新 decisions_map 中对应的条目 updated = entry.copy() + # 币种标记:HK股保留HKD原始值,A股为CNY + is_hk = len(str(code)) == 5 and str(code)[0] in '01' updated.update({ "action": result["action"], "stop_loss": result.get("stop_loss", entry.get("stop_loss")), @@ -128,6 +124,7 @@ def main(): "rr_ratio": result.get("rr_ratio", entry.get("rr_ratio", 0)), "status": result.get("status", "updated"), "price": price, + "currency": "HKD" if is_hk else "CNY", }) # Save last reassessed price for debounce tracking updated["last_reassessed_price"] = price diff --git a/scripts/stale_detector.py b/scripts/stale_detector.py index e624a83..d8ac9cf 100644 --- a/scripts/stale_detector.py +++ b/scripts/stale_detector.py @@ -55,6 +55,9 @@ def fetch_prices(codes): if not oc: continue p = float(fld[3]) if fld[3] else 0 + # NOTE: HK stock prices kept in HKD — decisions.json also stores HK values in HKD + # (stop_loss/take_profit/entry). Never convert here or we mismatch CNY price vs HKD stop. + # Downstream tools that need CNY should convert at display time. c = fld[32] if len(fld) > 32 else "0" results[oc] = (p, c) except (ValueError, IndexError): @@ -120,6 +123,10 @@ def main(): issues, flags = [], [] tag = "[自选]" if is_wl else "[持仓]" + # 币种标记(港股HKD vs A股CNY,辅助下游LLM避免混读) + currency_suffix = "(HKD)" if len(str(code)) == 5 and str(code)[0] in '01' else "" + price_str = f"{price:.2f}{currency_suffix}" + buy_zone_str = f"{el}~{eh}{currency_suffix}" if currency_suffix else f"{el}~{eh}" # -- 偏离 -- if is_wl and el and eh: @@ -152,16 +159,16 @@ def main(): if strategy_deficient: flags.append("[STRATEGY_STALE]") prefix = "⚠️仓位挤占 " if position_pct > 80 else "" - issues.append(f"[STRATEGY_STALE] {prefix}价{price:.2f}在买入区{el}~{eh}但策略不完整({'RR='+f'{rr:.2f}<1.5' if rr_invalid else '无止盈位' if not tp else '非买入信号'}),买入区需重评") + issues.append(f"[STRATEGY_STALE] {prefix}价{price_str}在买入区{buy_zone_str}但策略不完整({'RR='+f'{rr:.2f}<1.5' if rr_invalid else '无止盈位' if not tp else '非买入信号'}),买入区需重评") else: prefix = "⚠️仓位挤占 " if position_pct > 80 else "" - issues.append(f"[PUSH] {prefix}价{price:.2f}入买入区{el}~{eh}") + issues.append(f"[PUSH] {prefix}价{price_str}入买入区{buy_zone_str}") elif price > eh * 1.35: flags.append("[WL_HIGH]") - issues.append(f"价{price:.2f}高出买入区+{((price/eh)-1)*100:.0f}%,买入区需重评") + issues.append(f"价{price_str}高出买入区+{((price/eh)-1)*100:.0f}%,买入区需重评") elif price > eh * 1.20: flags.append("[WL_DRIFT]") - issues.append(f"价{price:.2f}高于买入区+{((price/eh)-1)*100:.0f}%") + issues.append(f"价{price_str}高于买入区+{((price/eh)-1)*100:.0f}%") elif not is_wl and eh: dp = (price / eh - 1) * 100 if dp > 35: @@ -215,7 +222,7 @@ def main(): pass if issues: - print(f"{' '.join(flags)} {tag} {name}({code}) 价{price:.2f}{chg} | 买入{el}~{eh} | {'; '.join(issues)}") + print(f"{' '.join(flags)} {tag} {name}({code}) 价{price_str}{chg} | 买入{el}~{eh} | {'; '.join(issues)}") found += 1 if found == 0: