Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/crypto_strategies/backtest/orchestrator_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def _metrics_to_result(
raise ImportError("quant_platform_kit is required to build BacktestResult")
cagr = float(metrics.get("CAGR") or 0.0)
max_drawdown = float(metrics.get("Max Drawdown") or 0.0)
calmar = abs(cagr / max_drawdown) if max_drawdown else None
calmar = cagr / abs(max_drawdown) if max_drawdown else None
return BacktestResult(
strategy_profile=strategy_profile,
domain="crypto",
Expand Down
17 changes: 17 additions & 0 deletions tests/test_orchestrator_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
SUPPORTED_PROFILES,
CryptoEquityComboBacktestRunner,
CryptoLivePoolBacktestRunner,
_metrics_to_result,
build_backtest_runner,
)
from crypto_strategies.strategies.crypto_equity_combo import PROFILE_NAME as CRYPTO_EQUITY_COMBO_PROFILE
Expand Down Expand Up @@ -127,6 +128,22 @@ def test_walk_forward_combo_profile(self) -> None:
class AccountingMetricsRegressionTests(unittest.TestCase):
"""QSL-20260906-006 / 007: initial NAV drawdown + fee-constrained share ledger."""

def test_calmar_keeps_cagr_sign_for_live_pool_and_combo(self) -> None:
cases = ((-0.1, -0.2, -0.5), (0.1, -0.2, 0.5), (0.0, -0.2, 0.0), (0.1, 0.0, None))
for profile in (PROFILE_NAME, CRYPTO_EQUITY_COMBO_PROFILE):
for cagr, max_drawdown, expected in cases:
with self.subTest(profile=profile, cagr=cagr, max_drawdown=max_drawdown):
result = _metrics_to_result(
strategy_profile=profile,
params={},
metrics={"CAGR": cagr, "Max Drawdown": max_drawdown},
start_date=date(2024, 1, 1),
end_date=date(2024, 12, 31),
run_duration_seconds=0.0,
)
self.assertEqual(result.calmar_ratio, expected)
self.assertIsNone(result.validation_identity)

def test_max_drawdown_includes_initial_nav(self) -> None:
from crypto_strategies.backtest.live_pool_simulator import _performance_metrics

Expand Down