"""Backtesting engine + learning-loop tests."""
from app.services.backtest import run_backtest, _stats
from app.services.learning import adjust_probability, MIN_SAMPLES, BLEND


def test_backtest_produces_consistent_stats():
    result = run_backtest(symbols=["TSLA", "NVDA", "AMD"], days=180, min_score=55)
    overall = result["overall"]
    assert overall["trades"] > 0, "expected at least one simulated trade"
    assert 0 <= overall["win_rate"] <= 100
    assert len(result["equity_curve_r"]) == overall["trades"]
    # equity curve final value equals total R
    assert abs(result["equity_curve_r"][-1] - overall["total_r"]) < 0.05
    assert overall["max_drawdown_r"] >= 0
    for row in result["per_symbol"]:
        assert row["symbol"] in ("TSLA", "NVDA", "AMD")


def test_backtest_no_lookahead_entry_after_signal():
    result = run_backtest(symbols=["META"], days=180, min_score=55)
    for t in result["recent_trades"]:
        assert t["outcome"] in ("stop", "target", "time")
        assert t["r"] is not None


def test_stats_math():
    trades = [{"r": 2.0}, {"r": -1.0}, {"r": 2.0}, {"r": -1.0}]
    s = _stats(trades)
    assert s["trades"] == 4
    assert s["win_rate"] == 50.0
    assert s["total_r"] == 2.0
    assert s["profit_factor"] == 2.0


def test_adjust_probability_blends_toward_actual():
    calibration = {
        "active": True,
        "samples": 20,
        "buckets": [
            {"range": [55, 62], "count": MIN_SAMPLES + 5, "actual_win_rate": 40.0},
        ],
    }
    adjusted = adjust_probability(58.0, calibration)
    expected = round(58.0 * (1 - BLEND) + 40.0 * BLEND, 1)
    assert adjusted == expected
    assert adjusted < 58.0  # model was overconfident; estimate moves down


def test_adjust_probability_ignores_small_buckets():
    calibration = {
        "active": True,
        "samples": 2,
        "buckets": [{"range": [55, 62], "count": 2, "actual_win_rate": 0.0}],
    }
    assert adjust_probability(58.0, calibration) == 58.0


def test_adjust_probability_no_calibration():
    assert adjust_probability(58.0, None) == 58.0
