from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from tools.evaluate_qualitative_sell_strategy_accuracy_v1 import ( _scoreable_direction, build_accuracy_report, evaluate_decision, ) from src.quant_engine.qualitative_sell_strategy_store_v1 import insert_sell_strategy_result def test_scoreable_direction(): assert _scoreable_direction("EXIT_REVIEW_FULL") == -1 assert _scoreable_direction("TRIM_REVIEW_PARTIAL") == -1 assert _scoreable_direction("HOLD_ADD_CONVICTION") == 1 assert _scoreable_direction("HOLD_NO_CONFLUENCE") is None assert _scoreable_direction("INSUFFICIENT_DATA_NO_ACTION") is None def test_evaluate_decision_sell_success_when_price_drops(): decision = {"action": "EXIT_REVIEW_FULL"} result = evaluate_decision(decision, price_at_decision=100.0, price_after=90.0) assert result["success"] is True assert result["realized_return_pct"] == -10.0 def test_evaluate_decision_sell_failure_when_price_rises(): decision = {"action": "TRIM_REVIEW_PARTIAL"} result = evaluate_decision(decision, price_at_decision=100.0, price_after=110.0) assert result["success"] is False def test_evaluate_decision_hold_add_success_when_price_rises(): decision = {"action": "HOLD_ADD_CONVICTION"} result = evaluate_decision(decision, price_at_decision=100.0, price_after=105.0) assert result["success"] is True def test_evaluate_decision_returns_none_for_non_directional_action(): assert evaluate_decision({"action": "HOLD_NO_CONFLUENCE"}, 100.0, 105.0) is None def test_build_accuracy_report_data_gated_when_sample_too_small(tmp_path): db_path = tmp_path / "test.db" insert_sell_strategy_result(db_path, { "code": "005930", "generated_at": "2026-06-01T12:00:00", "decision": {"action": "EXIT_REVIEW_FULL"}, }) report = build_accuracy_report(db_path, price_lookup={ "005930": {"2026-06-01": 100.0, "2026-06-06": 90.0}, }) assert report["status"] == "DATA_GATED" assert report["scored_sample_count"] == 1 def test_build_accuracy_report_ok_with_enough_samples(tmp_path): db_path = tmp_path / "test.db" price_lookup: dict = {} for i in range(12): code = f"00000{i % 3}" gen_at = f"2026-05-{(i % 20) + 1:02d}T12:00:00" insert_sell_strategy_result(db_path, { "code": code, "generated_at": gen_at, "decision": {"action": "EXIT_REVIEW_FULL"}, }) date_key = gen_at[:10] future_key = ( __import__("datetime").date.fromisoformat(date_key) + __import__("datetime").timedelta(days=5) ).isoformat() price_lookup.setdefault(code, {})[date_key] = 100.0 price_lookup[code][future_key] = 90.0 # 매도신호 후 하락 — success report = build_accuracy_report(db_path, price_lookup) assert report["status"] == "OK" assert report["hit_rate_pct"] == 100.0 assert report["scored_sample_count"] == 12