llm-quant/app/ui/views/pool.py

181 lines
7.1 KiB
Python
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.

"""投资池与仓位概览页面。"""
from __future__ import annotations
from datetime import date, timedelta
import pandas as pd
import plotly.express as px
import streamlit as st
from app.utils.db import db_session
from app.utils.portfolio import (
get_latest_snapshot,
list_investment_pool,
list_positions,
list_recent_trades,
)
from app.ui.shared import LOGGER, LOG_EXTRA, get_latest_trade_date
def render_pool_overview() -> None:
"""单独的投资池与仓位概览页面(从今日计划中提取)。"""
LOGGER.info("渲染投资池与仓位概览页面", extra=LOG_EXTRA)
st.header("投资池与仓位概览")
snapshot = get_latest_snapshot()
if snapshot:
col_a, col_b, col_c = st.columns(3)
if snapshot.total_value is not None:
col_a.metric("组合净值", f"{snapshot.total_value:,.2f}")
if snapshot.cash is not None:
col_b.metric("现金余额", f"{snapshot.cash:,.2f}")
if snapshot.invested_value is not None:
col_c.metric("持仓市值", f"{snapshot.invested_value:,.2f}")
detail_cols = st.columns(4)
if snapshot.unrealized_pnl is not None:
detail_cols[0].metric("浮盈", f"{snapshot.unrealized_pnl:,.2f}")
if snapshot.realized_pnl is not None:
detail_cols[1].metric("已实现盈亏", f"{snapshot.realized_pnl:,.2f}")
if snapshot.net_flow is not None:
detail_cols[2].metric("净流入", f"{snapshot.net_flow:,.2f}")
if snapshot.exposure is not None:
detail_cols[3].metric("风险敞口", f"{snapshot.exposure:.2%}")
if snapshot.notes:
st.caption(f"备注:{snapshot.notes}")
else:
st.info("暂无组合快照,请在执行回测或实盘同步后写入 portfolio_snapshots。")
try:
latest_date = get_latest_trade_date()
candidates = list_investment_pool(trade_date=latest_date)
except Exception: # noqa: BLE001
LOGGER.exception("加载候选池失败", extra=LOG_EXTRA)
candidates = []
if candidates:
candidate_df = pd.DataFrame(
[
{
"交易日": item.trade_date,
"代码": item.ts_code,
"评分": item.score,
"状态": item.status,
"标签": "".join(item.tags) if item.tags else "-",
"理由": item.rationale or "",
}
for item in candidates
]
)
st.write("候选投资池:")
st.dataframe(candidate_df, width='stretch', hide_index=True)
else:
st.caption("候选投资池暂无数据。")
positions = list_positions(active_only=False)
if positions:
position_df = pd.DataFrame(
[
{
"ID": pos.id,
"代码": pos.ts_code,
"开仓日": pos.opened_date,
"平仓日": pos.closed_date or "-",
"状态": pos.status,
"数量": pos.quantity,
"成本": pos.cost_price,
"现价": pos.market_price,
"市值": pos.market_value,
"浮盈": pos.unrealized_pnl,
"已实现": pos.realized_pnl,
"目标权重": pos.target_weight,
}
for pos in positions
]
)
st.write("组合持仓:")
st.dataframe(position_df, width='stretch', hide_index=True)
else:
st.caption("组合持仓暂无记录。")
trades = list_recent_trades(limit=20)
if trades:
trades_df = pd.DataFrame(trades)
st.write("近期成交:")
st.dataframe(trades_df, width='stretch', hide_index=True)
else:
st.caption("近期成交暂无记录。")
st.caption("数据来源agent_utils、investment_pool、portfolio_positions、portfolio_trades、portfolio_snapshots。")
default_compare_a = latest_date or date.today()
default_compare_b = default_compare_a - timedelta(days=1)
if default_compare_b > default_compare_a:
default_compare_b = default_compare_a
compare_col1, compare_col2 = st.columns(2)
compare_date1 = compare_col1.date_input(
"日志对比日期 A",
value=default_compare_a,
key="pool_compare_date_a",
)
compare_date2 = compare_col2.date_input(
"日志对比日期 B",
value=default_compare_b,
key="pool_compare_date_b",
)
if st.button("执行对比", type="secondary"):
with st.spinner("执行日志对比分析中..."):
try:
with db_session(read_only=True) as conn:
query_date1 = f"{compare_date1.isoformat()}T00:00:00Z"
query_date2 = f"{compare_date1.isoformat()}T23:59:59Z"
logs1 = conn.execute(
"SELECT level, COUNT(*) as count FROM run_log WHERE ts BETWEEN ? AND ? GROUP BY level",
(query_date1, query_date2),
).fetchall()
query_date3 = f"{compare_date2.isoformat()}T00:00:00Z"
query_date4 = f"{compare_date2.isoformat()}T23:59:59Z"
logs2 = conn.execute(
"SELECT level, COUNT(*) as count FROM run_log WHERE ts BETWEEN ? AND ? GROUP BY level",
(query_date3, query_date4),
).fetchall()
df1 = pd.DataFrame(logs1, columns=["level", "count"])
df1["date"] = compare_date1.strftime("%Y-%m-%d")
df2 = pd.DataFrame(logs2, columns=["level", "count"])
df2["date"] = compare_date2.strftime("%Y-%m-%d")
for df in (df1, df2):
for col in df.columns:
if col != "level":
df[col] = df[col].astype(object)
compare_df = pd.concat([df1, df2])
fig = px.bar(
compare_df,
x="level",
y="count",
color="date",
barmode="group",
title=f"日志级别分布对比 ({compare_date1} vs {compare_date2})",
)
st.plotly_chart(fig, width='stretch')
st.write("日志统计对比:")
date1_str = compare_date1.strftime("%Y%m%d")
date2_str = compare_date2.strftime("%Y%m%d")
merged_df = df1.merge(
df2,
on="level",
suffixes=(f"_{date1_str}", f"_{date2_str}"),
how="outer",
).fillna(0)
st.dataframe(merged_df, hide_index=True, width="stretch")
except Exception as exc: # noqa: BLE001
LOGGER.exception("日志对比失败", extra=LOG_EXTRA)
st.error(f"日志对比分析失败:{exc}")
return