import importlib.util
import sys
import unittest
from pathlib import Path

import numpy as np
import pandas as pd


MODULE_PATH = (
    Path(__file__).resolve().parents[2]
    / "strategies"
    / "research"
    / "ewmac_1h_math.py"
)
SPEC = importlib.util.spec_from_file_location("ewmac_1h_math", MODULE_PATH)
ewmac_math = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = ewmac_math
SPEC.loader.exec_module(ewmac_math)


class MultiSpeedEwmacTest(unittest.TestCase):
    def test_uptrend_and_downtrend_have_symmetric_direction(self):
        rng = np.random.default_rng(17)
        up_returns = rng.normal(0.002, 0.004, 600)
        up = pd.Series(100 * np.exp(np.cumsum(up_returns)))
        down = pd.Series(100 * np.exp(np.cumsum(-up_returns)))
        up_forecast = ewmac_math.multi_speed_ewmac(up)["ewmac_forecast"].iloc[-1]
        down_forecast = ewmac_math.multi_speed_ewmac(down)["ewmac_forecast"].iloc[-1]
        self.assertGreater(up_forecast, 0)
        self.assertLess(down_forecast, 0)

    def test_each_component_is_capped_before_combination(self):
        prices = pd.Series(100 * np.exp(np.arange(600) * 0.01))
        result = ewmac_math.multi_speed_ewmac(prices, forecast_cap=7)
        for fast, slow, _ in ewmac_math.EWMAC_SPEEDS:
            self.assertLessEqual(result[f"ewmac_{fast}_{slow}"].abs().max(), 7)
        self.assertLessEqual(result["ewmac_forecast"].abs().max(), 7)

    def test_hysteresis_uses_crossings_not_persistent_levels(self):
        forecast = pd.Series([0.0, 4.9, 5.1, 6.0, 4.0, 0.1, -0.1, -5.1, -6.0, 0.1])
        signals = ewmac_math.hysteresis_signals(forecast)
        self.assertEqual(signals.index[signals["enter_long"]].tolist(), [2])
        self.assertEqual(signals.index[signals["exit_long"]].tolist(), [6])
        self.assertEqual(signals.index[signals["enter_short"]].tolist(), [7])
        self.assertEqual(signals.index[signals["exit_short"]].tolist(), [9])

    def test_warmup_requires_slowest_component_and_volatility(self):
        prices = pd.Series(np.linspace(100, 150, 130))
        result = ewmac_math.multi_speed_ewmac(prices)
        self.assertTrue(result["ewmac_forecast"].iloc[:127].isna().all())
        self.assertTrue(result["ewmac_forecast"].iloc[-1] == result["ewmac_forecast"].iloc[-1])

    def test_invalid_prices_fail_closed(self):
        with self.assertRaises(ValueError):
            ewmac_math.multi_speed_ewmac(pd.Series([100.0, 0.0, 101.0]))


if __name__ == "__main__":
    unittest.main()
