#!/usr/bin/env python3
"""模拟盘持仓状态图(方案A定稿): 上=持仓浮盈浮亏(中文名+行业+强度+持有天数), 下=已平仓结算(原因+金额)
输出: {QUANT}/backtest_zt_full/sim_state_pnl.png (供17:00推送)"""
import json, sys, os
sys.path.insert(0, '/Users/xpresso/zt_app/code')
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.font_manager
matplotlib.font_manager.fontManager.addfont('/System/Library/Fonts/STHeiti Medium.ttc')
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC']
plt.rcParams['axes.unicode_minus'] = False
import grid_or as g
from scan_daily_tb import PN
from datetime import datetime, timedelta

SLIP, C_BUY, C_SELL = 0.001, 0.00025, 0.00025 + 0.001
QUANT = '/Users/xpresso/zt_app'
OUT = f'{QUANT}/backtest_zt_full/sim_state_pnl.png'
st = json.load(open(f'{QUANT}/sim_trading_state.json'))
info = {r['ts_code']: r for r in json.load(open(f'{QUANT}/backtest_zt_full/stock_industry.json'))}
# 交易日历(自然日近似: 用data日期轴)
d0 = datetime.strptime('20260901', '%Y%m%d')

def tdays(a, b):
    da, db = datetime.strptime(a, '%Y%m%d'), datetime.strptime(b, '%Y%m%d')
    # 交易日近似: 周末跳过
    n = 0
    cur = da
    while cur < db:
        cur += timedelta(days=1)
        if cur.weekday() < 5:
            n += 1
    return max(n, 0)

print('加载行情(约5分钟)...', flush=True)
import sim_live_daily as s
cache = s.cache
today = max(st['nav_history'][-1]['date'], st['last_date'])
print(f'最新日: {today}', flush=True)

def nm(code):
    r = info.get(code, {})
    return r.get('name', code)

rows_hold, rows_cl = [], []
# 持仓: 现值
for code, pos in st['positions'].items():
    d = cache[pos['pool']][pos['code']].sort_values('trade_date').reset_index(drop=True)
    cur = float(d[d['trade_date'] == today].iloc[0]['close'])
    val_now = pos['shares'] * cur * (1 - SLIP) * (1 - C_SELL)
    rows_hold.append({'code': code, 'name': nm(code), 'ind': info.get(code, {}).get('industry', ''),
                      'pool': PN[pos['pool']], 'std': pos['std'],
                      'ret': (val_now / pos['spent'] - 1) * 100, 'pnl': val_now - pos['spent'],
                      'days': tdays(pos['buy_date'], today), 'buy': pos['buy_date']})
# 平仓
for c in st['closed']:
    rows_cl.append({'code': c['code'], 'name': nm(c['code']), 'ind': info.get(c['code'], {}).get('industry', ''),
                    'pool': PN[c['pool']], 'std': c.get('std', 0),
                    'ret': c['ret_net'] * 100, 'pnl': c['pnl'], 'reason': c['reason'],
                    'days': tdays(c['buy_date'], c['sell_date']), 'sell': c['sell_date']})
rows_hold.sort(key=lambda r: r['ret'])
realized = sum(c['pnl'] for c in st['closed'])
floating = sum(r['pnl'] for r in rows_hold)
total = st['cash'] + sum(pos['shares'] * cache[pos['pool']][pos['code']]
                         .sort_values('trade_date').reset_index(drop=True)
                         .query('trade_date==@today').iloc[0]['close'] * (1 - SLIP) * (1 - C_SELL)
                         for pos in st['positions'].values())
n_hold, n_cl = len(rows_hold), len(rows_cl)
print(f'持仓{n_hold} 平仓{n_cl} 现金{st["cash"]:,.0f} 已实现{realized:+,.0f} 浮动{floating:+,.0f} 总{total:,.0f}')

fig, axes = plt.subplots(2, 1, figsize=(14, max(7, 0.52 * n_hold + 0.55 * n_cl + 3.2)))
maxabs = max(max(abs(r['ret']) for r in rows_hold + rows_cl), 10)
lim = maxabs * 1.55

# ---- 上区: 持仓 ----
ax = axes[0]
y = np.arange(len(rows_hold))
cols = ['#c62828' if r['ret'] >= 0 else '#2e7d32' for r in rows_hold]  # A股: 红盈绿亏
ax.barh(y, [r['ret'] for r in rows_hold], color=cols, alpha=0.88, height=0.66)
for yi, r in zip(y, rows_hold):
    lab = f"{r['name']} {r['pool']} 强{r['std']:.0f}"
    if r['ind']:
        lab += f"·{r['ind']}"
    lab += f" 持{r['days']}天"
    if abs(r['ret']) > 2.5:
        ax.text(r['ret'] / 2, yi, f"{r['ret']:+.1f}%",
                va='center', ha='center', fontsize=8, color='white', fontweight='bold')
        ax.text(r['ret'] + (0.7 if r['ret'] >= 0 else -0.7), yi, lab,
                va='center', ha='left' if r['ret'] >= 0 else 'right', fontsize=8.8)
    else:
        # 短条: %并入标签开头, 避免与标签文字重叠
        lab = f"{r['ret']:+.1f}%  " + lab
        ax.text(r['ret'] + (0.9 if r['ret'] >= 0 else -0.9), yi, lab,
                va='center', ha='left' if r['ret'] >= 0 else 'right', fontsize=8.8)
ax.set_yticks([])
ax.axvline(0, color='#555', lw=0.9)
ax.set_xlim(-lim, lim)
n_profit = sum(1 for r in rows_hold if r['ret'] > 0)
ax.set_title(f'当前持仓{n_hold}只 · 浮盈{n_profit}只/浮亏{n_hold-n_profit}只 · 浮动合计{floating:+,.0f}元 (截至{today}收盘)', fontsize=12)

# ---- 下区: 已平仓 ----
ax = axes[1]
y2 = np.arange(len(rows_cl))
cols2 = ['#c62828' if r['pnl'] >= 0 else '#2e7d32' for r in rows_cl]  # 红盈绿亏
ax.barh(y2, [r['ret'] for r in rows_cl], color=cols2, alpha=0.85, height=0.6)
for yi, r in zip(y2, rows_cl):
    lab = f"{r['name']} {r['pool']} 强{r['std']:.0f}"
    if r['ind']:
        lab += f"·{r['ind']}"
    lab += f" 持{r['days']}天 {r['ret']:+.1f}%"
    ax.text(r['ret'] + (0.7 if r['ret'] >= 0 else -0.7), yi, lab,
            va='center', ha='left' if r['ret'] >= 0 else 'right', fontsize=9.5,
            color=cols2[yi], weight='bold')
ax.set_yticks([])
ax.axvline(0, color='#555', lw=0.9)
ax.set_xlim(-lim, lim)
ax.set_title(f'已平仓结算{n_cl}笔 · 已实现收益{realized:+,.0f}元', fontsize=12)

fig.suptitle(f'模拟盘持仓状态: 总资产{total:,.0f}元 (初始10万, 现金{st["cash"]:,.0f}, 已实现{realized:+,.0f} + 浮动{floating:+,.0f})',
             fontsize=13)
plt.tight_layout()
plt.savefig(OUT, dpi=140, bbox_inches='tight')
print(f'已保存: {OUT}', flush=True)
