diff --git a/tests/test_opportunity_evaluation_service.py b/tests/test_opportunity_evaluation_service.py new file mode 100644 index 0000000..c75113b --- /dev/null +++ b/tests/test_opportunity_evaluation_service.py @@ -0,0 +1,90 @@ +"""Opportunity historical evaluation tests.""" + +from __future__ import annotations + +import json +import tempfile +import unittest +from pathlib import Path + +from coinhunter.services import opportunity_evaluation_service + + +def _rows(start_ms: int, closes: list[float]) -> list[list[float]]: + rows = [] + for index, close in enumerate(closes): + open_time = start_ms + index * 3_600_000 + volume = 1_000 + index * 10 + rows.append( + [ + float(open_time), + close * 0.99, + close * 1.02, + close * 0.98, + close, + float(volume), + float(open_time + 3_599_999), + close * volume, + ] + ) + return rows + + +class OpportunityEvaluationServiceTestCase(unittest.TestCase): + def test_evaluate_opportunity_dataset_scores_historical_samples(self): + start_ms = 1_767_225_600_000 + dataset = { + "metadata": { + "plan": { + "intervals": ["1h"], + "simulation_start": "2026-01-01T04:00:00Z", + "simulation_end": "2026-01-01T07:00:00Z", + "simulate_days": 1, + } + }, + "klines": { + "GOODUSDT": {"1h": _rows(start_ms, [100, 101, 102, 103, 104, 106, 108, 109, 110])}, + "BADUSDT": {"1h": _rows(start_ms, [100, 99, 98, 97, 96, 95, 94, 93, 92])}, + }, + } + config = { + "market": {"default_quote": "USDT"}, + "opportunity": { + "entry_threshold": 1.5, + "watch_threshold": 0.6, + "evaluation_horizon_hours": 2.0, + "evaluation_take_profit_pct": 1.0, + "evaluation_stop_loss_pct": 2.0, + "evaluation_setup_target_pct": 0.5, + "evaluation_lookback": 4, + "top_n": 2, + }, + } + + with tempfile.TemporaryDirectory() as tmp_dir: + dataset_path = Path(tmp_dir) / "opportunity-dataset.json" + dataset_path.write_text(json.dumps(dataset), encoding="utf-8") + + payload = opportunity_evaluation_service.evaluate_opportunity_dataset( + config, + dataset_path=str(dataset_path), + horizon_hours=2.0, + take_profit=0.01, + stop_loss=0.02, + setup_target=0.005, + lookback=4, + top_n=2, + max_examples=3, + ) + + self.assertEqual(payload["summary"]["symbols"], ["BADUSDT", "GOODUSDT"]) + self.assertEqual(payload["summary"]["interval"], "1h") + self.assertGreater(payload["summary"]["count"], 0) + self.assertIn("by_action", payload) + self.assertIn("trade_simulation", payload) + self.assertEqual(payload["rules"]["research_mode"], "disabled: dataset has no point-in-time research snapshots") + self.assertLessEqual(len(payload["examples"]), 3) + + +if __name__ == "__main__": + unittest.main()