dragon_layered_pnl_attribution.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. from __future__ import annotations
  2. from pathlib import Path
  3. import pandas as pd
  4. from dragon_branch_configs import alpha_first_glued_refined_hot_cap_config
  5. from dragon_rc1_golden_baseline import _load_indicator_snapshot
  6. from dragon_shared import END_DATE, START_DATE, format_num, format_pct, profit_factor
  7. from dragon_strategy import DragonRuleEngine
  8. def _summary(frame: pd.DataFrame, group_cols: list[str]) -> pd.DataFrame:
  9. grouped = frame.groupby(group_cols, dropna=False)
  10. rows: list[dict[str, object]] = []
  11. for keys, group in grouped:
  12. if not isinstance(keys, tuple):
  13. keys = (keys,)
  14. returns = group["return_pct"].astype(float)
  15. row = {group_cols[i]: keys[i] for i in range(len(group_cols))}
  16. row.update(
  17. {
  18. "trades": int(len(group)),
  19. "win_rate": float((returns > 0).mean()),
  20. "avg_return": float(returns.mean()),
  21. "median_return": float(returns.median()),
  22. "compounded_return": float((1.0 + returns).prod() - 1.0),
  23. "profit_factor": profit_factor(returns),
  24. }
  25. )
  26. rows.append(row)
  27. return pd.DataFrame(rows).sort_values("trades", ascending=False).reset_index(drop=True)
  28. def main() -> None:
  29. base_dir = Path(__file__).resolve().parent
  30. indexed, source = _load_indicator_snapshot(base_dir)
  31. engine = DragonRuleEngine(config=alpha_first_glued_refined_hot_cap_config())
  32. _, trades = engine.run(indexed)
  33. trades = trades[
  34. (trades["buy_date"] >= START_DATE)
  35. & (trades["buy_date"] <= END_DATE)
  36. & (trades["sell_date"] >= START_DATE)
  37. & (trades["sell_date"] <= END_DATE)
  38. ].copy()
  39. layer_summary = _summary(trades, ["buy_reason_layer", "sell_reason_layer"])
  40. family_summary = _summary(trades, ["buy_reason_family", "sell_reason_family"])
  41. entry_summary = _summary(trades, ["buy_reason_layer", "buy_reason_family"])
  42. exit_summary = _summary(trades, ["sell_reason_layer", "sell_reason_family"])
  43. layer_summary.to_csv(base_dir / "dragon_layered_pnl_attribution.csv", index=False, encoding="utf-8-sig")
  44. family_summary.to_csv(base_dir / "dragon_layered_family_pnl_attribution.csv", index=False, encoding="utf-8-sig")
  45. entry_summary.to_csv(base_dir / "dragon_layered_entry_pnl_attribution.csv", index=False, encoding="utf-8-sig")
  46. exit_summary.to_csv(base_dir / "dragon_layered_exit_pnl_attribution.csv", index=False, encoding="utf-8-sig")
  47. lines: list[str] = [
  48. "# Dragon Layered PnL Attribution",
  49. "",
  50. f"- window: `{START_DATE} -> {END_DATE}`",
  51. "- branch: `alpha_first_glued_refined_hot_cap` (RC1)",
  52. f"- indicator source: `{source}`",
  53. f"- trades: `{int(len(trades))}`",
  54. "",
  55. "## Entry-Layer x Exit-Layer",
  56. ]
  57. for _, row in layer_summary.iterrows():
  58. lines.append(
  59. "- "
  60. f"{row['buy_reason_layer']} -> {row['sell_reason_layer']}: "
  61. f"trades `{int(row['trades'])}`, "
  62. f"win_rate `{format_pct(float(row['win_rate']))}`, "
  63. f"avg_return `{format_pct(float(row['avg_return']))}`, "
  64. f"PF `{format_num(float(row['profit_factor']))}`"
  65. )
  66. lines.extend(
  67. [
  68. "",
  69. "## Top Entry Families",
  70. ]
  71. )
  72. for _, row in entry_summary.head(10).iterrows():
  73. lines.append(
  74. "- "
  75. f"{row['buy_reason_layer']} / {row['buy_reason_family']}: "
  76. f"trades `{int(row['trades'])}`, "
  77. f"avg_return `{format_pct(float(row['avg_return']))}`, "
  78. f"PF `{format_num(float(row['profit_factor']))}`"
  79. )
  80. lines.extend(
  81. [
  82. "",
  83. "## Top Exit Families",
  84. ]
  85. )
  86. for _, row in exit_summary.head(10).iterrows():
  87. lines.append(
  88. "- "
  89. f"{row['sell_reason_layer']} / {row['sell_reason_family']}: "
  90. f"trades `{int(row['trades'])}`, "
  91. f"avg_return `{format_pct(float(row['avg_return']))}`, "
  92. f"PF `{format_num(float(row['profit_factor']))}`"
  93. )
  94. (base_dir / "dragon_layered_pnl_attribution.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
  95. if __name__ == "__main__":
  96. main()