180 lines
6.2 KiB
Python
180 lines
6.2 KiB
Python
"""Bounded optimizer runs actual core strategies and rejects ambiguous ranking."""
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from quant_engine import strategy_optimizer as optimizer
|
|
from quant_engine.strategy_contracts import STRATEGIES, strategy_parameters
|
|
from quant_engine.strategy_research import BenchmarkInput, run_strategy_research
|
|
from quant_engine import strategy_contracts
|
|
|
|
|
|
def feed(values=None):
|
|
values = np.asarray(
|
|
values if values is not None else 10 + 2 * np.sin(np.arange(80) / 2), dtype=float
|
|
)
|
|
return pd.DataFrame(
|
|
dict.fromkeys(("open", "high", "low", "close"), values),
|
|
index=pd.date_range("2026-01-01", periods=len(values), freq="B"),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("name", STRATEGIES)
|
|
def test_each_default_strategy_uses_the_real_ledger(name):
|
|
defaults = strategy_parameters(name)
|
|
key = next(iter(defaults))
|
|
result = optimizer.optimize_strategy_research(
|
|
name,
|
|
feed(),
|
|
asset="SYNTHETIC",
|
|
param_grid={key: [defaults[key]]},
|
|
objective="total_return",
|
|
initial_cash=1000,
|
|
commission=0.01,
|
|
stamp_duty=0.002,
|
|
)
|
|
direct = run_strategy_research(
|
|
name, feed(), asset="SYNTHETIC", initial_cash=1000, commission=0.01, stamp_duty=0.002
|
|
)
|
|
assert len(result.trials) == 1
|
|
assert result.trials[0].result.ledger.positions == direct.ledger.positions
|
|
assert result.trials[0].result.ledger.daily_executions == direct.ledger.daily_executions
|
|
assert result.trials[0].score == direct.metrics["total_return"]
|
|
assert result.decision_eligible is False
|
|
|
|
|
|
def test_real_negative_zero_scores_and_explicit_costs_sort_without_defaults():
|
|
result = optimizer.optimize_strategy_research(
|
|
"BuyAndHold",
|
|
feed([10, 10, 9, 8]),
|
|
asset="SYNTHETIC",
|
|
param_grid={"buy_pct": [0.5, 0, 0.25]},
|
|
objective="total_return",
|
|
initial_cash=1000,
|
|
commission=0.01,
|
|
stamp_duty=0.002,
|
|
)
|
|
assert [trial.parameters["buy_pct"] for trial in result.trials] == [0, 0.25, 0.5]
|
|
assert [trial.score for trial in result.trials] == pytest.approx([0, -0.0525, -0.105])
|
|
assert [trial.result.ledger.total_costs for trial in result.trials] == pytest.approx(
|
|
[0, 2.5, 5]
|
|
)
|
|
|
|
|
|
def test_stable_ties_retain_canonical_axis_and_candidate_order():
|
|
result = optimizer.optimize_strategy_research(
|
|
"MACross",
|
|
feed([10] * 40),
|
|
asset="SYNTHETIC",
|
|
param_grid={"atr_period": [2, 0], "fast": [5, 4]},
|
|
objective="total_return",
|
|
commission=0,
|
|
stamp_duty=0,
|
|
)
|
|
assert [
|
|
(trial.parameters["fast"], trial.parameters["atr_period"]) for trial in result.trials
|
|
] == [(5, 2), (5, 0), (4, 2), (4, 0)]
|
|
|
|
|
|
def test_hundred_combinations_are_unique_actual_results():
|
|
result = optimizer.optimize_strategy_research(
|
|
"MACross",
|
|
feed(),
|
|
asset="SYNTHETIC",
|
|
param_grid={"fast": list(range(1, 11)), "slow": list(range(11, 21))},
|
|
objective="total_return",
|
|
commission=0,
|
|
stamp_duty=0,
|
|
)
|
|
assert len(result.trials) == 100
|
|
assert len({tuple(trial.parameters.items()) for trial in result.trials}) == 100
|
|
assert all(len(trial.result.ledger.positions) == 80 for trial in result.trials)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"grid,objective",
|
|
[
|
|
({"fast": []}, "total_return"),
|
|
({"fast": [5, 40]}, "total_return"),
|
|
({"fast": [True]}, "total_return"),
|
|
({"fast": [5]}, "unknown"),
|
|
({"fast": [5, 5]}, "total_return"),
|
|
(
|
|
{"fast": list(range(1, 11)), "slow": list(range(11, 21)), "atr_period": [0, 1]},
|
|
"total_return",
|
|
),
|
|
],
|
|
)
|
|
def test_invalid_or_partly_invalid_grid_never_starts_a_strategy(monkeypatch, grid, objective):
|
|
calls = []
|
|
monkeypatch.setattr(optimizer, "run_strategy_research", lambda *a, **kw: calls.append(1))
|
|
with pytest.raises(
|
|
ValueError, match=r"grid|Grid|candidate|number|less than|objective|Duplicate"
|
|
):
|
|
optimizer.optimize_strategy_research(
|
|
"MACross", feed(), asset="SYNTHETIC", param_grid=grid, objective=objective
|
|
)
|
|
assert calls == []
|
|
|
|
|
|
def test_history_failure_for_one_candidate_prevents_all_runs(monkeypatch):
|
|
calls = []
|
|
monkeypatch.setattr(optimizer, "run_strategy_research", lambda *a, **kw: calls.append(1))
|
|
with pytest.raises(ValueError, match=r"history"):
|
|
optimizer.optimize_strategy_research(
|
|
"MACross",
|
|
feed([10] * 40),
|
|
asset="SYNTHETIC",
|
|
param_grid={"slow": [30, 100]},
|
|
objective="total_return",
|
|
)
|
|
assert calls == []
|
|
|
|
|
|
def test_requested_benchmark_error_prevents_every_run(monkeypatch):
|
|
calls = []
|
|
monkeypatch.setattr(optimizer, "run_strategy_research", lambda *a, **kw: calls.append(1))
|
|
with pytest.raises(ValueError, match=r"benchmark source"):
|
|
optimizer.optimize_strategy_research(
|
|
"BuyAndHold",
|
|
feed(),
|
|
asset="SYNTHETIC",
|
|
param_grid={"buy_pct": [0.5, 1]},
|
|
objective="total_return",
|
|
benchmark=BenchmarkInput("source_error"),
|
|
)
|
|
assert calls == []
|
|
|
|
|
|
def test_undefined_sharpe_fails_the_ranking_instead_of_winning_as_zero():
|
|
with pytest.raises(ValueError, match=r"objective.*unavailable"):
|
|
optimizer.optimize_strategy_research(
|
|
"BuyAndHold",
|
|
feed([10] * 4),
|
|
asset="SYNTHETIC",
|
|
param_grid={"buy_pct": [0, 0.5]},
|
|
objective="sharpe_ratio",
|
|
commission=0,
|
|
stamp_duty=0,
|
|
)
|
|
|
|
|
|
def test_large_grid_fails_before_creating_cartesian_product(monkeypatch):
|
|
calls = []
|
|
monkeypatch.setattr(strategy_contracts, "product", lambda *a, **kw: calls.append("product"))
|
|
monkeypatch.setattr(optimizer, "run_strategy_research", lambda *a, **kw: calls.append("run"))
|
|
with pytest.raises(ValueError, match=r"limit"):
|
|
optimizer.optimize_strategy_research(
|
|
"MACross",
|
|
feed(),
|
|
asset="SYNTHETIC",
|
|
param_grid={
|
|
"fast": list(range(1, 11)),
|
|
"slow": list(range(11, 21)),
|
|
"atr_period": list(range(10)),
|
|
"atr_mult": list(range(1, 11)),
|
|
},
|
|
)
|
|
assert calls == []
|