dragon_mismatch_diagnostics.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. from __future__ import annotations
  2. from pathlib import Path
  3. import pandas as pd
  4. def _find_workbook(base_dir: Path) -> Path:
  5. matches = sorted(base_dir.glob("*.xlsx"))
  6. if not matches:
  7. raise FileNotFoundError(f"No workbook found in {base_dir}")
  8. return matches[0]
  9. def _collect_gap_rows(workbook_df: pd.DataFrame, strategy_df: pd.DataFrame, indicators_df: pd.DataFrame, side: str, layer: str, gap_type: str) -> pd.DataFrame:
  10. wb = workbook_df[(workbook_df["side"] == side) & (workbook_df["layer"] == layer)].copy()
  11. st = strategy_df[(strategy_df["side"] == side) & (strategy_df["layer"] == layer)].copy()
  12. wb_dates = set(wb["date"])
  13. st_dates = set(st["date"])
  14. if gap_type == "missing_from_strategy":
  15. target_dates = sorted(wb_dates - st_dates)
  16. base = wb[wb["date"].isin(target_dates)].copy()
  17. base["diagnostic_type"] = gap_type
  18. base["source_note"] = base.get("note", "")
  19. base["source_reason"] = base.get("signal_reason", "")
  20. else:
  21. target_dates = sorted(st_dates - wb_dates)
  22. base = st[st["date"].isin(target_dates)].copy()
  23. base["diagnostic_type"] = gap_type
  24. base["source_note"] = ""
  25. base["source_reason"] = base.get("reason", "")
  26. if base.empty:
  27. return base
  28. merged = base.merge(
  29. indicators_df[
  30. ["date", "close", "a1", "b1", "c1", "kdj_buy", "kdj_sell", "ql_buy", "ql_sell"]
  31. ],
  32. on="date",
  33. how="left",
  34. suffixes=("", "_ind"),
  35. )
  36. merged["target_side"] = side
  37. merged["target_layer"] = layer
  38. return merged[
  39. [
  40. "diagnostic_type",
  41. "target_layer",
  42. "target_side",
  43. "date",
  44. "source_reason",
  45. "source_note",
  46. "close",
  47. "a1",
  48. "b1",
  49. "c1",
  50. "kdj_buy",
  51. "kdj_sell",
  52. "ql_buy",
  53. "ql_sell",
  54. ]
  55. ]
  56. def main() -> None:
  57. base_dir = Path(__file__).resolve().parent
  58. workbook_path = _find_workbook(base_dir)
  59. workbook_layers = pd.read_csv(base_dir / "dragon_workbook_layers.csv", encoding="utf-8-sig")
  60. strategy_events = pd.read_csv(base_dir / "dragon_strategy_events.csv", encoding="utf-8-sig")
  61. indicators = pd.read_csv(base_dir / "dragon_indicator_snapshot.csv", encoding="utf-8-sig")
  62. parts = []
  63. for side in ("BUY", "SELL"):
  64. for layer in ("real_trade", "aux_signal"):
  65. parts.append(_collect_gap_rows(workbook_layers, strategy_events, indicators, side, layer, "missing_from_strategy"))
  66. parts.append(_collect_gap_rows(workbook_layers, strategy_events, indicators, side, layer, "extra_in_strategy"))
  67. gaps = pd.concat(parts, ignore_index=True)
  68. gaps.to_csv(base_dir / "dragon_event_gaps.csv", index=False, encoding="utf-8-sig")
  69. lines = [
  70. "# Dragon Event Gap Diagnostics",
  71. "",
  72. f"- Workbook: `{workbook_path.name}`",
  73. f"- Gap rows: `{len(gaps)}`",
  74. "",
  75. "## Counts",
  76. ]
  77. summary = (
  78. gaps.groupby(["diagnostic_type", "target_layer", "target_side"])
  79. .size()
  80. .reset_index(name="count")
  81. .sort_values(["diagnostic_type", "target_layer", "target_side"])
  82. )
  83. for _, row in summary.iterrows():
  84. lines.append(
  85. f"- {row['diagnostic_type']} / {row['target_layer']} / {row['target_side']}: `{int(row['count'])}`"
  86. )
  87. top_missing_real_sell = gaps[
  88. (gaps["diagnostic_type"] == "missing_from_strategy")
  89. & (gaps["target_layer"] == "real_trade")
  90. & (gaps["target_side"] == "SELL")
  91. ].head(20)
  92. lines.extend(["", "## Sample Missing Real SELL Rows"])
  93. for _, row in top_missing_real_sell.iterrows():
  94. lines.append(
  95. f"- {row['date']} reason `{row['source_reason']}` note `{row['source_note']}` a1 `{row['a1']:.4f}` b1 `{row['b1']:.4f}` c1 `{row['c1']:.2f}`"
  96. )
  97. (base_dir / "dragon_event_gaps.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
  98. if __name__ == "__main__":
  99. main()