Files
MoFin/scripts/import_holding_xls.py
T
知微 df4f898bc4 持仓来源修复:holding.xls导入+持仓数据修正
老问题:scripts读的是 strategy_staleness_report.json 里的旧现金值,
portfolio.json 被 strategy_lifecycle.regenerate_all 反复覆盖。

修复:
1. import_holding_xls.py — 从 ~/stocks/holding.xls 导入TSV持仓
   (含25只真实持仓,14A/11H,总市值93万,现金8万,仓位92%)
2. stale_push_wlin 现金来源改读 portfolio.json(取代旧stale_report缓存)
3. 港股市值×汇率修正(之前按1:1当人民币算,总资产多估了)
4. 每条策略的决策树同步重建

脚本执行:python3 MoFin/scripts/import_holding_xls.py (含全量重评)
Dad你以后更新holding.xls后跑这条命令就行
2026-06-24 11:19:29 +08:00

139 lines
4.6 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
import_holding_xls.py — 从券商导出文件 holding.xls 导入持仓数据
读取 /home/hmo/stocks/holding.xlsTSV格式,GBK编码)
写入 /home/hmo/web-dashboard/data/portfolio.json
触发 per_stock_reassess 全量重评(更新 decisions.json + 决策树)
用法:python3 import_holding_xls.py
"""
import csv, json, sys, subprocess
from datetime import datetime
STOCKS_FILE = "/home/hmo/stocks/holding.xls"
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
CASH = 80476 # 现金余额(手动更新或从其他源读取)
def clean_cell(v):
v = v.strip()
if v.startswith('="') and v.endswith('"'):
v = v[2:-1]
elif v.startswith('='):
v = v[1:]
return v.strip()
def main():
with open(STOCKS_FILE, 'r', encoding='gbk') as f:
reader = csv.reader(f, delimiter='\t')
rows = list(reader)
print(f"读取 {STOCKS_FILE}: {len(rows)-1} 条记录")
holdings = []
total_mv_cny = 0
total_pl = 0
for r in rows[1:]:
code = clean_cell(r[0])
name = r[1].strip()
shares = int(clean_cell(r[2]))
avail = int(clean_cell(r[3]))
price_raw = r[4].strip()
currency = 'HKD' if '港币' in price_raw or '' in r[10] else 'CNY'
price_str = price_raw.replace('港币', '').replace('港元', '').replace('', '').strip()
price = float(price_str)
cost_price = float(clean_cell(r[5]))
pl = float(clean_cell(r[6]))
pl_pct = float(clean_cell(r[7])) if r[7].strip() else 0.0
mkt_val = float(clean_cell(r[11]))
cost_amount = float(clean_cell(r[15])) if r[15].strip() and r[15].strip() != '--' else 0
rate_str = clean_cell(r[16])
rate = float(rate_str) if rate_str and rate_str != '--' else 0.866
holdings.append({
'code': code,
'name': name,
'shares': shares,
'avail_shares': avail,
'price': price,
'cost_price': cost_price,
'pl': pl,
'pl_pct': pl_pct,
'currency': currency,
'market_val': mkt_val,
'cost_amount': cost_amount,
'exchange_rate': rate,
})
total_pl += pl
mv_cny = mkt_val if currency == 'CNY' else mkt_val * rate
total_mv_cny += mv_cny
pfx = 'HK$' if currency == 'HKD' else ''
print(f" {code} {name} {shares}{pfx}{price:.2f} 盈亏{pl:+,.0f}({pl_pct:+.1f}%)")
total_assets = total_mv_cny + CASH
position_pct = round(total_mv_cny / total_assets * 100, 1) if total_assets > 0 else 0
portfolio = {
'holdings': holdings,
'cash': CASH,
'total_market_value': round(total_mv_cny, 2),
'total_pl': round(total_pl, 2),
'position_pct': position_pct,
'updated_at': datetime.now().strftime('%Y-%m-%d %H:%M'),
'source': STOCKS_FILE,
}
with open(PORTFOLIO_PATH, 'w') as f:
json.dump(portfolio, f, indent=2, ensure_ascii=False)
print(f"\n已写入 {len(holdings)} 只持仓")
print(f"A股: {sum(1 for h in holdings if h['currency']=='CNY')} | 港股: {sum(1 for h in holdings if h['currency']=='HKD')}")
print(f"总市值: {round(total_mv_cny):,.0f}元 | 现金: {CASH:,}")
print(f"总资产: {round(total_assets):,.0f}元 | 仓位: {position_pct}%")
print(f"累计盈亏: {round(total_pl):+,}")
# 触发策略重评
print("\n→ 触发 per_stock_reassess 全量重评...")
r = subprocess.run(
["python3", "/home/hmo/.hermes/profiles/position-analyst/scripts/per_stock_reassess.py"],
capture_output=True, text=True, timeout=120
)
print(r.stdout[-500:] if len(r.stdout) > 500 else r.stdout)
# 重建决策树
print("\n→ 重建决策树...")
sys.path.insert(0, '/home/hmo/web-dashboard')
from strategy_tree import init_default_branches
with open('/home/hmo/web-dashboard/data/decisions.json') as f:
data = json.load(f)
ok = 0
for e in data.get('decisions', []):
branches = init_default_branches(
e.get('code', ''),
e.get('name', ''),
e.get('entry_low', 0),
e.get('entry_high', 0),
e.get('stop_loss', 0),
e.get('take_profit', 0),
)
e['strategy_tree'] = {
'branches': branches,
'created_at': datetime.now().strftime('%Y-%m-%d'),
}
ok += 1
with open('/home/hmo/web-dashboard/data/decisions.json', 'w') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(f"决策树重建完成: {ok}/{len(data.get('decisions',[]))}")
if __name__ == '__main__':
main()