feat: add opportunity historical evaluation

This commit is contained in:
Carlos Ouyang
2026-04-22 14:15:24 +08:00
parent 003212de99
commit 10b314aa2b

View File

@@ -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()