This commit is contained in:
2026-06-24 14:02:08 +08:00
parent 6494f43ddd
commit 363efd6d2d
9 changed files with 99 additions and 3221 deletions
+94 -1
View File
@@ -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: