dragon_backtest.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. from __future__ import annotations
  2. from pathlib import Path
  3. import pandas as pd
  4. from dragon_indicators import DragonIndicatorConfig, DragonIndicatorEngine
  5. from dragon_strategy import DragonRuleEngine
  6. from dragon_workbook import DragonWorkbook
  7. def _find_workbook(base_dir: Path) -> Path:
  8. matches = sorted(base_dir.glob("*.xlsx"))
  9. if not matches:
  10. raise FileNotFoundError(f"No workbook found in {base_dir}")
  11. return matches[0]
  12. def _event_match_report(workbook_events: pd.DataFrame, strategy_events: pd.DataFrame, side: str, layer: str) -> dict[str, object]:
  13. wb = set(workbook_events[(workbook_events["side"] == side) & (workbook_events["layer"] == layer)]["date"])
  14. st = set(strategy_events[(strategy_events["side"] == side) & (strategy_events["layer"] == layer)]["date"])
  15. hit = wb & st
  16. return {
  17. "side": side,
  18. "layer": layer,
  19. "workbook": len(wb),
  20. "strategy": len(st),
  21. "overlap": len(hit),
  22. "missing_from_strategy": sorted(wb - st),
  23. "extra_in_strategy": sorted(st - wb),
  24. }
  25. def main() -> None:
  26. base_dir = Path(__file__).resolve().parent
  27. workbook_path = _find_workbook(base_dir)
  28. workbook = DragonWorkbook(workbook_path)
  29. workbook_events = pd.DataFrame(
  30. [
  31. {
  32. "date": event.date.isoformat(),
  33. "side": event.side,
  34. "layer": event.layer,
  35. "signal_reason": event.signal_reason,
  36. "note": event.note,
  37. }
  38. for event in workbook.split_layers()
  39. ]
  40. )
  41. engine = DragonIndicatorEngine(DragonIndicatorConfig(start_date="2015-01-01", end_date="2026-01-31"))
  42. indicator_df = engine.compute(engine.fetch_daily_data())
  43. strategy = DragonRuleEngine()
  44. strategy_events, strategy_trades = strategy.run(indicator_df)
  45. first_workbook_date = pd.to_datetime(workbook_events["date"]).min().date().isoformat()
  46. last_workbook_date = pd.to_datetime(workbook_events["date"]).max().date().isoformat()
  47. strategy_events = strategy_events[
  48. (strategy_events["date"] >= first_workbook_date) & (strategy_events["date"] <= last_workbook_date)
  49. ].copy()
  50. strategy_trades = strategy_trades[
  51. (strategy_trades["buy_date"] >= first_workbook_date)
  52. & (strategy_trades["buy_date"] <= last_workbook_date)
  53. & (strategy_trades["sell_date"] >= first_workbook_date)
  54. & (strategy_trades["sell_date"] <= last_workbook_date)
  55. ].copy()
  56. strategy_events.to_csv(base_dir / "dragon_strategy_events.csv", index=False, encoding="utf-8-sig")
  57. strategy_trades.to_csv(base_dir / "dragon_strategy_trades.csv", index=False, encoding="utf-8-sig")
  58. comparisons = [
  59. _event_match_report(workbook_events, strategy_events, side="BUY", layer="real_trade"),
  60. _event_match_report(workbook_events, strategy_events, side="SELL", layer="real_trade"),
  61. _event_match_report(workbook_events, strategy_events, side="BUY", layer="aux_signal"),
  62. _event_match_report(workbook_events, strategy_events, side="SELL", layer="aux_signal"),
  63. ]
  64. lines = [
  65. "# Dragon Baseline Backtest",
  66. "",
  67. f"- Source workbook: `{workbook_path.name}`",
  68. f"- Strategy events: `{len(strategy_events)}`",
  69. f"- Strategy trades: `{len(strategy_trades)}`",
  70. "",
  71. "## Event Fit",
  72. ]
  73. for item in comparisons:
  74. lines.append(
  75. f"- {item['layer']} {item['side']}: workbook `{item['workbook']}`, strategy `{item['strategy']}`, overlap `{item['overlap']}`"
  76. )
  77. lines.append(f"- missing_from_strategy: `{item['missing_from_strategy'][:20]}`")
  78. lines.append(f"- extra_in_strategy: `{item['extra_in_strategy'][:20]}`")
  79. if not strategy_trades.empty:
  80. wins = (strategy_trades["return_pct"] > 0).sum()
  81. avg_ret = strategy_trades["return_pct"].mean()
  82. med_ret = strategy_trades["return_pct"].median()
  83. lines.extend(
  84. [
  85. "",
  86. "## Strategy Trade Stats",
  87. f"- trades: `{len(strategy_trades)}`",
  88. f"- win_rate: `{wins}/{len(strategy_trades)} = {wins / len(strategy_trades):.2%}`",
  89. f"- avg_return: `{avg_ret:.2%}`",
  90. f"- median_return: `{med_ret:.2%}`",
  91. ]
  92. )
  93. (base_dir / "dragon_strategy_fit.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
  94. if __name__ == "__main__":
  95. main()