Files
ai-exchange/tests/unit/test_report_services.py

93 lines
4.1 KiB
Python

import contextlib
import io
import json
import os
import unittest
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://user:pass@localhost:5432/ai_ict_test")
from src.services.reports import IntradayReportService, PostTradeReviewService, PreMarketReportService
from scripts import run_reports
class PreMarketReportTests(unittest.TestCase):
def test_builds_pre_market_plan(self) -> None:
report = PreMarketReportService().build_report(
bias_snapshot={
"daily_bias": "bullish",
"draw_side": "buy_side",
"execution_intensity": "high",
"reason_codes": ["latest_mss", "active_range_available"],
},
dealing_range={"id": 1, "high": 110.0, "low": 90.0, "equilibrium": 100.0},
current_price=98.0,
session_code="LONDON",
)
self.assertEqual(report["report_type"], "pre_market_plan")
self.assertEqual(report["daily_bias"], "bullish")
self.assertEqual(report["working_range"]["location"], "discount")
self.assertTrue(report["execution_permission"])
class IntradayReportTests(unittest.TestCase):
def test_builds_intraday_explanation_and_invalidations(self) -> None:
report = IntradayReportService().build_report(
sweep_event={"side_swept": "buy_side", "close_back_inside": True},
structure_event={"event_type": "MSS", "direction": "bearish", "accepted": True},
fvg_zone={"direction": "bearish", "status": "mitigated", "fill_pct": 1.0, "meta": {}},
trade_signal={"status": "awaiting_confirmation", "side": "sell", "session_code": "NY_AM", "invalidations": ["session_mismatch"]},
)
self.assertEqual(report["report_type"], "intraday_explanation")
self.assertFalse(report["signal"]["is_valid"])
self.assertIn("fvg_no_longer_actionable", report["invalidations"])
self.assertIn("session_mismatch", report["invalidations"])
class PostTradeReviewTests(unittest.TestCase):
def test_builds_post_trade_review(self) -> None:
report = PostTradeReviewService().build_report(
trade_execution={"status": "rejected", "meta": {"risk_checks": ["session_not_allowed"]}},
trade_signal={"status": "awaiting_context", "invalidations": ["session_mismatch"]},
backtest_result={"outcome": "loss", "invalidated_before_entry": False, "error_tags": ["same_candle_entry_exit_ambiguous"]},
)
self.assertEqual(report["report_type"], "post_trade_review")
self.assertFalse(report["rule_aligned"])
self.assertIn("execution_rejected", report["error_types"])
self.assertIn("session_not_allowed", report["error_types"])
self.assertEqual(report["execution_audit"]["audit_status"], "review")
self.assertEqual(report["execution_audit"]["lifecycle_state"], "rejected")
self.assertIn("inspect_stop_and_structure_context", report["improvement_notes"])
self.assertIn("review_same_candle_ambiguity_cases", report["improvement_notes"])
class ReportScriptTests(unittest.TestCase):
def test_run_reports_prints_backtest_quality_gate(self) -> None:
output = io.StringIO()
with contextlib.redirect_stdout(output):
exit_code = run_reports.main()
self.assertEqual(exit_code, 0)
self.assertIn("Backtest quality gate passed: False", output.getvalue())
def test_run_reports_outputs_json_summary(self) -> None:
output = io.StringIO()
with contextlib.redirect_stdout(output):
exit_code = run_reports.main(env={"REPORTS_OUTPUT_JSON": "1"})
payload = json.loads(output.getvalue())
self.assertEqual(exit_code, 0)
self.assertTrue(payload["passed"])
self.assertEqual(payload["pre_market"]["report_type"], "pre_market_plan")
self.assertEqual(payload["intraday"]["report_type"], "intraday_explanation")
self.assertEqual(payload["post_trade"]["report_type"], "post_trade_review")
self.assertIn("quality_gate", payload["backtest_summary"])
if __name__ == "__main__":
unittest.main()