dragon_short_holding_master_review.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. from __future__ import annotations
  2. import json
  3. from dataclasses import asdict
  4. from pathlib import Path
  5. import pandas as pd
  6. from dragon_branch_configs import alpha_first_selective_veto_config
  7. def _load_csv(base_dir: Path, name: str) -> pd.DataFrame:
  8. return pd.read_csv(base_dir / name, encoding="utf-8-sig")
  9. def _format_pct(value: float) -> str:
  10. if pd.isna(value):
  11. return "NA"
  12. if value == float("inf"):
  13. return "inf"
  14. return f"{value:.2%}"
  15. def _format_num(value: float) -> str:
  16. if pd.isna(value):
  17. return "NA"
  18. if value == float("inf"):
  19. return "inf"
  20. return f"{value:.2f}"
  21. def main() -> None:
  22. base_dir = Path(__file__).resolve().parent
  23. audit = _load_csv(base_dir, "dragon_short_holding_audit.csv")
  24. pressure = _load_csv(base_dir, "dragon_short_holding_family_pressure.csv")
  25. experiments = _load_csv(base_dir, "dragon_short_holding_experiments.csv")
  26. best = experiments[experiments["experiment"] != "baseline_alpha_first"].sort_values(
  27. ["avg_return", "profit_factor"], ascending=[False, False]
  28. ).iloc[0]
  29. winner_config = alpha_first_selective_veto_config().with_updates(
  30. glued_selective_hot_c1_min=40.0,
  31. glued_selective_hot_b1_min=0.10,
  32. glued_selective_low_c1_min=23.0,
  33. glued_selective_low_c1_max=28.0,
  34. glued_selective_low_b1_max=0.02,
  35. )
  36. snapshot = asdict(winner_config)
  37. snapshot["disabled_rules"] = sorted(winner_config.disabled_rules)
  38. (base_dir / "dragon_short_holding_candidate_config.json").write_text(
  39. json.dumps(snapshot, indent=2, ensure_ascii=False) + "\n",
  40. encoding="utf-8",
  41. )
  42. root_summary = (
  43. audit.groupby("failure_root")
  44. .agg(trades=("buy_date", "count"), avg_return=("return_pct", "mean"))
  45. .reset_index()
  46. .sort_values("trades", ascending=False)
  47. )
  48. top_entry_drag = pressure[pressure["group_type"] == "entry_family"].sort_values("drag_score", ascending=False).head(5)
  49. top_path_drag = pressure[pressure["group_type"] == "path_combo"].sort_values("drag_score", ascending=False).head(5)
  50. lines = [
  51. "# Dragon Short Holding Master Review",
  52. "",
  53. "- Branch under audit: `alpha_first_selective_veto`.",
  54. "- Goal: identify the dominant short-holding drag and the next narrow optimization target.",
  55. "",
  56. "## Audit Conclusions",
  57. f"- audited short trades: `{int(len(audit))}`",
  58. ]
  59. for _, row in root_summary.iterrows():
  60. lines.append(
  61. f"- failure_root `{row['failure_root']}`: trades `{int(row['trades'])}`, avg_return `{_format_pct(float(row['avg_return']))}`"
  62. )
  63. lines.extend(["", "## Lead Drag Families"])
  64. for _, row in top_entry_drag.iterrows():
  65. lines.append(
  66. f"- `{row['holding_bucket']} / {row['entry_family']}`: trades `{int(row['trades'])}`, "
  67. f"avg_return `{_format_pct(float(row['avg_return']))}`, drag_score `{row['drag_score']:.4f}`"
  68. )
  69. lines.extend(["", "## Lead Drag Paths"])
  70. for _, row in top_path_drag.iterrows():
  71. lines.append(
  72. f"- `{row['holding_bucket']} / {row['entry_family']} -> {row['sell_reason']}`: trades `{int(row['trades'])}`, "
  73. f"avg_return `{_format_pct(float(row['avg_return']))}`, drag_score `{row['drag_score']:.4f}`"
  74. )
  75. lines.extend(
  76. [
  77. "",
  78. "## Experiment Winner",
  79. f"- best branch: `{best['experiment']}`",
  80. f"- trades: `{int(best['trades'])}`",
  81. f"- avg_return: `{_format_pct(float(best['avg_return']))}`",
  82. f"- profit_factor: `{_format_num(float(best['profit_factor']))}`",
  83. f"- short_avg_return: `{_format_pct(float(best['short_avg_return']))}`",
  84. f"- `00-05d`: `{_format_pct(float(best['short_00_05d_avg_return']))}`",
  85. f"- `06-10d`: `{_format_pct(float(best['short_06_10d_avg_return']))}`",
  86. f"- real BUY / SELL overlap: `{int(best['real_buy_overlap'])}/{int(best['real_sell_overlap'])}`",
  87. "",
  88. "## Interpretation",
  89. "- `post_sell_rebound_buy` is not the main short-holding problem in this pack; disabling it hurts or adds little value.",
  90. "- The dominant short-holding drag is `glued_buy`, especially in mid-regime short exits.",
  91. "- The winning branch confirms that narrow glued-entry veto is more valuable than attacking `post_sell_rebound_buy` first.",
  92. "- The most useful next alpha-first direction is now a glued-focused selective-veto branch, not a post-sell-rebound branch.",
  93. "",
  94. "## Candidate Config",
  95. "- Snapshot file: `dragon_short_holding_candidate_config.json`.",
  96. "- This candidate should remain alpha-first research only until a branch-level governance decision upgrades it.",
  97. ]
  98. )
  99. (base_dir / "dragon_short_holding_master_review.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
  100. if __name__ == "__main__":
  101. main()