Files
ai-exchange/tests/unit/test_historical_compatibility_service.py
T

439 lines
18 KiB
Python

import os
import unittest
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://user:pass@localhost:5432/ai_ict_test")
from src.services.maintenance import (
build_historical_compatibility_summary,
classify_liquidity_duplicate_groups,
derive_historical_fvg_meta,
derive_historical_mss_meta,
plan_liquidity_reference_migrations,
plan_fvg_meta_repairs,
plan_mss_meta_repairs,
plan_trade_signal_invalidation_repairs,
summarize_liquidity_duplicate_groups,
)
class HistoricalCompatibilityServiceTests(unittest.TestCase):
def test_plans_trade_signal_invalidation_repairs(self) -> None:
repairs = plan_trade_signal_invalidation_repairs(
[
{"id": 1, "invalidations": ["mss invalidated", "fvg fully mitigated"]},
{"id": 2, "invalidations": ["mss_invalidated"]},
]
)
self.assertEqual(repairs, [{"id": 1, "invalidations": ["mss_invalidated", "fvg_mitigated"]}])
def test_derives_historical_mss_meta(self) -> None:
repaired = derive_historical_mss_meta({"accepted": False, "meta": {"reclaimed_old_control": True}})
self.assertEqual(repaired["state"], "invalidated")
self.assertEqual(repaired["status_reason"], "old_control_reclaimed")
def test_plans_historical_fvg_meta_repairs(self) -> None:
repairs = plan_fvg_meta_repairs(
[
{"id": 3, "status": "mitigated", "meta": {}},
{"id": 4, "status": "open", "meta": {"status_reason": "no_touch", "invalidated": False, "has_data_gap": False, "gap_count": 0, "data_quality_warnings": []}},
]
)
self.assertEqual(len(repairs), 1)
self.assertEqual(repairs[0]["id"], 3)
self.assertEqual(repairs[0]["meta"]["status_reason"], "mitigation_threshold_reached")
self.assertFalse(repairs[0]["meta"]["invalidated"])
def test_summarizes_liquidity_duplicate_groups(self) -> None:
summary = summarize_liquidity_duplicate_groups(
[
{
"instrument_id": 1,
"timeframe": "1m",
"side": "buy_side",
"price_low": 100.0,
"price_high": 100.0,
"duplicate_count": 2,
"ids": [10, 9],
}
]
)
self.assertEqual(summary["group_count"], 1)
self.assertEqual(summary["row_count"], 2)
self.assertEqual(summary["groups"][0]["ids"], [10, 9])
def test_classifies_unreferenced_superset_pair_as_safe_auto_merge(self) -> None:
classified = classify_liquidity_duplicate_groups(
[
{
"instrument_id": 1,
"timeframe": "1m",
"side": "buy_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 9,
"pool_type": "equal_highs",
"reference_ids": [1, 2],
"created_ts": "2026-04-29T10:36:00+00:00",
"meta": {"merged_reference_count": 2},
"sweep_ts_list": [],
"sweep_count": 0,
},
{
"instrument_id": 1,
"timeframe": "1m",
"side": "buy_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 10,
"pool_type": "equal_highs",
"reference_ids": [1, 2, 3],
"created_ts": "2026-04-29T10:36:00+00:00",
"meta": {"merged_reference_count": 3},
"sweep_ts_list": [],
"sweep_count": 0,
},
]
)
self.assertEqual(classified["classification_counts"]["safe_auto_merge_unreferenced_superset"], 1)
self.assertEqual(classified["groups"][0]["classification"], "safe_auto_merge_unreferenced_superset")
self.assertEqual(classified["groups"][0]["canonical_pool_id"], 10)
self.assertEqual(classified["groups"][0]["remove_pool_ids"], [9])
def test_classifies_exact_duplicate_referenced_pair_as_reference_migration(self) -> None:
classified = classify_liquidity_duplicate_groups(
[
{
"instrument_id": 1,
"timeframe": "1m",
"side": "sell_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 11,
"pool_type": "prior_low",
"reference_ids": [7],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_events": [
{
"id": 100,
"sweep_ts": "2026-04-23T05:24:00+00:00",
"side_swept": "sell_side",
"sweep_high": "101.0",
"sweep_low": "99.0",
"close_back_inside": False,
"validation_level": "weak",
"status": "active",
"meta": {"pool_type": "prior_low"},
}
],
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
{
"instrument_id": 1,
"timeframe": "1m",
"side": "sell_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 12,
"pool_type": "prior_low",
"reference_ids": [7],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_events": [
{
"id": 101,
"sweep_ts": "2026-04-23T05:24:00+00:00",
"side_swept": "sell_side",
"sweep_high": "101.0",
"sweep_low": "99.0",
"close_back_inside": False,
"validation_level": "weak",
"status": "active",
"meta": {"pool_type": "prior_low"},
}
],
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
]
)
self.assertEqual(classified["classification_counts"]["safe_exact_duplicate_with_duplicate_sweeps"], 1)
self.assertEqual(classified["groups"][0]["classification"], "safe_exact_duplicate_with_duplicate_sweeps")
def test_classifies_nonidentical_referenced_pair_as_reference_migration(self) -> None:
classified = classify_liquidity_duplicate_groups(
[
{
"instrument_id": 1,
"timeframe": "1m",
"side": "sell_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 21,
"pool_type": "prior_low",
"reference_ids": [9],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_events": [
{
"id": 200,
"sweep_ts": "2026-04-23T05:24:00+00:00",
"side_swept": "sell_side",
"sweep_high": "101.0",
"sweep_low": "99.0",
"close_back_inside": False,
"validation_level": "weak",
"status": "active",
"meta": {"pool_type": "prior_low"},
}
],
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
{
"instrument_id": 1,
"timeframe": "1m",
"side": "sell_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 22,
"pool_type": "prior_low",
"reference_ids": [9],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_events": [
{
"id": 201,
"sweep_ts": "2026-04-23T05:24:00+00:00",
"side_swept": "sell_side",
"sweep_high": "102.0",
"sweep_low": "98.0",
"close_back_inside": False,
"validation_level": "weak",
"status": "active",
"meta": {"pool_type": "prior_low"},
}
],
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
]
)
self.assertEqual(classified["classification_counts"]["needs_sweep_reference_migration"], 1)
self.assertEqual(classified["groups"][0]["classification"], "needs_sweep_reference_migration")
def test_plans_executable_liquidity_reference_migration_when_sweep_timestamps_do_not_conflict(self) -> None:
duplicates = classify_liquidity_duplicate_groups(
[
{
"instrument_id": 1,
"timeframe": "1m",
"side": "sell_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 21,
"pool_type": "prior_low",
"reference_ids": [9],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_events": [
{
"id": 200,
"sweep_ts": "2026-04-23T05:24:00+00:00",
"side_swept": "sell_side",
"sweep_high": "101.0",
"sweep_low": "99.0",
"close_back_inside": False,
"validation_level": "weak",
"status": "active",
"meta": {"pool_type": "prior_low"},
}
],
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
{
"instrument_id": 1,
"timeframe": "1m",
"side": "sell_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 22,
"pool_type": "prior_low",
"reference_ids": [9],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_events": [
{
"id": 201,
"sweep_ts": "2026-04-23T05:26:00+00:00",
"side_swept": "sell_side",
"sweep_high": "102.0",
"sweep_low": "98.0",
"close_back_inside": False,
"validation_level": "weak",
"status": "active",
"meta": {"pool_type": "prior_low"},
}
],
"sweep_ts_list": ["2026-04-23T05:26:00+00:00"],
"sweep_count": 1,
},
]
)
plan = plan_liquidity_reference_migrations(duplicates)
self.assertEqual(plan["group_count"], 1)
self.assertEqual(plan["executable_group_count"], 1)
self.assertEqual(plan["blocked_group_count"], 0)
self.assertTrue(plan["groups"][0]["is_executable"])
self.assertEqual(plan["groups"][0]["actions"][0]["action"], "rebind_sweep_event")
self.assertEqual(plan["groups"][0]["remove_pool_ids"], [21])
def test_blocks_liquidity_reference_migration_when_sweep_timestamp_conflicts(self) -> None:
duplicates = classify_liquidity_duplicate_groups(
[
{
"instrument_id": 1,
"timeframe": "1m",
"side": "sell_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 21,
"pool_type": "prior_low",
"reference_ids": [9],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_events": [
{
"id": 200,
"sweep_ts": "2026-04-23T05:24:00+00:00",
"side_swept": "sell_side",
"sweep_high": "101.0",
"sweep_low": "99.0",
"close_back_inside": False,
"validation_level": "weak",
"status": "active",
"meta": {"pool_type": "prior_low"},
}
],
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
{
"instrument_id": 1,
"timeframe": "1m",
"side": "sell_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 22,
"pool_type": "prior_low",
"reference_ids": [9],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_events": [
{
"id": 201,
"sweep_ts": "2026-04-23T05:24:00+00:00",
"side_swept": "sell_side",
"sweep_high": "102.0",
"sweep_low": "98.0",
"close_back_inside": False,
"validation_level": "weak",
"status": "active",
"meta": {"pool_type": "prior_low"},
}
],
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
]
)
plan = plan_liquidity_reference_migrations(duplicates)
self.assertEqual(plan["group_count"], 1)
self.assertEqual(plan["executable_group_count"], 0)
self.assertEqual(plan["blocked_group_count"], 1)
self.assertFalse(plan["groups"][0]["is_executable"])
self.assertEqual(plan["groups"][0]["blocked_reason"], "sweep_ts_conflict")
self.assertEqual(plan["groups"][0]["conflicts"][0]["type"], "duplicate_sweep_ts_conflict")
def test_classifies_mixed_version_group_as_keep_history(self) -> None:
classified = classify_liquidity_duplicate_groups(
[
{
"instrument_id": 1,
"timeframe": "1m",
"side": "buy_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 13,
"pool_type": "prior_high",
"reference_ids": [1],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {},
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
{
"instrument_id": 1,
"timeframe": "1m",
"side": "buy_side",
"price_low": 100.0,
"price_high": 100.0,
"id": 14,
"pool_type": "equal_highs",
"reference_ids": [1, 2],
"created_ts": "2026-04-23T05:24:00+00:00",
"meta": {"merged_reference_count": 2},
"sweep_ts_list": ["2026-04-23T05:24:00+00:00"],
"sweep_count": 1,
},
]
)
self.assertEqual(classified["classification_counts"]["keep_version_history"], 1)
self.assertEqual(classified["groups"][0]["classification"], "keep_version_history")
def test_builds_historical_compatibility_summary(self) -> None:
summary = build_historical_compatibility_summary(
signal_repairs=[{"id": 1}],
mss_repairs=[{"id": 2}],
fvg_repairs=[],
liquidity_duplicates={
"group_count": 1,
"row_count": 3,
"classification_counts": {
"safe_auto_merge_unreferenced_superset": 1,
"needs_sweep_reference_migration": 0,
"keep_version_history": 0,
},
"groups": [{"ids": [9, 8, 7]}],
},
liquidity_reference_migrations={"group_count": 0, "executable_group_count": 0, "blocked_group_count": 0, "groups": []},
applied_repairs={"signal_invalidations": 1, "mss_meta": 1, "fvg_meta": 0, "liquidity_reference_migrations": 0},
dry_run=False,
)
self.assertTrue(summary["passed"])
self.assertFalse(summary["dry_run"])
self.assertEqual(summary["candidate_repairs"]["liquidity_duplicate_rows"], 3)
self.assertEqual(summary["candidate_repairs"]["liquidity_safe_auto_merge_groups"], 1)
self.assertEqual(summary["candidate_repairs"]["liquidity_reference_migration_executable_groups"], 0)
self.assertEqual(summary["applied_repairs"]["signal_invalidations"], 1)
self.assertEqual(summary["manual_review"]["liquidity_duplicates"][0]["ids"], [9, 8, 7])
if __name__ == "__main__":
unittest.main()