Support builtin and custom generate_signals strategies with SQLite persistence, exhaustive grid scans (VectorBT comb optimization for MA crossover), professional backtest/optimize UI, and split harvester/app requirements with BuildKit pip cache.
62 lines
1.6 KiB
Python
62 lines
1.6 KiB
Python
"""Portfolio metric helpers shared by engine and optimizers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import pandas as pd
|
|
import vectorbt as vbt
|
|
|
|
|
|
def safe_float(value: Any) -> float:
|
|
try:
|
|
if value is None or (isinstance(value, float) and value != value):
|
|
return 0.0
|
|
return float(value)
|
|
except (TypeError, ValueError):
|
|
return 0.0
|
|
|
|
|
|
def run_from_signals(
|
|
close: pd.Series,
|
|
entries: pd.Series,
|
|
exits: pd.Series,
|
|
init_cash: float,
|
|
fees: float,
|
|
params: dict[str, Any],
|
|
metric: str = "sharpe_ratio",
|
|
) -> dict[str, Any]:
|
|
portfolio = vbt.Portfolio.from_signals(
|
|
close,
|
|
entries=entries,
|
|
exits=exits,
|
|
init_cash=init_cash,
|
|
fees=fees,
|
|
freq="1D",
|
|
)
|
|
stats = portfolio.stats()
|
|
|
|
sharpe = safe_float(stats.get("Sharpe Ratio"))
|
|
sortino = safe_float(stats.get("Sortino Ratio"))
|
|
max_dd = safe_float(stats.get("Max Drawdown [%]")) / 100.0
|
|
total_return = safe_float(stats.get("Total Return [%]")) / 100.0
|
|
win_rate = safe_float(stats.get("Win Rate [%]")) / 100.0
|
|
total_trades = int(stats.get("Total Trades", 0) or 0)
|
|
|
|
score_map = {
|
|
"sharpe_ratio": sharpe,
|
|
"sortino_ratio": sortino,
|
|
"total_return": total_return,
|
|
"max_drawdown": -max_dd,
|
|
}
|
|
|
|
return {
|
|
**params,
|
|
"sharpe_ratio": sharpe,
|
|
"sortino_ratio": sortino,
|
|
"max_drawdown": max_dd,
|
|
"total_return": total_return,
|
|
"win_rate": win_rate,
|
|
"total_trades": total_trades,
|
|
"score": score_map.get(metric, sharpe),
|
|
}
|