update
This commit is contained in:
+94
-1
@@ -474,6 +474,7 @@ class _DrawerPanel:
|
||||
self._dataset_table = ft.ListView(expand=True)
|
||||
self._dataset_col = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True)
|
||||
self._scoring_table = ft.ListView(expand=True)
|
||||
self._backtest_col = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True)
|
||||
|
||||
bar = ft.TabBar(tabs=[
|
||||
ft.Tab(label="实时价格监控"),
|
||||
@@ -666,10 +667,15 @@ class _DrawerPanel:
|
||||
|
||||
def _build_dataset_tab(self) -> ft.Control:
|
||||
self._dataset_status = ft.Text("就绪", size=12, color='#888888')
|
||||
self._backtest_label = ft.Text("v6.7r3 回测曲线", size=11, color='#aaaaaa')
|
||||
self._backtest_img = ft.Container(visible=False) # 回测图表占位,运行时替换为 Image
|
||||
return ft.Column([
|
||||
self._dataset_status,
|
||||
self._dataset_col,
|
||||
], expand=True, spacing=6)
|
||||
ft.Divider(height=2, color='#444444'),
|
||||
self._backtest_label,
|
||||
self._backtest_img,
|
||||
], expand=True, spacing=6, scroll=ft.ScrollMode.AUTO)
|
||||
|
||||
def _run_sync(self, targets: list):
|
||||
# 线程锁
|
||||
@@ -712,6 +718,92 @@ class _DrawerPanel:
|
||||
|
||||
threading.Thread(target=_do, daemon=True).start()
|
||||
|
||||
def _refresh_backtest(self):
|
||||
"""加载 v6.7r3 回测图表,嵌入数据集 Tab"""
|
||||
import pandas as pd
|
||||
import io, base64, json
|
||||
import matplotlib
|
||||
matplotlib.use('Agg')
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates as mdates
|
||||
from pathlib import Path
|
||||
|
||||
model_dir = Path(__file__).parent.parent.parent.parent / 'models'
|
||||
daily_csv = model_dir / 'backtest_v67r3_daily.csv'
|
||||
summary_json = model_dir / 'backtest_v67r3_summary.json'
|
||||
|
||||
if not daily_csv.exists():
|
||||
self._backtest_label.value = "回测数据不存在"
|
||||
self._backtest_label.update()
|
||||
return
|
||||
|
||||
try:
|
||||
df_daily = pd.read_csv(daily_csv, parse_dates=['date'])
|
||||
summary = json.loads(summary_json.read_text()) if summary_json.exists() else {}
|
||||
|
||||
# 更新标题行
|
||||
start_val = summary.get('initial_cash', 60000)
|
||||
final_val = summary.get('final_total_value', 0)
|
||||
self._backtest_label.value = (f"v6.7r3 回测 初始:{start_val:,.0f} → 终值:{final_val:,.0f} "
|
||||
f"收益:{summary.get('total_return_pct',0):.1f}% "
|
||||
f"夏普:{summary.get('annual_sharpe',0):.2f} "
|
||||
f"回撤:{summary.get('max_drawdown_pct',0):.1f}%")
|
||||
self._backtest_label.update()
|
||||
|
||||
# 渲染总资产曲线
|
||||
import matplotlib.font_manager as mfont
|
||||
# 查找中文字体
|
||||
cjk_names = ['Microsoft YaHei', 'SimHei', 'Noto Sans SC', 'WenQuanYi']
|
||||
cjk_font = next((f.fname for f in mfont.fontManager.ttflist
|
||||
if any(n in f.name for n in cjk_names)), None)
|
||||
if cjk_font:
|
||||
prop = mfont.FontProperties(fname=cjk_font)
|
||||
plt.rcParams['font.family'] = prop.get_name()
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
fig, ax = plt.subplots(figsize=(7, 3.5), dpi=100)
|
||||
fig.patch.set_facecolor('#1e1e1e')
|
||||
ax.set_facecolor('#2d2d2d')
|
||||
ax.plot(df_daily['date'], df_daily['total_asset'], color='#4CAF50', linewidth=1.5)
|
||||
ax.fill_between(df_daily['date'], df_daily['total_asset'], alpha=0.1, color='#4CAF50')
|
||||
ax.set_title('总资产曲线 (v6.7r3)', color='#ffffff', fontsize=10)
|
||||
ax.set_ylabel('元', color='#cccccc', fontsize=9)
|
||||
ax.tick_params(colors='#cccccc', labelsize=8)
|
||||
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
|
||||
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=3))
|
||||
plt.setp(ax.xaxis.get_majorticklabels(), rotation=30, ha='right')
|
||||
for spine in ax.spines.values():
|
||||
spine.set_color('#555555')
|
||||
ax.grid(True, alpha=0.15, color='#888888')
|
||||
# 标注起点终点
|
||||
ax.annotate(f'{start_val:,.0f}', xy=(df_daily['date'].iloc[0], start_val),
|
||||
xytext=(3, 5), textcoords='offset points', color='#aaaaaa', fontsize=8)
|
||||
ax.annotate(f'{final_val:,.0f}', xy=(df_daily['date'].iloc[-1], final_val),
|
||||
xytext=(3, 5), textcoords='offset points', color='#4CAF50', fontsize=8, fontweight='bold')
|
||||
plt.tight_layout()
|
||||
buf = io.BytesIO()
|
||||
fig.savefig(buf, format='png', bbox_inches='tight', facecolor=fig.get_facecolor())
|
||||
buf.seek(0)
|
||||
img_b64 = base64.b64encode(buf.read()).decode()
|
||||
plt.close(fig)
|
||||
|
||||
# 用实际 Image 替换占位的 Container
|
||||
new_img = ft.Image(
|
||||
src=f'data:image/png;base64,{img_b64}',
|
||||
fit=ft.BoxFit.CONTAIN,
|
||||
height=240,
|
||||
visible=True,
|
||||
)
|
||||
# 找到 placeholder Container 在 Column 中的位置,替换
|
||||
idx = self._backtest_label.parent.controls.index(self._backtest_img)
|
||||
self._backtest_label.parent.controls[idx] = new_img
|
||||
self._backtest_img = new_img
|
||||
self._backtest_label.parent.update()
|
||||
|
||||
except Exception as ex:
|
||||
self._backtest_label.value = f"图表加载失败: {ex}"
|
||||
self._backtest_label.color = '#F44336'
|
||||
self._backtest_label.update()
|
||||
|
||||
def _set_sync_btns_disabled(self, disabled: bool):
|
||||
for btn in self._sync_btns:
|
||||
btn.disabled = disabled
|
||||
@@ -776,6 +868,7 @@ class _DrawerPanel:
|
||||
rows.append(ft.Divider(height=1, color='#f0f0f0'))
|
||||
|
||||
self._dataset_col.controls = rows
|
||||
self._refresh_backtest()
|
||||
|
||||
# ── Tab 6: 每日评分 ──
|
||||
def _build_scoring_tab(self) -> ft.Control:
|
||||
|
||||
Reference in New Issue
Block a user