"""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), }