dragon_rule_catalog.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from __future__ import annotations
  2. from dataclasses import dataclass
  3. from dragon_reason_types import DecisionReason, RuleFamily, RuleLayer
  4. @dataclass(frozen=True)
  5. class _RulePattern:
  6. match_type: str # exact or prefix
  7. pattern: str
  8. layer: RuleLayer
  9. family: RuleFamily
  10. code: str
  11. tags: tuple[str, ...] = ()
  12. def matches(self, legacy_reason: str) -> bool:
  13. if self.match_type == "exact":
  14. return legacy_reason == self.pattern
  15. if self.match_type == "prefix":
  16. return legacy_reason.startswith(self.pattern)
  17. raise ValueError(f"Unknown match_type: {self.match_type}")
  18. ENTRY_PATTERNS: tuple[_RulePattern, ...] = (
  19. _RulePattern("exact", "glued_buy", RuleLayer.CORE, RuleFamily.GLUED, "entry_glued_buy"),
  20. _RulePattern("exact", "early_crash_probe_buy", RuleLayer.CORE, RuleFamily.EARLY_CRASH, "entry_early_crash_probe"),
  21. _RulePattern("exact", "oversold_recovery_buy", RuleLayer.CORE, RuleFamily.OVERSOLD_RECOVERY, "entry_oversold_recovery"),
  22. _RulePattern("exact", "dual_gold_resonance_buy", RuleLayer.SECONDARY, RuleFamily.DUAL_GOLD, "entry_dual_gold"),
  23. _RulePattern("prefix", "deep_oversold_rebound_buy", RuleLayer.SECONDARY, RuleFamily.DEEP_OVERSOLD, "entry_deep_oversold"),
  24. _RulePattern("exact", "post_sell_rebound_buy", RuleLayer.SECONDARY, RuleFamily.POST_SELL_REBOUND, "entry_post_sell_rebound"),
  25. _RulePattern("exact", "oversold_reversal_after_ql_buy", RuleLayer.SECONDARY, RuleFamily.OVERSOLD_RECOVERY, "entry_oversold_reversal_after_ql"),
  26. _RulePattern("prefix", "glued_followthrough_reentry_buy", RuleLayer.BRIDGE, RuleFamily.GLUED, "entry_glued_followthrough_reentry"),
  27. _RulePattern("exact", "hot_exit_reentry_buy", RuleLayer.BRIDGE, RuleFamily.BRIDGE_REENTRY, "entry_hot_exit_reentry"),
  28. _RulePattern("exact", "predictive_error_reentry_buy", RuleLayer.BRIDGE, RuleFamily.PREDICTIVE_BREAK, "entry_predictive_error_reentry"),
  29. _RulePattern("exact", "post_washout_kdj_reentry_buy", RuleLayer.BRIDGE, RuleFamily.BRIDGE_REENTRY, "entry_post_washout_reentry"),
  30. _RulePattern("exact", "knife_catch_1", RuleLayer.SECONDARY, RuleFamily.DEEP_OVERSOLD, "entry_knife_catch_1"),
  31. _RulePattern("exact", "knife_catch_2", RuleLayer.SECONDARY, RuleFamily.DEEP_OVERSOLD, "entry_knife_catch_2"),
  32. )
  33. EXIT_PATTERNS: tuple[_RulePattern, ...] = (
  34. _RulePattern("exact", "predictive_b1_break_exit", RuleLayer.BRIDGE, RuleFamily.PREDICTIVE_BREAK, "exit_predictive_b1_break"),
  35. _RulePattern("exact", "post_ql_decay_exit", RuleLayer.BRIDGE, RuleFamily.PREDICTIVE_BREAK, "exit_post_ql_decay"),
  36. _RulePattern("exact", "crash_protection_exit", RuleLayer.CORE, RuleFamily.RISK_MANAGEMENT, "exit_crash_protection"),
  37. _RulePattern("exact", "prewarning_reduction_exit", RuleLayer.CORE, RuleFamily.RISK_MANAGEMENT, "exit_prewarning_reduction"),
  38. _RulePattern("exact", "medium_hot_take_profit", RuleLayer.CORE, RuleFamily.HIGH_REGIME, "exit_medium_hot_take_profit"),
  39. _RulePattern("exact", "ql_high_zone_take_profit", RuleLayer.CORE, RuleFamily.HIGH_REGIME, "exit_ql_high_zone_take_profit"),
  40. _RulePattern("exact", "ql_mid_zone_take_profit", RuleLayer.CORE, RuleFamily.HIGH_REGIME, "exit_ql_mid_zone_take_profit"),
  41. _RulePattern("prefix", "high_regime_", RuleLayer.CORE, RuleFamily.HIGH_REGIME, "exit_high_regime"),
  42. _RulePattern("prefix", "glued_exit:", RuleLayer.CORE, RuleFamily.GLUED, "exit_glued_signal"),
  43. _RulePattern("prefix", "knife_take_profit_", RuleLayer.CORE, RuleFamily.GLUED, "exit_glued_take_profit"),
  44. _RulePattern("prefix", "hard_exit:", RuleLayer.CORE, RuleFamily.RISK_MANAGEMENT, "exit_hard_risk"),
  45. _RulePattern("prefix", "negative_a1_no_b1_recovery:", RuleLayer.SECONDARY, RuleFamily.POST_SELL_REBOUND, "exit_negative_a1_recovery"),
  46. _RulePattern("prefix", "small_positive_a1_declining:", RuleLayer.SECONDARY, RuleFamily.HIGH_REGIME, "exit_small_positive_decline"),
  47. _RulePattern("prefix", "low_zone_dual_gold_exit:", RuleLayer.SECONDARY, RuleFamily.DUAL_GOLD, "exit_low_zone_dual_gold"),
  48. _RulePattern("exact", "early_failed_rebound_exit", RuleLayer.SECONDARY, RuleFamily.POST_SELL_REBOUND, "exit_early_failed_rebound"),
  49. _RulePattern("exact", "good_to_take_profit_2:kdj_sell", RuleLayer.CORE, RuleFamily.HIGH_REGIME, "exit_good_to_take_profit_2"),
  50. _RulePattern("prefix", "negative_a1_b1_not_strong:", RuleLayer.SECONDARY, RuleFamily.POST_SELL_REBOUND, "exit_negative_a1_b1_not_strong"),
  51. _RulePattern("exact", "early_positive_take_profit", RuleLayer.CORE, RuleFamily.HIGH_REGIME, "exit_early_positive_take_profit"),
  52. _RulePattern("exact", "high_zone_post_ql_fade_exit", RuleLayer.CORE, RuleFamily.HIGH_REGIME, "exit_high_zone_post_ql_fade"),
  53. _RulePattern("exact", "oversold_rebound_take_profit", RuleLayer.SECONDARY, RuleFamily.DEEP_OVERSOLD, "exit_oversold_rebound_take_profit"),
  54. )
  55. AUX_PATTERNS: tuple[_RulePattern, ...] = (
  56. _RulePattern("prefix", "bearish_signal_after_exit:", RuleLayer.BRIDGE, RuleFamily.AUXILIARY, "aux_bearish_after_exit"),
  57. _RulePattern("exact", "bullish_signal_while_holding", RuleLayer.BRIDGE, RuleFamily.AUXILIARY, "aux_bullish_while_holding"),
  58. )
  59. def _classify_with_patterns(
  60. legacy_reason: str,
  61. patterns: tuple[_RulePattern, ...],
  62. default_layer: RuleLayer,
  63. default_family: RuleFamily,
  64. default_code: str,
  65. ) -> DecisionReason:
  66. normalized = str(legacy_reason or "").strip()
  67. for rule in patterns:
  68. if rule.matches(normalized):
  69. return DecisionReason(
  70. code=rule.code,
  71. layer=rule.layer,
  72. family=rule.family,
  73. legacy_reason=normalized,
  74. tags=rule.tags,
  75. )
  76. return DecisionReason(
  77. code=default_code,
  78. layer=default_layer,
  79. family=default_family,
  80. legacy_reason=normalized,
  81. )
  82. def classify_entry_reason(legacy_reason: str) -> DecisionReason:
  83. return _classify_with_patterns(
  84. legacy_reason=legacy_reason,
  85. patterns=ENTRY_PATTERNS,
  86. default_layer=RuleLayer.UNKNOWN,
  87. default_family=RuleFamily.UNKNOWN,
  88. default_code="entry_unknown",
  89. )
  90. def classify_exit_reason(legacy_reason: str) -> DecisionReason:
  91. return _classify_with_patterns(
  92. legacy_reason=legacy_reason,
  93. patterns=EXIT_PATTERNS,
  94. default_layer=RuleLayer.UNKNOWN,
  95. default_family=RuleFamily.UNKNOWN,
  96. default_code="exit_unknown",
  97. )
  98. def classify_aux_reason(legacy_reason: str) -> DecisionReason:
  99. return _classify_with_patterns(
  100. legacy_reason=legacy_reason,
  101. patterns=AUX_PATTERNS,
  102. default_layer=RuleLayer.UNKNOWN,
  103. default_family=RuleFamily.UNKNOWN,
  104. default_code="aux_unknown",
  105. )