| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324 |
- from __future__ import annotations
- from dataclasses import dataclass
- from datetime import date
- from typing import Optional
- import pandas as pd
- from dragon_bridge_predictive_break import (
- allow_predictive_b1_break_long_exit,
- allow_predictive_b1_break_short_exit,
- allow_predictive_error_reentry,
- )
- from dragon_deep_oversold_classifier import (
- deep_oversold_base_entry,
- deep_oversold_requires_confirmation,
- deep_oversold_selective_veto,
- deep_oversold_subtype,
- )
- from dragon_deep_oversold_confirmation import (
- evaluate_pending_confirmation,
- should_increment_pending,
- )
- from dragon_decision_types import StrategyDecision
- from dragon_glued_followthrough_confirmation import (
- evaluate_glued_followthrough_confirmation,
- glued_followthrough_pending_allowed,
- glued_high_weak_rebound_subtype,
- )
- from dragon_glued_followthrough_exit import should_hold_glued_followthrough_reentry_kdj_only
- from dragon_rule_catalog import classify_aux_reason, classify_entry_reason, classify_exit_reason
- from dragon_strategy_config import StrategyConfig
- @dataclass
- class StrategyContext:
- in_position: bool = False
- entry_date: Optional[date] = None
- entry_price: Optional[float] = None
- entry_a1: Optional[float] = None
- entry_b1: Optional[float] = None
- entry_c1: Optional[float] = None
- entry_reason: str = ""
- entry_reason_layer: str = "unknown"
- entry_reason_family: str = "unknown"
- entry_reason_code: str = ""
- first_exit_checked: bool = False
- c1_over_80_seen: bool = False
- a1_big_pos_count: int = 0
- b1_big_pos_count: int = 0
- b1_negative_sell_count: int = 0
- prev_real_sell_c1: Optional[float] = None
- last_real_sell_date: Optional[date] = None
- last_real_sell_reason: str = ""
- last_real_sell_reason_layer: str = "unknown"
- last_real_sell_reason_family: str = "unknown"
- last_real_sell_reason_code: str = ""
- bridge_last_exit_predictive_break: bool = False
- bridge_last_exit_negative_a1_no_b1_recovery: bool = False
- last_big_regime_exit_date: Optional[date] = None
- last_kdj_buy_date: Optional[date] = None
- last_kdj_buy_a1: Optional[float] = None
- last_kdj_buy_b1: Optional[float] = None
- last_kdj_buy_c1: Optional[float] = None
- last_kdj_sell_date: Optional[date] = None
- last_ql_sell_date: Optional[date] = None
- last_aux_buy_date: Optional[date] = None
- last_aux_buy_c1: Optional[float] = None
- last_aux_sell_date: Optional[date] = None
- last_aux_sell_c1: Optional[float] = None
- last_aux_sell_b1: Optional[float] = None
- last_aux_sell_reason: str = ""
- flat_post_exit_ql_emitted: bool = False
- flat_post_exit_kdj_emitted: bool = False
- kdj_cross_count_since_big_regime_exit: int = 999
- max_a1_since_entry: float = -999.0
- max_b1_since_entry: float = -999.0
- max_c1_since_entry: float = -999.0
- a1_big_cycle_count: int = 0
- b1_big_cycle_count: int = 0
- combo_big_cycle_count: int = 0
- sell_signal_count: int = 0
- kdj_sell_signal_count: int = 0
- ql_sell_signal_count: int = 0
- prev_a1_big_flag: bool = False
- prev_b1_big_flag: bool = False
- prev_combo_big_flag: bool = False
- pending_deep_oversold_subtype: str = ""
- pending_deep_oversold_origin_date: Optional[date] = None
- pending_deep_oversold_reason: str = ""
- pending_deep_oversold_a1: Optional[float] = None
- pending_deep_oversold_b1: Optional[float] = None
- pending_deep_oversold_c1: Optional[float] = None
- pending_deep_oversold_bars_waited: int = 0
- bridge_pending_deep_oversold_active: bool = False
- pending_glued_followthrough_subtype: str = ""
- pending_glued_followthrough_origin_date: Optional[date] = None
- pending_glued_followthrough_reason: str = ""
- pending_glued_followthrough_signal_close: Optional[float] = None
- pending_glued_followthrough_a1: Optional[float] = None
- pending_glued_followthrough_b1: Optional[float] = None
- pending_glued_followthrough_c1: Optional[float] = None
- pending_glued_followthrough_bars_waited: int = 0
- bridge_pending_glued_followthrough_active: bool = False
- class DragonRuleEngine:
- """
- Executable baseline for the Dragon narrative rules.
- This version focuses on the main rule trunk:
- - KDJ gold-cross driven entries
- - KDJ / QL sell-cross driven exits
- - hard vetoes and hard exits from A1/B1 thresholds
- - special low-C1 knife-catching entries
- - auxiliary bearish / bullish signal downgrades
- """
- def __init__(self, config: Optional[StrategyConfig] = None):
- self.config = config or StrategyConfig()
- self.context = StrategyContext()
- def _rule_enabled(self, rule_name: str) -> bool:
- return rule_name not in self.config.disabled_rules
- def _classify_reason(self, side: str, layer: str, reason: str):
- if layer == "aux_signal":
- return classify_aux_reason(reason)
- if side == "BUY":
- return classify_entry_reason(reason)
- return classify_exit_reason(reason)
- def _build_decision(self, side: str, layer: str, reason: str) -> StrategyDecision:
- if side == "BUY":
- action = "BUY" if layer == "real_trade" else "AUX_BUY"
- else:
- action = "SELL" if layer == "real_trade" else "AUX_SELL"
- return StrategyDecision(
- action=action,
- reason=self._classify_reason(side, layer, reason),
- )
- def _entry_reason_is(self, *reason_names: str) -> bool:
- entry_reason = self.context.entry_reason
- entry_family = entry_reason.split(":", 1)[0]
- if entry_reason in reason_names or entry_family in reason_names:
- return True
- if not self.context.entry_reason_code and not self.context.entry_reason_family:
- return False
- if "glued_buy" in reason_names and self.context.entry_reason_code == "entry_glued_buy":
- return True
- if "dual_gold_resonance_buy" in reason_names and self.context.entry_reason_code == "entry_dual_gold":
- return True
- if (
- "deep_oversold_rebound_buy" in reason_names
- and self.context.entry_reason_family == "deep_oversold"
- ):
- return True
- if (
- "oversold_reversal_after_ql_buy" in reason_names
- and self.context.entry_reason_code == "entry_oversold_reversal_after_ql"
- ):
- return True
- return False
- def _last_sell_reason_is(self, reason_name: str) -> bool:
- if self.context.last_real_sell_reason == reason_name:
- return True
- code_map = {
- "crash_protection_exit": "exit_crash_protection",
- "predictive_b1_break_exit": "exit_predictive_b1_break",
- }
- target_code = code_map.get(reason_name, "")
- return bool(target_code and self.context.last_real_sell_reason_code == target_code)
- def _clear_pending_deep_oversold(self) -> None:
- self.context.pending_deep_oversold_subtype = ""
- self.context.pending_deep_oversold_origin_date = None
- self.context.pending_deep_oversold_reason = ""
- self.context.pending_deep_oversold_a1 = None
- self.context.pending_deep_oversold_b1 = None
- self.context.pending_deep_oversold_c1 = None
- self.context.pending_deep_oversold_bars_waited = 0
- self.context.bridge_pending_deep_oversold_active = False
- def _queue_pending_deep_oversold(self, row: pd.Series, subtype: str) -> None:
- self.context.pending_deep_oversold_subtype = subtype
- self.context.pending_deep_oversold_origin_date = row.name.date()
- self.context.pending_deep_oversold_reason = f"deep_oversold_rebound_buy:{subtype}"
- self.context.pending_deep_oversold_a1 = float(row["a1"])
- self.context.pending_deep_oversold_b1 = float(row["b1"])
- self.context.pending_deep_oversold_c1 = float(row["c1"])
- self.context.pending_deep_oversold_bars_waited = 0
- self.context.bridge_pending_deep_oversold_active = True
- def _clear_pending_glued_followthrough(self) -> None:
- self.context.pending_glued_followthrough_subtype = ""
- self.context.pending_glued_followthrough_origin_date = None
- self.context.pending_glued_followthrough_reason = ""
- self.context.pending_glued_followthrough_signal_close = None
- self.context.pending_glued_followthrough_a1 = None
- self.context.pending_glued_followthrough_b1 = None
- self.context.pending_glued_followthrough_c1 = None
- self.context.pending_glued_followthrough_bars_waited = 0
- self.context.bridge_pending_glued_followthrough_active = False
- def _queue_pending_glued_followthrough(self, row: pd.Series, subtype: str) -> None:
- self.context.pending_glued_followthrough_subtype = subtype
- self.context.pending_glued_followthrough_origin_date = row.name.date()
- self.context.pending_glued_followthrough_reason = f"buy_block_glued_high_weak_rebound:{subtype}"
- self.context.pending_glued_followthrough_signal_close = float(row["close"])
- self.context.pending_glued_followthrough_a1 = float(row["a1"])
- self.context.pending_glued_followthrough_b1 = float(row["b1"])
- self.context.pending_glued_followthrough_c1 = float(row["c1"])
- self.context.pending_glued_followthrough_bars_waited = 0
- self.context.bridge_pending_glued_followthrough_active = True
- @staticmethod
- def _is_glued(a1: float) -> bool:
- return abs(a1) < 0.02
- @staticmethod
- def _is_big_positive(a1: float) -> bool:
- return a1 > 0.028
- @staticmethod
- def _is_big_negative(a1: float) -> bool:
- return a1 < -0.04
- @staticmethod
- def _is_b1_hard_negative(b1: float) -> bool:
- return b1 < -0.17
- @staticmethod
- def _is_b1_strong_positive(b1: float) -> bool:
- return b1 > 0.17
- def _holding_days(self, row_date: date) -> int:
- if self.context.entry_date is None:
- return 0
- return (row_date - self.context.entry_date).days
- def _days_from_last_real_sell(self, row_date: date) -> Optional[int]:
- if self.context.last_real_sell_date is None:
- return None
- return (row_date - self.context.last_real_sell_date).days
- def _days_from_last_aux_buy(self, row_date: date) -> Optional[int]:
- if self.context.last_aux_buy_date is None:
- return None
- return (row_date - self.context.last_aux_buy_date).days
- def _days_from_last_aux_sell(self, row_date: date) -> Optional[int]:
- if self.context.last_aux_sell_date is None:
- return None
- return (row_date - self.context.last_aux_sell_date).days
- def _record_cross_counters(self, row: pd.Series) -> None:
- if bool(row["kdj_buy"]):
- self.context.last_kdj_buy_date = row.name.date()
- self.context.last_kdj_buy_a1 = float(row["a1"])
- self.context.last_kdj_buy_b1 = float(row["b1"])
- self.context.last_kdj_buy_c1 = float(row["c1"])
- if self.context.kdj_cross_count_since_big_regime_exit != 999:
- self.context.kdj_cross_count_since_big_regime_exit += 1
- if bool(row["kdj_sell"]):
- self.context.last_kdj_sell_date = row.name.date()
- if self.context.kdj_cross_count_since_big_regime_exit != 999:
- self.context.kdj_cross_count_since_big_regime_exit += 1
- if bool(row["ql_sell"]):
- self.context.last_ql_sell_date = row.name.date()
- def _update_pending_states(self, row: pd.Series) -> None:
- if should_increment_pending(
- active=self.context.bridge_pending_deep_oversold_active,
- subtype=self.context.pending_deep_oversold_subtype,
- origin_date=self.context.pending_deep_oversold_origin_date,
- row_date=row.name.date(),
- ):
- self.context.pending_deep_oversold_bars_waited += 1
- if should_increment_pending(
- active=self.context.bridge_pending_glued_followthrough_active,
- subtype=self.context.pending_glued_followthrough_subtype,
- origin_date=self.context.pending_glued_followthrough_origin_date,
- row_date=row.name.date(),
- ):
- self.context.pending_glued_followthrough_bars_waited += 1
- if self.context.bridge_pending_glued_followthrough_active:
- if bool(row["kdj_sell"]) or bool(row["ql_sell"]):
- self._clear_pending_glued_followthrough()
- elif self.context.pending_glued_followthrough_bars_waited > self.config.glued_followthrough_confirm_window_bars:
- self._clear_pending_glued_followthrough()
- def _update_position_counters(self, row: pd.Series) -> None:
- if not self.context.in_position:
- return
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- self.context.max_a1_since_entry = max(self.context.max_a1_since_entry, a1)
- self.context.max_b1_since_entry = max(self.context.max_b1_since_entry, b1)
- self.context.max_c1_since_entry = max(self.context.max_c1_since_entry, c1)
- a1_big_flag = self._is_big_positive(a1)
- b1_big_flag = self._is_b1_strong_positive(b1)
- combo_big_flag = a1_big_flag and b1_big_flag
- if a1_big_flag and not self.context.prev_a1_big_flag:
- self.context.a1_big_cycle_count += 1
- if b1_big_flag and not self.context.prev_b1_big_flag:
- self.context.b1_big_cycle_count += 1
- if combo_big_flag and not self.context.prev_combo_big_flag:
- self.context.combo_big_cycle_count += 1
- self.context.prev_a1_big_flag = a1_big_flag
- self.context.prev_b1_big_flag = b1_big_flag
- self.context.prev_combo_big_flag = combo_big_flag
- if self._is_big_positive(a1):
- self.context.a1_big_pos_count += 1
- if self._is_b1_strong_positive(b1):
- self.context.b1_big_pos_count += 1
- if c1 > 80:
- self.context.c1_over_80_seen = True
- def _is_high_regime_trade(self) -> bool:
- return (
- self.context.c1_over_80_seen
- and self.context.max_c1_since_entry > 80
- and self.context.max_a1_since_entry > 0.045
- )
- def _high_regime_exit_decision(self, row: pd.Series, source: str) -> Optional[tuple[str, str]]:
- if not self._is_high_regime_trade():
- return None
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- kdj_sell = bool(row["kdj_sell"])
- ql_sell = bool(row["ql_sell"])
- if (
- ql_sell
- and not kdj_sell
- and self.context.max_b1_since_entry > 0.15
- and a1 <= 0.038
- and b1 <= 0.12
- and c1 > 80
- and self._rule_enabled("ql_high_zone_take_profit")
- ):
- return "SELL", "ql_high_zone_take_profit"
- if ql_sell and not kdj_sell and c1 > 80 and a1 > 0.02:
- return "HOLD", "high_regime_wait_kdj_confirmation"
- if (
- self.context.max_a1_since_entry > 0.09
- and a1 > 0.055
- and b1 < -0.05
- and ql_sell
- and self._rule_enabled("high_regime_momentum_break")
- ):
- return "SELL", "high_regime_momentum_break"
- if a1 > 0.06 and c1 > 90 and b1 > -0.03 and not ql_sell:
- return "HOLD", "super_hot_trend_hold"
- if not kdj_sell:
- return None
- decayed_from_peak = (
- self.context.max_b1_since_entry > 0.18
- and a1 < self.context.max_a1_since_entry - 0.005
- and b1 < min(0.13, self.context.max_b1_since_entry - 0.02)
- )
- if 0.033 <= a1 <= 0.05 and c1 > 80 and decayed_from_peak:
- if self._rule_enabled("prewarning_reduction_exit"):
- return "SELL", "prewarning_reduction_exit"
- if (
- b1 <= 0
- and (
- a1 <= 0.022
- or c1 < 80
- or self.context.kdj_sell_signal_count >= 2
- or self.context.b1_negative_sell_count >= 2
- )
- ):
- if self._rule_enabled(f"high_regime_confirmed_exit:{source}"):
- return "SELL", f"high_regime_confirmed_exit:{source}"
- return "HOLD", f"high_regime_hold:{source}"
- def _ql_only_take_profit_exit(self, row: pd.Series) -> Optional[tuple[str, str]]:
- if not bool(row["ql_sell"]) or bool(row["kdj_sell"]) or not self.context.in_position:
- return None
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- if self.context.max_b1_since_entry <= 0.15:
- return None
- if self.context.max_c1_since_entry >= 78 and 0 < a1 <= 0.02 and b1 <= 0.12:
- if self._rule_enabled("ql_mid_zone_take_profit"):
- return "SELL", "ql_mid_zone_take_profit"
- if c1 > 80 and a1 <= 0.038 and b1 <= 0.12:
- if self._rule_enabled("ql_high_zone_take_profit"):
- return "SELL", "ql_high_zone_take_profit"
- return None
- def _buy_filter_early_downtrend(self, row: pd.Series, prev_row: Optional[pd.Series]) -> bool:
- if prev_row is None:
- return False
- c1 = float(row["c1"])
- prev_c1 = float(prev_row["c1"])
- if prev_c1 <= 80 or c1 >= prev_c1:
- return False
- if self.context.kdj_cross_count_since_big_regime_exit > 6:
- return False
- return True
- def _buy_filter_high_zone_after_hot_exit(self, row: pd.Series) -> bool:
- prev_sell_c1 = self.context.prev_real_sell_c1
- if prev_sell_c1 is None:
- return False
- return prev_sell_c1 > 80 and float(row["c1"]) > 80
- def _buy_filter_glued_b1_descending(self, row: pd.Series) -> bool:
- prev_b1 = self.context.last_kdj_buy_b1
- if prev_b1 is None:
- return False
- b1 = float(row["b1"])
- return b1 < 0 and prev_b1 < 0 and b1 < prev_b1
- def _glued_high_weak_rebound_subtype(self, row: pd.Series) -> str:
- return glued_high_weak_rebound_subtype(
- a1=float(row["a1"]),
- b1=float(row["b1"]),
- c1=float(row["c1"]),
- ql_buy=bool(row["ql_buy"]),
- config=self.config,
- )
- def _buy_filter_glued_high_weak_rebound(self, row: pd.Series) -> bool:
- return bool(self._glued_high_weak_rebound_subtype(row))
- def _glued_followthrough_should_queue(self, subtype: str) -> bool:
- return glued_followthrough_pending_allowed(
- subtype=subtype,
- config=self.config,
- )
- def _buy_filter_glued_selective_short_holding(self, row: pd.Series) -> bool:
- c1 = float(row["c1"])
- b1 = float(row["b1"])
- if (
- self.config.glued_selective_hot_c1_min > 0
- and c1 >= self.config.glued_selective_hot_c1_min
- and c1 < self.config.glued_selective_hot_c1_max
- and b1 >= self.config.glued_selective_hot_b1_min
- ):
- return True
- if (
- self.config.glued_selective_low_c1_max > self.config.glued_selective_low_c1_min
- and self.config.glued_selective_low_c1_min <= c1 < self.config.glued_selective_low_c1_max
- and b1 <= self.config.glued_selective_low_b1_max
- ):
- return True
- return False
- def _buy_improving(self, row: pd.Series) -> bool:
- prev_a1 = self.context.last_kdj_buy_a1
- prev_b1 = self.context.last_kdj_buy_b1
- if prev_a1 is None or prev_b1 is None:
- return True
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- return (a1 >= prev_a1) and (b1 >= prev_b1 or b1 > 0)
- def _should_emit_aux_buy(self, row: pd.Series) -> bool:
- if not bool(row["kdj_buy"]) or not self.context.in_position:
- return False
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- holding_days = self._holding_days(row.name.date())
- days_from_last_aux_buy = self._days_from_last_aux_buy(row.name.date())
- if days_from_last_aux_buy is not None and days_from_last_aux_buy <= 10:
- return False
- strong_dual_gold_reconfirm = bool(row["ql_buy"]) and a1 > 0.03 and (b1 > 0.15 or c1 > 60)
- early_strength_reconfirm = holding_days <= 35 and b1 > 0.24 and c1 > 45
- super_hot_reconfirm = holding_days >= 20 and a1 > 0.07 and c1 > 90
- if not (strong_dual_gold_reconfirm or early_strength_reconfirm or super_hot_reconfirm):
- return False
- if (
- days_from_last_aux_buy is not None
- and days_from_last_aux_buy <= 20
- and self.context.last_aux_buy_c1 is not None
- and c1 <= self.context.last_aux_buy_c1 + 8
- and b1 < 0.24
- ):
- return False
- return True
- def _should_emit_aux_sell(self, row: pd.Series) -> bool:
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- ql_sell = bool(row["ql_sell"])
- kdj_sell = bool(row["kdj_sell"])
- days_from_last_sell = self._days_from_last_real_sell(row.name.date())
- days_from_last_aux_sell = self._days_from_last_aux_sell(row.name.date())
- current_source = "ql_sell" if ql_sell and not kdj_sell else "kdj_sell"
- if (
- kdj_sell
- and not ql_sell
- and c1 > self.config.aux_sell_high_zone_kdj_only_block_c1
- and b1 > self.config.aux_sell_high_zone_kdj_only_block_b1
- ):
- return False
- if days_from_last_sell is not None and 0 < days_from_last_sell <= self.config.post_exit_confirmation_window_days:
- base_emit = True
- elif c1 > self.config.aux_sell_high_zone_warning_c1 and (ql_sell or kdj_sell):
- base_emit = True
- elif kdj_sell and c1 > self.config.aux_sell_strong_break_c1 and b1 < self.config.aux_sell_strong_break_b1:
- base_emit = True
- else:
- base_emit = False
- if not base_emit:
- return False
- if (
- self.config.aux_sell_same_side_once_per_cycle
- and days_from_last_sell is not None
- and 0 < days_from_last_sell <= self.config.post_exit_confirmation_window_days
- ):
- if current_source == "ql_sell" and self.context.flat_post_exit_ql_emitted:
- return False
- if current_source == "kdj_sell" and self.context.flat_post_exit_kdj_emitted:
- return False
- if days_from_last_aux_sell is not None and days_from_last_aux_sell <= self.config.aux_sell_duplicate_cooldown_days:
- stronger = False
- if (
- self.context.last_aux_sell_c1 is not None
- and c1 < self.context.last_aux_sell_c1 - self.config.aux_sell_stronger_c1_delta
- ):
- stronger = True
- if (
- self.context.last_aux_sell_b1 is not None
- and b1 < self.context.last_aux_sell_b1 - self.config.aux_sell_stronger_b1_delta
- ):
- stronger = True
- if self.context.last_aux_sell_reason.endswith("ql_sell") and kdj_sell:
- stronger = True
- if not stronger and not (
- c1 > self.config.aux_sell_high_zone_warning_c1
- and self.context.last_aux_sell_c1 is not None
- and c1 > self.context.last_aux_sell_c1 + self.config.aux_sell_high_zone_rearm_c1_delta
- ):
- return False
- return True
- def _should_emit_state_aux_sell(self, row: pd.Series) -> bool:
- days_from_last_sell = self._days_from_last_real_sell(row.name.date())
- if days_from_last_sell is None:
- return False
- if not self._last_sell_reason_is("crash_protection_exit"):
- return False
- if not (0 < days_from_last_sell <= self.config.state_crash_followthrough_window_days):
- return False
- if (
- self._days_from_last_aux_sell(row.name.date()) is not None
- and self._days_from_last_aux_sell(row.name.date()) <= self.config.state_crash_followthrough_repeat_cooldown_days
- ):
- return False
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- return (
- c1 < self.config.state_crash_followthrough_c1_max
- and a1 < self.config.state_crash_followthrough_a1_max
- and b1 < self.config.state_crash_followthrough_b1_max
- )
- def _special_buy_knife1(self, row: pd.Series) -> bool:
- c1 = float(row["c1"])
- if c1 >= 12:
- return False
- prev_a1 = self.context.last_kdj_buy_a1
- prev_b1 = self.context.last_kdj_buy_b1
- if prev_a1 is None or prev_b1 is None:
- return False
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- if b1 <= prev_b1:
- return False
- if min(b1, prev_b1) < -0.17:
- return False
- if min(a1, prev_a1) < -0.04 and a1 <= prev_a1:
- return False
- return True
- def _special_buy_knife2(self, row: pd.Series) -> bool:
- c1 = float(row["c1"])
- if c1 >= 12:
- return False
- prev_b1 = self.context.last_kdj_buy_b1
- if prev_b1 is None:
- return False
- b1 = float(row["b1"])
- return b1 > -0.03 and b1 > prev_b1
- def _special_buy_deep_oversold_rebound(self, row: pd.Series) -> bool:
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- return deep_oversold_base_entry(
- a1=a1,
- b1=b1,
- c1=c1,
- config=self.config,
- )
- def _deep_oversold_requires_confirmation(self, row: pd.Series, subtype: str) -> bool:
- return deep_oversold_requires_confirmation(
- subtype=subtype,
- ql_buy=bool(row["ql_buy"]),
- config=self.config,
- )
- def _pending_deep_oversold_decision(self, row: pd.Series) -> tuple[str, str]:
- action, reason, should_clear = evaluate_pending_confirmation(
- active=self.context.bridge_pending_deep_oversold_active,
- subtype=self.context.pending_deep_oversold_subtype,
- in_position=self.context.in_position,
- kdj_sell=bool(row["kdj_sell"]),
- ql_sell=bool(row["ql_sell"]),
- bars_waited=self.context.pending_deep_oversold_bars_waited,
- window_bars=self.config.deep_oversold_confirm_window_bars,
- ql_buy=bool(row["ql_buy"]),
- )
- if should_clear:
- self._clear_pending_deep_oversold()
- return action, reason
- def _pending_glued_followthrough_decision(self, row: pd.Series) -> tuple[str, str]:
- action, reason, should_clear = evaluate_glued_followthrough_confirmation(
- active=self.context.bridge_pending_glued_followthrough_active,
- subtype=self.context.pending_glued_followthrough_subtype,
- in_position=self.context.in_position,
- kdj_sell=bool(row["kdj_sell"]),
- ql_sell=bool(row["ql_sell"]),
- bars_waited=self.context.pending_glued_followthrough_bars_waited,
- ql_buy=bool(row["ql_buy"]),
- close=float(row["close"]),
- signal_close=float(self.context.pending_glued_followthrough_signal_close or 0.0),
- b1=float(row["b1"]),
- signal_b1=float(self.context.pending_glued_followthrough_b1 or 0.0),
- config=self.config,
- )
- if should_clear:
- self._clear_pending_glued_followthrough()
- return action, reason
- def _deep_oversold_selective_veto(self, row: pd.Series, subtype: str) -> bool:
- c1 = float(row["c1"])
- b1 = float(row["b1"])
- return deep_oversold_selective_veto(
- subtype=subtype,
- c1=c1,
- b1=b1,
- ql_buy=bool(row["ql_buy"]),
- config=self.config,
- )
- @staticmethod
- def _deep_oversold_subtype(a1: float, b1: float, c1: float) -> str:
- return deep_oversold_subtype(a1=a1, b1=b1, c1=c1)
- def _special_buy_oversold_recovery(self, row: pd.Series) -> bool:
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- return (
- self.config.oversold_recovery_c1_low <= c1 < self.config.oversold_recovery_c1_high
- and self.config.oversold_recovery_a1_min <= a1 < self.config.oversold_recovery_a1_max
- and b1 > self.config.oversold_recovery_b1_min
- )
- def _special_buy_oversold_reversal_after_ql(self, row: pd.Series, prev_row: Optional[pd.Series]) -> bool:
- if prev_row is None or not bool(prev_row["ql_sell"]):
- return False
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- if (
- self.config.oversold_reversal_after_ql_block_c1_low < c1 < self.config.oversold_reversal_after_ql_block_c1_high
- and b1 > self.config.oversold_reversal_after_ql_block_b1_min
- and a1 > self.config.oversold_reversal_after_ql_block_a1_min
- ):
- return False
- return (
- self.config.oversold_reversal_after_ql_entry_c1_low <= c1 < self.config.oversold_reversal_after_ql_entry_c1_high
- and self.config.oversold_reversal_after_ql_entry_a1_min <= a1 < self.config.oversold_reversal_after_ql_entry_a1_max
- and self.config.oversold_reversal_after_ql_entry_b1_min < b1 < self.config.oversold_reversal_after_ql_entry_b1_max
- )
- def _special_buy_early_crash_probe(self, row: pd.Series) -> bool:
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- return c1 < 20 and -0.07 < a1 < -0.04 and -0.04 < b1 < 0
- def _special_buy_post_sell_rebound(self, row: pd.Series) -> bool:
- if self.context.last_kdj_sell_date is None:
- return False
- days_from_last_kdj_sell = (row.name.date() - self.context.last_kdj_sell_date).days
- if days_from_last_kdj_sell < 0 or days_from_last_kdj_sell > 7:
- return False
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- if (
- c1 > self.config.post_sell_rebound_block_high_c1
- and a1 > self.config.post_sell_rebound_block_high_a1_min
- and b1 < self.config.post_sell_rebound_block_high_b1_max
- ):
- return False
- if (
- c1 < self.config.post_sell_rebound_block_low_c1
- and a1 > self.config.post_sell_rebound_block_low_a1_min
- and b1 < self.config.post_sell_rebound_block_low_b1_max
- ):
- return False
- if (
- self.config.post_sell_rebound_entry1_c1_low <= c1 < self.config.post_sell_rebound_entry1_c1_high
- and self.config.post_sell_rebound_entry1_a1_min <= a1 < self.config.post_sell_rebound_entry1_a1_max
- and self.config.post_sell_rebound_entry1_b1_low < b1 < self.config.post_sell_rebound_entry1_b1_high
- ):
- return True
- if (
- c1 < self.config.post_sell_rebound_entry2_c1_high
- and self.config.post_sell_rebound_entry2_a1_min <= a1 < self.config.post_sell_rebound_entry2_a1_max
- and self.config.post_sell_rebound_entry2_b1_low < b1 < self.config.post_sell_rebound_entry2_b1_high
- ):
- return True
- return False
- def _special_buy_post_washout_reentry(self, row: pd.Series) -> bool:
- if self.context.last_real_sell_date is None or self.context.prev_real_sell_c1 is None:
- return False
- days_from_last_sell = (row.name.date() - self.context.last_real_sell_date).days
- if days_from_last_sell < 10 or days_from_last_sell > 20:
- return False
- if self.context.prev_real_sell_c1 >= 15:
- return False
- if not self.context.bridge_last_exit_negative_a1_no_b1_recovery:
- return False
- if bool(row["kdj_buy"]) or bool(row["ql_buy"]) or bool(row["ql_sell"]) or not bool(row["kdj_sell"]):
- return False
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- return 0.028 <= a1 < 0.035 and b1 > 0.30 and 50 < c1 < 60
- def _buy_decision(self, row: pd.Series, prev_row: Optional[pd.Series]) -> tuple[str, str]:
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- if (
- not self.context.in_position
- and self._rule_enabled("post_washout_kdj_reentry_buy")
- and self._special_buy_post_washout_reentry(row)
- ):
- return "BUY", "post_washout_kdj_reentry_buy"
- has_buy_signal = bool(row["kdj_buy"] or row["ql_buy"])
- if not has_buy_signal:
- return "NONE", ""
- dual_gold = bool(row["ql_buy"])
- if self.context.in_position:
- if self._should_emit_aux_buy(row):
- return "AUX_BUY", "bullish_signal_while_holding"
- return "NONE", ""
- pending_action, pending_reason = self._pending_deep_oversold_decision(row)
- if pending_action != "NONE":
- return pending_action, pending_reason
- pending_action, pending_reason = self._pending_glued_followthrough_decision(row)
- if pending_action != "NONE":
- return pending_action, pending_reason
- if bool(row["ql_buy"]) and not bool(row["kdj_buy"]):
- if allow_predictive_error_reentry(
- enabled=self._rule_enabled("predictive_error_reentry_buy"),
- last_exit_predictive_break=self.context.bridge_last_exit_predictive_break,
- last_real_sell_date=self.context.last_real_sell_date,
- row_date=row.name.date(),
- a1=a1,
- b1=b1,
- c1=c1,
- ):
- return "BUY", "predictive_error_reentry_buy"
- if (
- self._rule_enabled("hot_exit_reentry_buy")
- and
- self.context.prev_real_sell_c1 is not None
- and self.context.prev_real_sell_c1 > 80
- and self.context.last_real_sell_date is not None
- and (row.name.date() - self.context.last_real_sell_date).days <= 10
- and c1 > 80
- and -0.02 < a1 < 0.03
- and b1 > -0.03
- ):
- return "BUY", "hot_exit_reentry_buy"
- return "NONE", ""
- if self._rule_enabled("early_crash_probe_buy") and self._special_buy_early_crash_probe(row):
- return "BUY", "early_crash_probe_buy"
- if self._rule_enabled("deep_oversold_rebound_buy") and self._special_buy_deep_oversold_rebound(row):
- subtype = self._deep_oversold_subtype(a1, b1, c1)
- if subtype == "positive_b1_rebound" and self.config.deep_oversold_block_positive_b1_rebound:
- pass
- elif (
- subtype == "shallow_false_start"
- and not bool(row["ql_buy"])
- and self.config.deep_oversold_block_shallow_false_start_without_ql
- ):
- pass
- elif subtype == "shallow_false_start" and bool(row["ql_buy"]) and self.config.deep_oversold_shallow_ql_fallback:
- pass
- elif (
- subtype == "positive_b1_rebound"
- and a1 > self.config.deep_oversold_positive_b1_fallback_a1_min
- ):
- pass
- elif self._deep_oversold_selective_veto(row, subtype):
- pass
- elif self._deep_oversold_requires_confirmation(row, subtype):
- if not self.context.bridge_pending_deep_oversold_active:
- self._queue_pending_deep_oversold(row, subtype)
- else:
- return "BUY", f"deep_oversold_rebound_buy:{subtype}"
- if self._rule_enabled("oversold_recovery_buy") and self._special_buy_oversold_recovery(row):
- return "BUY", "oversold_recovery_buy"
- if self._rule_enabled("post_sell_rebound_buy") and self._special_buy_post_sell_rebound(row):
- return "BUY", "post_sell_rebound_buy"
- if (
- self._rule_enabled("oversold_reversal_after_ql_buy")
- and self._special_buy_oversold_reversal_after_ql(row, prev_row)
- ):
- return "BUY", "oversold_reversal_after_ql_buy"
- if self._special_buy_knife1(row):
- return "BUY", "knife_catch_1"
- if self._special_buy_knife2(row):
- return "BUY", "knife_catch_2"
- if self._is_big_negative(a1) and not dual_gold:
- return "BLOCK", "buy_block_a1_too_negative"
- if self._is_b1_hard_negative(b1):
- return "BLOCK", "buy_block_b1_too_negative"
- if self._buy_filter_early_downtrend(row, prev_row):
- return "BLOCK", "buy_block_early_downtrend"
- if self._buy_filter_high_zone_after_hot_exit(row):
- return "BLOCK", "buy_block_c1_still_over_80_after_hot_exit"
- if self._is_glued(a1):
- if self._buy_filter_glued_b1_descending(row):
- return "BLOCK", "buy_block_glued_b1_still_descending"
- glued_block_subtype = self._glued_high_weak_rebound_subtype(row)
- if glued_block_subtype:
- if (
- self._glued_followthrough_should_queue(glued_block_subtype)
- and not self.context.bridge_pending_glued_followthrough_active
- ):
- self._queue_pending_glued_followthrough(row, glued_block_subtype)
- return "BLOCK", "buy_block_glued_high_weak_rebound"
- if self._buy_filter_glued_selective_short_holding(row):
- return "BLOCK", "buy_block_glued_selective_short_holding"
- if self._rule_enabled("glued_buy"):
- return "BUY", "glued_buy"
- return "BLOCK", "buy_block_glued_rule_disabled"
- if (
- not dual_gold
- and a1 > 0
- and b1 > 0
- and self._buy_improving(row)
- and self._rule_enabled("non_glued_positive_expansion_buy")
- ):
- return "BUY", "non_glued_positive_expansion_buy"
- if dual_gold and c1 > 18 and c1 < 20 and a1 > -0.05 and b1 < -0.09:
- return "BLOCK", "buy_block_dual_gold_false_rebound"
- if dual_gold and self._buy_improving(row) and self._rule_enabled("dual_gold_resonance_buy"):
- return "BUY", "dual_gold_resonance_buy"
- return "BLOCK", "buy_block_base_conditions_not_met"
- def _sell_decision(self, row: pd.Series, prev_row: Optional[pd.Series]) -> tuple[str, str]:
- a1 = float(row["a1"])
- b1 = float(row["b1"])
- c1 = float(row["c1"])
- has_sell_signal = bool(row["kdj_sell"] or row["ql_sell"])
- source = "ql_sell" if bool(row["ql_sell"]) and not bool(row["kdj_sell"]) else "kdj_sell"
- if not has_sell_signal:
- source = "state"
- if not self.context.in_position:
- if has_sell_signal and self._should_emit_aux_sell(row):
- return "AUX_SELL", f"bearish_signal_after_exit:{source}"
- if not has_sell_signal and self._should_emit_state_aux_sell(row):
- return "AUX_SELL", "bearish_signal_after_exit:state_crash_followthrough"
- return "NONE", ""
- if allow_predictive_b1_break_short_exit(
- enabled=self._rule_enabled("predictive_b1_break_exit"),
- has_sell_signal=has_sell_signal,
- entry_is_glued=self._entry_reason_is("glued_buy"),
- holding_days=self._holding_days(row.name.date()),
- a1=a1,
- b1=b1,
- c1=c1,
- config=self.config,
- ):
- return "SELL", "predictive_b1_break_exit"
- if allow_predictive_b1_break_long_exit(
- enabled=self._rule_enabled("predictive_b1_break_exit"),
- has_sell_signal=has_sell_signal,
- entry_is_glued=self._entry_reason_is("glued_buy"),
- holding_days=self._holding_days(row.name.date()),
- max_c1_since_entry=self.context.max_c1_since_entry,
- max_a1_since_entry=self.context.max_a1_since_entry,
- max_b1_since_entry=self.context.max_b1_since_entry,
- last_ql_sell_date=self.context.last_ql_sell_date,
- row_date=row.name.date(),
- a1=a1,
- b1=b1,
- c1=c1,
- config=self.config,
- ):
- return "SELL", "predictive_b1_break_exit"
- if (
- not has_sell_signal
- and self._holding_days(row.name.date()) <= 2
- and abs(a1) < 0.01
- and -0.05 < b1 < 0
- and 45 < c1 < 55
- ):
- return "SELL", "early_failed_rebound_exit"
- if (
- bool(row["ql_sell"])
- and not bool(row["kdj_sell"])
- and 14 <= c1 < 30
- and -0.03 <= a1 < 0
- and -0.05 < b1 < 0
- ):
- return "HOLD", "low_zone_wait_kdj_confirmation"
- if (
- self._is_big_negative(a1)
- and bool(row["ql_sell"])
- and not bool(row["kdj_sell"])
- and c1 < 12
- and self._holding_days(row.name.date()) <= 2
- and b1 > -0.10
- ):
- return "HOLD", "deep_oversold_wait_kdj_confirmation"
- if (self._is_big_negative(a1) or self._is_b1_hard_negative(b1)) and (has_sell_signal or b1 < -0.12):
- return "SELL", f"hard_exit:{source}"
- if (
- self.context.c1_over_80_seen
- and self.context.max_a1_since_entry > 0.05
- and a1 < 0.03
- and b1 < -0.08
- ):
- return "SELL", "crash_protection_exit"
- if not has_sell_signal:
- if (
- self.context.last_ql_sell_date is not None
- and 0 <= (row.name.date() - self.context.last_ql_sell_date).days <= 7
- and self.context.max_c1_since_entry < 80
- and 75 < c1 < 80
- and a1 > 0.025
- and b1 < 0.14
- ):
- return "SELL", "post_ql_decay_exit"
- if (
- self.context.last_kdj_sell_date is not None
- and self.context.last_ql_sell_date is not None
- and 0 <= (row.name.date() - self.context.last_kdj_sell_date).days <= 3
- and 0 <= (row.name.date() - self.context.last_ql_sell_date).days <= 3
- and 55 < c1 < 65
- and 0.015 < a1 < 0.025
- and 0.17 < b1 < 0.19
- ):
- return "SELL", "post_dual_sell_decay_exit"
- if (
- self.context.last_ql_sell_date is not None
- and 0 <= (row.name.date() - self.context.last_ql_sell_date).days <= 7
- and self._entry_reason_is("glued_buy")
- and 84 < c1 < 86.5
- and a1 < 0.028
- and b1 < 0
- and self.context.max_b1_since_entry < 0.14
- ):
- return "SELL", "high_zone_post_ql_fade_exit"
- if (
- self._entry_reason_is("deep_oversold_rebound_buy", "oversold_reversal_after_ql_buy")
- and bool(row["ql_buy"])
- and self._holding_days(row.name.date()) >= 10
- and c1 < 15
- and a1 > -0.02
- and b1 > 0
- ):
- return "SELL", "oversold_rebound_take_profit"
- return "NONE", ""
- high_regime_decision = self._high_regime_exit_decision(row, source)
- if high_regime_decision is not None:
- return high_regime_decision
- ql_only_decision = self._ql_only_take_profit_exit(row)
- if ql_only_decision is not None:
- return ql_only_decision
- if (
- bool(row["ql_sell"])
- and not bool(row["kdj_sell"])
- and self._entry_reason_is("glued_buy")
- and self._holding_days(row.name.date()) <= 7
- and 60 < c1 < 72
- and abs(a1) < 0.01
- and -0.06 < b1 < 0
- and self.context.max_c1_since_entry < 75
- ):
- return "HOLD", "glued_mid_zone_wait_kdj_confirmation"
- if (
- bool(row["ql_sell"])
- and not bool(row["kdj_sell"])
- and self._entry_reason_is("deep_oversold_rebound_buy", "oversold_reversal_after_ql_buy")
- and self._holding_days(row.name.date()) <= 20
- and c1 < 18
- and -0.03 <= a1 < -0.018
- and 0 < b1 < 0.08
- ):
- return "HOLD", "oversold_low_zone_wait_kdj_confirmation"
- if self.context.entry_a1 is not None and self.context.entry_b1 is not None and not self.context.first_exit_checked:
- entry_a1 = self.context.entry_a1
- entry_b1 = self.context.entry_b1
- if (
- entry_a1 < 0
- and entry_b1 < 0
- and self.context.max_c1_since_entry < 80
- and 0.02 < a1 < 0.028
- and b1 < 0.17
- and not self._is_glued(a1)
- and self._rule_enabled("knife_take_profit_1")
- ):
- return "SELL", "knife_take_profit_1"
- if self._is_glued(a1) and self.context.max_c1_since_entry < 80:
- if (
- bool(row["ql_sell"])
- and not bool(row["kdj_sell"])
- and self._entry_reason_is("glued_buy")
- and self._holding_days(row.name.date()) >= 40
- and 70 < c1 < 75
- and 0 < a1 < 0.02
- and 0 <= b1 < 0.02
- and self.context.max_a1_since_entry > 0.15
- and self.context.max_b1_since_entry > 0.30
- ):
- return "HOLD", "long_glued_wait_predictive_break"
- if (
- bool(row["ql_sell"])
- and not bool(row["kdj_sell"])
- and self._entry_reason_is("glued_buy")
- and self._holding_days(row.name.date()) >= 15
- and 60 < c1 < 66
- and a1 > 0.01
- and b1 > 0.12
- ):
- return "HOLD", "knife_take_profit_2_glued_wait_followthrough"
- if should_hold_glued_followthrough_reentry_kdj_only(
- enabled=self.config.glued_followthrough_exit_hold_kdj_only_enabled,
- entry_reason_code=self.context.entry_reason_code,
- kdj_sell=bool(row["kdj_sell"]),
- ql_sell=bool(row["ql_sell"]),
- holding_days=self._holding_days(row.name.date()),
- a1=a1,
- b1=b1,
- c1=c1,
- config=self.config,
- ):
- return "HOLD", "glued_followthrough_reentry_wait_ql_confirmation"
- if b1 < 0.17 and self._rule_enabled("knife_take_profit_2_glued"):
- return "SELL", "knife_take_profit_2_glued"
- if bool(row["ql_sell"]) and self.config.enable_knife_take_profit_2_wait_ql:
- return "SELL", "knife_take_profit_2_wait_ql_s"
- weakened_from_peak = (
- self.context.c1_over_80_seen
- and self.context.combo_big_cycle_count >= 2
- and self.context.max_a1_since_entry > 0.04
- and a1 < self.context.max_a1_since_entry - 0.008
- and b1 < min(0.17, self.context.max_b1_since_entry - 0.04)
- )
- if weakened_from_peak and a1 < 0.05:
- if not bool(row["kdj_sell"]):
- return "HOLD", f"high_zone_wait_kdj_sell:{source}"
- return "SELL", "good_to_take_profit_1"
- large_regime_confirmed = self.context.a1_big_cycle_count >= 4 or self.context.a1_big_pos_count >= 4
- if (
- large_regime_confirmed
- and self.context.b1_negative_sell_count >= 2
- and self.context.c1_over_80_seen
- and a1 < 0.05
- ):
- return "SELL", f"good_to_take_profit_2:{source}"
- if a1 > 0.05:
- return "HOLD", f"hold_a1_above_5pct:{source}"
- if self._is_glued(a1):
- if large_regime_confirmed and self.context.b1_negative_sell_count < 2:
- return "HOLD", f"hold_glued_large_regime_wait_b1_negative_count:{source}"
- return "SELL", f"glued_exit:{source}"
- prev_a1 = float(prev_row["a1"]) if prev_row is not None else a1
- prev_c1 = float(prev_row["c1"]) if prev_row is not None else c1
- a1_declining = a1 < prev_a1
- c1_declining = c1 < prev_c1
- if 0.028 < a1 < 0.05:
- if (
- bool(row["kdj_sell"])
- and not bool(row["ql_sell"])
- and c1 > 80
- and b1 > 0.13
- and self.context.combo_big_cycle_count >= 2
- ):
- return "HOLD", "mid_hot_wait_ql_confirmation"
- if (
- bool(row["kdj_sell"])
- and not bool(row["ql_sell"])
- and c1 > 84
- and b1 < 0.11
- and self.context.max_a1_since_entry < 0.04
- and self.context.max_b1_since_entry > 0.18
- ):
- return "SELL", "medium_hot_take_profit"
- if large_regime_confirmed and self.context.b1_negative_sell_count >= 2:
- return "SELL", f"good_to_take_profit_2:{source}"
- if self.context.combo_big_cycle_count >= 2 and a1 < 0.04 and a1_declining and b1 < 0.17:
- return "SELL", "good_to_take_profit_1"
- if self.context.b1_negative_sell_count >= 2 and c1_declining:
- return "SELL", f"good_to_take_profit_2:{source}"
- return "HOLD", f"hold_mid_positive_a1:{source}"
- if -0.04 < a1 < -0.02:
- if (
- bool(row["kdj_sell"])
- and self._entry_reason_is("dual_gold_resonance_buy")
- and c1 < 20
- and b1 > 0
- ):
- return "SELL", f"low_zone_dual_gold_exit:{source}"
- if b1 <= 0:
- return "SELL", f"negative_a1_no_b1_recovery:{source}"
- if b1 < 0.17 and a1_declining:
- return "SELL", f"negative_a1_b1_not_strong:{source}"
- return "HOLD", f"hold_negative_a1_wait_confirmation:{source}"
- if 0.02 < a1 < 0.028:
- if (
- bool(row["kdj_sell"])
- and not bool(row["ql_sell"])
- and c1 > 60
- and b1 > 0.20
- and self.context.max_c1_since_entry < 80
- and self.context.max_a1_since_entry < 0.025
- and self.context.max_b1_since_entry < 0.25
- and self._holding_days(row.name.date()) <= 20
- ):
- return "SELL", "early_positive_take_profit"
- if (
- bool(row["kdj_sell"])
- and not bool(row["ql_sell"])
- and c1 > 80
- and b1 > 0.08
- and self.context.max_b1_since_entry > 0.15
- ):
- return "HOLD", f"wait_ql_confirmation_small_positive:{source}"
- if b1 < 0.17 and a1_declining:
- return "SELL", f"small_positive_a1_declining:{source}"
- return "HOLD", f"hold_small_positive_a1:{source}"
- if (
- bool(row["ql_sell"])
- and not bool(row["kdj_sell"])
- and 55 < c1 < 75
- and abs(a1) < 0.01
- and -0.05 < b1 < 0
- ):
- return "HOLD", "mid_zone_wait_kdj_confirmation"
- return "SELL", f"default_exit:{source}"
- def _post_real_buy(self, row: pd.Series, reason: str) -> None:
- self._clear_pending_deep_oversold()
- self._clear_pending_glued_followthrough()
- entry_meta = classify_entry_reason(reason)
- self.context.in_position = True
- self.context.entry_date = row.name.date()
- self.context.entry_price = float(row["close"])
- self.context.entry_a1 = float(row["a1"])
- self.context.entry_b1 = float(row["b1"])
- self.context.entry_c1 = float(row["c1"])
- self.context.entry_reason = reason
- self.context.entry_reason_layer = entry_meta.layer.value
- self.context.entry_reason_family = entry_meta.family.value
- self.context.entry_reason_code = entry_meta.code
- self.context.first_exit_checked = False
- self.context.c1_over_80_seen = float(row["c1"]) > 80
- self.context.a1_big_pos_count = 1 if self._is_big_positive(float(row["a1"])) else 0
- self.context.b1_big_pos_count = 1 if self._is_b1_strong_positive(float(row["b1"])) else 0
- self.context.b1_negative_sell_count = 0
- self.context.max_a1_since_entry = float(row["a1"])
- self.context.max_b1_since_entry = float(row["b1"])
- self.context.max_c1_since_entry = float(row["c1"])
- self.context.a1_big_cycle_count = 1 if self._is_big_positive(float(row["a1"])) else 0
- self.context.b1_big_cycle_count = 1 if self._is_b1_strong_positive(float(row["b1"])) else 0
- self.context.combo_big_cycle_count = 1 if (self._is_big_positive(float(row["a1"])) and self._is_b1_strong_positive(float(row["b1"]))) else 0
- self.context.sell_signal_count = 0
- self.context.kdj_sell_signal_count = 0
- self.context.ql_sell_signal_count = 0
- self.context.prev_a1_big_flag = self._is_big_positive(float(row["a1"]))
- self.context.prev_b1_big_flag = self._is_b1_strong_positive(float(row["b1"]))
- self.context.prev_combo_big_flag = self.context.prev_a1_big_flag and self.context.prev_b1_big_flag
- def _post_real_sell(self, row: pd.Series, reason: str) -> None:
- self._clear_pending_deep_oversold()
- self._clear_pending_glued_followthrough()
- sell_meta = classify_exit_reason(reason)
- if self.context.a1_big_pos_count >= 4:
- self.context.last_big_regime_exit_date = row.name.date()
- self.context.kdj_cross_count_since_big_regime_exit = 0
- self.context.prev_real_sell_c1 = float(row["c1"])
- self.context.last_real_sell_date = row.name.date()
- self.context.last_real_sell_reason = reason
- self.context.last_real_sell_reason_layer = sell_meta.layer.value
- self.context.last_real_sell_reason_family = sell_meta.family.value
- self.context.last_real_sell_reason_code = sell_meta.code
- self.context.bridge_last_exit_predictive_break = sell_meta.code == "exit_predictive_b1_break"
- self.context.bridge_last_exit_negative_a1_no_b1_recovery = sell_meta.code == "exit_negative_a1_recovery"
- self.context.in_position = False
- self.context.entry_date = None
- self.context.entry_price = None
- self.context.entry_a1 = None
- self.context.entry_b1 = None
- self.context.entry_c1 = None
- self.context.entry_reason = ""
- self.context.entry_reason_layer = "unknown"
- self.context.entry_reason_family = "unknown"
- self.context.entry_reason_code = ""
- self.context.first_exit_checked = False
- self.context.c1_over_80_seen = False
- self.context.a1_big_pos_count = 0
- self.context.b1_big_pos_count = 0
- self.context.b1_negative_sell_count = 0
- self.context.max_a1_since_entry = -999.0
- self.context.max_b1_since_entry = -999.0
- self.context.max_c1_since_entry = -999.0
- self.context.a1_big_cycle_count = 0
- self.context.b1_big_cycle_count = 0
- self.context.combo_big_cycle_count = 0
- self.context.sell_signal_count = 0
- self.context.kdj_sell_signal_count = 0
- self.context.ql_sell_signal_count = 0
- self.context.flat_post_exit_ql_emitted = False
- self.context.flat_post_exit_kdj_emitted = False
- self.context.prev_a1_big_flag = False
- self.context.prev_b1_big_flag = False
- self.context.prev_combo_big_flag = False
- def _post_aux_buy(self, row: pd.Series) -> None:
- self.context.last_aux_buy_date = row.name.date()
- self.context.last_aux_buy_c1 = float(row["c1"])
- def _post_aux_sell(self, row: pd.Series, reason: str) -> None:
- self.context.last_aux_sell_date = row.name.date()
- self.context.last_aux_sell_c1 = float(row["c1"])
- self.context.last_aux_sell_b1 = float(row["b1"])
- self.context.last_aux_sell_reason = reason
- days_from_last_sell = self._days_from_last_real_sell(row.name.date())
- if days_from_last_sell is not None and 0 < days_from_last_sell <= self.config.post_exit_confirmation_window_days:
- if reason.endswith("ql_sell"):
- self.context.flat_post_exit_ql_emitted = True
- elif reason.endswith("kdj_sell"):
- self.context.flat_post_exit_kdj_emitted = True
- def run(self, df: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]:
- from dragon_execution_runtime import run_compat_execution
- return run_compat_execution(self, df)
- def run_with_layered_engine(df: pd.DataFrame, config: Optional[StrategyConfig] = None) -> tuple[pd.DataFrame, pd.DataFrame]:
- from dragon_rule_engine_v2 import LayeredDragonRuleEngine
- engine = LayeredDragonRuleEngine(config=config)
- return engine.run(df)
|