mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 13:24:18 +08:00
feat: 参数寻优新增买入持有基准对比 — 两个寻优接口返回同口径 buy_hold(run_buy_hold_benchmark),全局最佳/最优结果总收益旁展示 [买入持有:xx.xx%],涨红跌绿
This commit is contained in:
@@ -360,6 +360,9 @@ class OptimizeAllResult(BaseModel):
|
||||
best: OptimizeAllRankEntry | None = None
|
||||
per_strategy: dict[str, OptimizeAllRankEntry] = {} # 策略名 → 最优点
|
||||
total_grid_points: int = 0 # 所有策略网格点合计
|
||||
# 买入持有基准(同区间/同费率/同资金;含 total_return 等 6 项指标),
|
||||
# 供前端在全局最佳旁直观对比「策略 vs 买入不动」。老版本结果可能缺省。
|
||||
buy_hold: dict[str, Any] | None = None
|
||||
|
||||
|
||||
# ── 已保存策略(策略库 / StrategyLibrary)───────────────────────────────────────
|
||||
|
||||
@@ -1184,6 +1184,7 @@ def _run_multi_strategy_evaluate(
|
||||
|
||||
def _run_optimize(df: pd.DataFrame, req: OptimizeBacktestRequest) -> dict[str, Any]:
|
||||
"""执行参数网格寻优并返回清洗后的结果字典(后台线程内调用)。"""
|
||||
from easy_tdx.backtest.benchmark import run_buy_hold_benchmark
|
||||
from easy_tdx.backtest.optimizer import ParamGridOptimizer
|
||||
|
||||
optimizer = ParamGridOptimizer(
|
||||
@@ -1197,7 +1198,17 @@ def _run_optimize(df: pd.DataFrame, req: OptimizeBacktestRequest) -> dict[str, A
|
||||
workers=req.workers,
|
||||
)
|
||||
result = optimizer.run()
|
||||
return result.to_dict()
|
||||
out = result.to_dict()
|
||||
# 买入持有基准(同区间/同费率/同资金,与一条龙评估同口径),
|
||||
# 供前端在最优结果旁直观对比「策略 vs 买入不动」。
|
||||
out["buy_hold"] = run_buy_hold_benchmark(
|
||||
df,
|
||||
cash=req.cash,
|
||||
commission=req.commission,
|
||||
slippage=req.slippage,
|
||||
execution=req.execution,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def _optimize_one_strategy(
|
||||
@@ -1263,6 +1274,7 @@ def _run_optimize_all(df: pd.DataFrame, req: OptimizeAllBacktestRequest) -> dict
|
||||
``workers`` 为 0 或 1 时串行。进程池在函数内 ``with`` 创建/销毁,对前端
|
||||
轮询与 task_runner 透明。
|
||||
"""
|
||||
from easy_tdx.backtest.benchmark import run_buy_hold_benchmark
|
||||
from easy_tdx.backtest.strategies import get_registry
|
||||
from easy_tdx.backtest.strategies.presets import STRATEGY_PRESETS
|
||||
|
||||
@@ -1338,6 +1350,14 @@ def _run_optimize_all(df: pd.DataFrame, req: OptimizeAllBacktestRequest) -> dict
|
||||
best=best,
|
||||
per_strategy=per_strategy,
|
||||
total_grid_points=total_grid,
|
||||
# 买入持有基准(同区间/同费率/同资金),供前端在全局最佳旁直观对比
|
||||
buy_hold=run_buy_hold_benchmark(
|
||||
df,
|
||||
cash=req.cash,
|
||||
commission=req.commission,
|
||||
slippage=req.slippage,
|
||||
execution=req.execution,
|
||||
),
|
||||
)
|
||||
return result_obj.model_dump()
|
||||
|
||||
|
||||
@@ -257,12 +257,24 @@ export interface OptimizeHeatmap {
|
||||
data: Array<[number, number, number | null]>
|
||||
}
|
||||
|
||||
/** 买入持有基准(同区间/同费率,首根买入持有到末根,与一条龙评估同口径) */
|
||||
export interface BuyHoldBenchmark {
|
||||
total_return: number
|
||||
annual_return: number
|
||||
max_drawdown: number
|
||||
sharpe: number
|
||||
calmar: number
|
||||
volatility: number
|
||||
}
|
||||
|
||||
export interface OptimizeResult {
|
||||
strategy: string
|
||||
param_names: string[]
|
||||
results: GridPointResult[]
|
||||
best: GridPointResult | null
|
||||
heatmap: OptimizeHeatmap | null
|
||||
/** 买入持有基准,供直观对比「策略 vs 买入不动」(老版本后端结果可能缺省) */
|
||||
buy_hold?: BuyHoldBenchmark | null
|
||||
}
|
||||
|
||||
// ── 一键寻优所有策略(Phase 6) ──────────────────────────────────────────────
|
||||
@@ -299,6 +311,8 @@ export interface OptimizeAllResult {
|
||||
best: OptimizeAllRankEntry | null
|
||||
per_strategy: Record<string, OptimizeAllRankEntry>
|
||||
total_grid_points: number
|
||||
/** 买入持有基准,供直观对比「策略 vs 买入不动」(老版本后端结果可能缺省) */
|
||||
buy_hold?: BuyHoldBenchmark | null
|
||||
}
|
||||
|
||||
// ── 错误响应(后端 ApiErrorResponse) ─────────────────────────────────────────
|
||||
|
||||
@@ -196,6 +196,10 @@ function onViewAll(strategyName: string, params: Record<string, number | string>
|
||||
function pct(v: number | null | undefined): string {
|
||||
return v !== null && v !== undefined && Number.isFinite(v) ? `${(v * 100).toFixed(2)}%` : '-'
|
||||
}
|
||||
/** 买入持有的盈亏配色(A 股习惯:涨红跌绿;非数值不加色) */
|
||||
function bhClass(v: number | null | undefined): string {
|
||||
return v !== null && v !== undefined && Number.isFinite(v) ? (v >= 0 ? 'pos' : 'neg') : ''
|
||||
}
|
||||
function num(v: number | null | undefined, d = 2): string {
|
||||
return v !== null && v !== undefined && Number.isFinite(v) ? v.toFixed(d) : '-'
|
||||
}
|
||||
@@ -316,6 +320,11 @@ const rankingGrades = computed<GradeResult[]>(() =>
|
||||
<span class="best-return pos">
|
||||
{{ (store.optimizeResult.best.total_return! * 100).toFixed(2) }}%
|
||||
</span>
|
||||
<span v-if="store.optimizeResult.buy_hold" class="best-bh">
|
||||
[买入持有:<span :class="bhClass(store.optimizeResult.buy_hold.total_return)">{{
|
||||
pct(store.optimizeResult.buy_hold.total_return)
|
||||
}}</span>]
|
||||
</span>
|
||||
<span class="best-meta">
|
||||
夏普 {{ store.optimizeResult.best.sharpe?.toFixed(2) }} · 回撤
|
||||
{{ (store.optimizeResult.best.max_drawdown! * 100).toFixed(2) }}%
|
||||
@@ -351,6 +360,11 @@ const rankingGrades = computed<GradeResult[]>(() =>
|
||||
<span class="best-return pos">
|
||||
{{ (store.optimizeAllResult.best.total_return! * 100).toFixed(2) }}%
|
||||
</span>
|
||||
<span v-if="store.optimizeAllResult.buy_hold" class="best-bh">
|
||||
[买入持有:<span :class="bhClass(store.optimizeAllResult.buy_hold.total_return)">{{
|
||||
pct(store.optimizeAllResult.buy_hold.total_return)
|
||||
}}</span>]
|
||||
</span>
|
||||
<span class="best-meta">
|
||||
夏普 {{ store.optimizeAllResult.best.sharpe?.toFixed(2) }} · 回撤
|
||||
{{ (store.optimizeAllResult.best.max_drawdown! * 100).toFixed(2) }}% · 胜率
|
||||
@@ -542,6 +556,12 @@ const rankingGrades = computed<GradeResult[]>(() =>
|
||||
font-weight: 700;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
/* 买入持有对比项:标签/括号弱化不抢焦点,百分比按盈亏走 .pos/.neg 红绿 */
|
||||
.best-bh {
|
||||
color: var(--text-dim);
|
||||
font-size: 13px;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.best-meta {
|
||||
color: var(--text-dim);
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user