dragon_rc1_golden_baseline.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. from __future__ import annotations
  2. import hashlib
  3. import json
  4. from dataclasses import asdict
  5. from datetime import datetime
  6. from pathlib import Path
  7. import pandas as pd
  8. from dragon_branch_configs import alpha_first_glued_refined_hot_cap_config
  9. from dragon_indicators import DragonIndicatorConfig, DragonIndicatorEngine
  10. from dragon_shared import END_DATE, START_DATE
  11. from dragon_strategy import DragonRuleEngine
  12. RC_VERSION = "RC1"
  13. RC_BRANCH = "alpha_first_glued_refined_hot_cap"
  14. TRADES_OUTPUT = "dragon_rc1_golden_trades.csv"
  15. EVENTS_OUTPUT = "dragon_rc1_golden_events.csv"
  16. MANIFEST_OUTPUT = "dragon_rc1_golden_manifest.json"
  17. EVENTS_CORE_COLUMNS = [
  18. "date",
  19. "side",
  20. "layer",
  21. "reason",
  22. "close",
  23. "a1",
  24. "b1",
  25. "c1",
  26. "kdj_buy",
  27. "kdj_sell",
  28. "ql_buy",
  29. "ql_sell",
  30. ]
  31. TRADES_CORE_COLUMNS = [
  32. "buy_date",
  33. "buy_price",
  34. "buy_reason",
  35. "sell_date",
  36. "sell_price",
  37. "sell_reason",
  38. "holding_days",
  39. "return_pct",
  40. ]
  41. def _sha256(path: Path) -> str:
  42. digest = hashlib.sha256()
  43. with path.open("rb") as fp:
  44. for chunk in iter(lambda: fp.read(1024 * 1024), b""):
  45. digest.update(chunk)
  46. return digest.hexdigest()
  47. def _df_sha256(df: pd.DataFrame) -> str:
  48. digest = hashlib.sha256()
  49. payload = df.to_csv(index=False, encoding="utf-8", float_format="%.15g")
  50. digest.update(payload.encode("utf-8"))
  51. return digest.hexdigest()
  52. def _load_indicator_snapshot(base_dir: Path) -> tuple[pd.DataFrame, str]:
  53. full_snapshot = base_dir / "dragon_indicator_snapshot_full.csv"
  54. if full_snapshot.exists():
  55. df = pd.read_csv(full_snapshot, encoding="utf-8-sig")
  56. source = full_snapshot.name
  57. else:
  58. fallback_snapshot = base_dir / "dragon_indicator_snapshot.csv"
  59. if fallback_snapshot.exists():
  60. df = pd.read_csv(fallback_snapshot, encoding="utf-8-sig")
  61. source = fallback_snapshot.name
  62. else:
  63. engine = DragonIndicatorEngine(DragonIndicatorConfig(start_date="2015-01-01", end_date=None))
  64. df = (
  65. engine.compute(engine.fetch_daily_data())
  66. .reset_index(drop=False)
  67. .rename(columns={"index": "date"})
  68. )
  69. source = "fetched_live_history"
  70. df["date"] = pd.to_datetime(df["date"])
  71. indexed = df.sort_values("date").set_index("date")
  72. return indexed, source
  73. def main() -> None:
  74. base_dir = Path(__file__).resolve().parent
  75. indexed, source = _load_indicator_snapshot(base_dir)
  76. config = alpha_first_glued_refined_hot_cap_config()
  77. engine = DragonRuleEngine(config=config)
  78. events, trades = engine.run(indexed)
  79. events = events[
  80. (events["date"] >= START_DATE)
  81. & (events["date"] <= END_DATE)
  82. ].copy()
  83. trades = trades[
  84. (trades["buy_date"] >= START_DATE)
  85. & (trades["buy_date"] <= END_DATE)
  86. & (trades["sell_date"] >= START_DATE)
  87. & (trades["sell_date"] <= END_DATE)
  88. ].copy()
  89. events.insert(0, "branch", RC_BRANCH)
  90. trades.insert(0, "branch", RC_BRANCH)
  91. events.sort_values(["date", "side", "layer", "reason"], inplace=True)
  92. trades.sort_values(["buy_date", "sell_date", "buy_reason", "sell_reason"], inplace=True)
  93. events_path = base_dir / EVENTS_OUTPUT
  94. trades_path = base_dir / TRADES_OUTPUT
  95. manifest_path = base_dir / MANIFEST_OUTPUT
  96. events.to_csv(events_path, index=False, encoding="utf-8-sig")
  97. trades.to_csv(trades_path, index=False, encoding="utf-8-sig")
  98. returns = trades["return_pct"].astype(float) if not trades.empty else pd.Series(dtype=float)
  99. summary = {
  100. "trade_count": int(len(trades)),
  101. "event_count": int(len(events)),
  102. "win_rate": float((returns > 0).mean()) if not returns.empty else float("nan"),
  103. "avg_return": float(returns.mean()) if not returns.empty else float("nan"),
  104. "median_return": float(returns.median()) if not returns.empty else float("nan"),
  105. }
  106. manifest = {
  107. "generated_at": datetime.now().isoformat(timespec="seconds"),
  108. "release_version": RC_VERSION,
  109. "branch": RC_BRANCH,
  110. "evaluation_window": {"start": START_DATE, "end": END_DATE},
  111. "indicator_source": source,
  112. "artifacts": {
  113. "trades": {
  114. "path": TRADES_OUTPUT,
  115. "sha256": _sha256(trades_path),
  116. "columns": list(trades.columns),
  117. "core_columns": TRADES_CORE_COLUMNS,
  118. "core_sha256": _df_sha256(trades[TRADES_CORE_COLUMNS]),
  119. },
  120. "events": {
  121. "path": EVENTS_OUTPUT,
  122. "sha256": _sha256(events_path),
  123. "columns": list(events.columns),
  124. "core_columns": EVENTS_CORE_COLUMNS,
  125. "core_sha256": _df_sha256(events[EVENTS_CORE_COLUMNS]),
  126. },
  127. },
  128. "summary": summary,
  129. "config_snapshot": {
  130. **asdict(config),
  131. "disabled_rules": sorted(config.disabled_rules),
  132. },
  133. }
  134. manifest_path.write_text(
  135. json.dumps(manifest, indent=2, ensure_ascii=False) + "\n",
  136. encoding="utf-8",
  137. )
  138. if __name__ == "__main__":
  139. main()