dragon_stability_report.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. from __future__ import annotations
  2. from pathlib import Path
  3. import pandas as pd
  4. def _load_csv(base_dir: Path, name: str) -> pd.DataFrame:
  5. return pd.read_csv(base_dir / name, encoding="utf-8-sig")
  6. def main() -> None:
  7. base_dir = Path(__file__).resolve().parent
  8. group_summary = _load_csv(base_dir, "dragon_trade_group_summary.csv")
  9. ablation = _load_csv(base_dir, "dragon_rule_ablation.csv")
  10. sensitivity = _load_csv(base_dir, "dragon_threshold_sensitivity_summary.csv")
  11. deep_oversold = _load_csv(base_dir, "dragon_deep_oversold_subtype_summary.csv")
  12. deep_oversold_exp = _load_csv(base_dir, "dragon_deep_oversold_experiments.csv")
  13. predictive_exp = _load_csv(base_dir, "dragon_predictive_break_experiments.csv")
  14. baseline = ablation[ablation["experiment"] == "baseline"].iloc[0]
  15. holding = group_summary[group_summary["group_type"] == "holding_bucket"].copy()
  16. sample_split = group_summary[group_summary["group_type"] == "sample_split"].copy()
  17. regime = group_summary[group_summary["group_type"] == "market_state_layer"].copy()
  18. protected = ablation[
  19. (ablation["experiment"] != "baseline")
  20. & (ablation["real_buy_overlap"] == baseline["real_buy_overlap"])
  21. & (ablation["real_sell_overlap"] == baseline["real_sell_overlap"])
  22. ].copy()
  23. fragile = sensitivity[sensitivity["stable_real_alignment"] == False].copy()
  24. robust = sensitivity[sensitivity["stable_real_alignment"] == True].copy()
  25. lines = [
  26. "# Dragon Stage 3 Stability Report",
  27. "",
  28. "## Baseline",
  29. f"- trades: `{int(baseline['trades'])}`",
  30. f"- win_rate: `{baseline['win_rate']:.2%}`",
  31. f"- avg_return: `{baseline['avg_return']:.2%}`",
  32. f"- profit_factor: `{baseline['profit_factor']:.2f}`",
  33. f"- real BUY overlap: `{int(baseline['real_buy_overlap'])}`",
  34. f"- real SELL overlap: `{int(baseline['real_sell_overlap'])}`",
  35. "",
  36. "## Holding Structure",
  37. ]
  38. for _, row in holding.sort_values("holding_bucket").iterrows():
  39. lines.append(
  40. f"- `{row['holding_bucket']}`: trades `{int(row['trades'])}`, win_rate `{row['win_rate']:.2%}`, "
  41. f"avg_return `{row['avg_return']:.2%}`, avg_mfe `{row['avg_mfe_pct']:.2%}`, avg_mae `{row['avg_mae_pct']:.2%}`"
  42. )
  43. lines.extend(["", "## Sample Split"])
  44. for _, row in sample_split.sort_values("sample_split").iterrows():
  45. lines.append(
  46. f"- `{row['sample_split']}`: trades `{int(row['trades'])}`, avg_return `{row['avg_return']:.2%}`, "
  47. f"profit_factor `{row['profit_factor']:.2f}`"
  48. )
  49. lines.extend(["", "## Regime Structure"])
  50. for _, row in regime.sort_values("trades", ascending=False).iterrows():
  51. key = row["market_state_layer"]
  52. lines.append(
  53. f"- `{key}`: trades `{int(row['trades'])}`, avg_return `{row['avg_return']:.2%}`, profit_factor `{row['profit_factor']:.2f}`"
  54. )
  55. lines.extend(["", "## Rule Ablation"])
  56. if protected.empty:
  57. lines.append("- No protected experiment preserved full real-trade alignment.")
  58. else:
  59. protected_sorted = protected.sort_values("delta_avg_return", ascending=False)
  60. for _, row in protected_sorted.head(8).iterrows():
  61. lines.append(
  62. f"- `{row['experiment']}`: delta_avg_return `{row['delta_avg_return']:.2%}`, "
  63. f"delta_profit_factor `{row['delta_profit_factor']:.2f}`, delta_aux_sell_overlap `{int(row['delta_aux_sell_overlap'])}`"
  64. )
  65. best_degrade = ablation[(ablation["experiment"] != "baseline")].sort_values("delta_avg_return", ascending=False).head(6)
  66. lines.extend(["", "## Candidate Pressure Points"])
  67. for _, row in best_degrade.iterrows():
  68. lines.append(
  69. f"- `{row['experiment']}`: delta_avg_return `{row['delta_avg_return']:.2%}`, "
  70. f"real BUY `{int(row['real_buy_overlap'])}`, real SELL `{int(row['real_sell_overlap'])}`"
  71. )
  72. lines.extend(["", "## Deep Oversold Subtypes"])
  73. for _, row in deep_oversold.iterrows():
  74. lines.append(
  75. f"- `{row['entry_subtype']}`: trades `{int(row['trades'])}`, win_rate `{row['win_rate']:.2%}`, "
  76. f"avg_return `{row['avg_return']:.2%}`, fast_failures `{int(row['fast_failures'])}`"
  77. )
  78. lines.extend(["", "## Focused Deep Oversold Experiments"])
  79. for _, row in deep_oversold_exp.iterrows():
  80. lines.append(
  81. f"- `{row['experiment']}`: deep trades `{int(row['deep_oversold_trade_count'])}`, "
  82. f"delta_avg_return `{row['delta_avg_return']:.2%}`, real BUY `{int(row['real_buy_overlap'])}`, real SELL `{int(row['real_sell_overlap'])}`"
  83. )
  84. lines.extend(["", "## Predictive Break Experiments"])
  85. for _, row in predictive_exp.iterrows():
  86. lines.append(
  87. f"- `{row['experiment']}`: predictive trades `{int(row['predictive_trade_count'])}`, "
  88. f"delta_avg_return `{row['delta_avg_return']:.2%}`, real BUY `{int(row['real_buy_overlap'])}`, real SELL `{int(row['real_sell_overlap'])}`"
  89. )
  90. lines.extend(["", "## Threshold Sensitivity"])
  91. for _, row in sensitivity.iterrows():
  92. lines.append(
  93. f"- `{row['parameter']}`: stable_real_alignment `{bool(row['stable_real_alignment'])}`, "
  94. f"avg_return_range `{row['avg_return_range']:.2%}`, profit_factor_range `{row['profit_factor_range']:.2f}`"
  95. )
  96. lines.extend(["", "## Fragile Parameters"])
  97. if fragile.empty:
  98. lines.append("- None in this pack.")
  99. else:
  100. for _, row in fragile.iterrows():
  101. lines.append(
  102. f"- `{row['parameter']}`: minimum real BUY overlap `{int(row['real_buy_overlap_min'])}`, minimum real SELL overlap `{int(row['real_sell_overlap_min'])}`"
  103. )
  104. lines.extend(["", "## Robust Parameters"])
  105. for _, row in robust.sort_values("avg_return_range").head(6).iterrows():
  106. lines.append(
  107. f"- `{row['parameter']}`: avg_return_range `{row['avg_return_range']:.2%}`, aux_sell_overlap_range `{int(row['aux_sell_overlap_range'])}`"
  108. )
  109. lines.extend(
  110. [
  111. "",
  112. "## Quant Judgment",
  113. "- `glued_buy` remains the structural backbone. Disabling it destroys alignment and does not produce a credible upgrade path.",
  114. "- `non_glued_positive_expansion_buy` is redundant in the current sample window: its aligned dates are now absorbed by `dual_gold_resonance_buy`, so it should not be treated as an independent alpha family.",
  115. "- `deep_oversold_rebound_buy` is the clearest weak entry family: removing it improves average return materially, but at a significant alignment cost.",
  116. "- Within the deep-oversold family, the weakest subtypes are `positive_b1_rebound` and `shallow_false_start`; this is now a subtype redesign problem, not a whole-rule deletion problem.",
  117. "- Safe default improvement was limited to rerouting 4 weak subtype dates to same-day fallback rules; blocking the remaining weak subtypes raises return modestly but degrades real-trade overlap too much for the current objective.",
  118. "- `knife_take_profit_2_glued` did not improve results when disabled in rerun form, which implies the current drag is partly replaced by alternative same-cycle exits rather than removed cleanly.",
  119. "- `predictive_b1_break` is now effectively a frozen bridge rule: loosening it worsens results, tightening it marginally helps but breaks workbook alignment.",
  120. "- Auxiliary sell compression parameters are relatively robust; major remaining optimization leverage is not in the aux layer.",
  121. ]
  122. )
  123. (base_dir / "dragon_stage3_stability_report.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
  124. if __name__ == "__main__":
  125. main()