summarize_current_strategy_recent_activity.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. from __future__ import annotations
  2. import sys
  3. from pathlib import Path
  4. import pandas as pd
  5. sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
  6. from scripts.search_eth_bearish_price_proxy import (
  7. BTC_SYMBOL,
  8. SYMBOL,
  9. Spec,
  10. joined_frames,
  11. load_frame,
  12. period_metrics,
  13. resample,
  14. run_spec,
  15. )
  16. from scripts.search_eth_bb_squeeze_t_gates import (
  17. Variant as TGateVariant,
  18. _align_pair,
  19. _load_candles as load_tgate_candles,
  20. cost_equity_frame as tgate_cost_equity_frame,
  21. run_variant as run_tgate_variant,
  22. )
  23. from scripts.search_live_bb_squeeze_exit_variants import (
  24. PRIMARY_COST,
  25. Variant as LiveVariant,
  26. _load_candles as load_live_candles,
  27. cost_equity_frame as live_cost_equity_frame,
  28. run_variant as run_live_variant,
  29. )
  30. OUTPUT_DIR = Path("reports/eth-exploration")
  31. SUMMARY_CSV = OUTPUT_DIR / "current-strategy-recent-activity.csv"
  32. REPORT_MD = OUTPUT_DIR / "current-strategy-recent-activity-report.md"
  33. WINDOWS = (30, 14, 7)
  34. def scoped_return(frame: pd.DataFrame, days: int) -> float:
  35. cutoff = frame["ts"].iloc[-1] - pd.Timedelta(days=days)
  36. scoped = frame[frame["ts"] >= cutoff]
  37. if len(scoped) < 2:
  38. return 0.0
  39. return float(scoped["equity"].iloc[-1] / scoped["equity"].iloc[0] - 1.0)
  40. def count_trades(trades: list[dict[str, object]], days: int, end: pd.Timestamp) -> int:
  41. cutoff = end - pd.Timedelta(days=days)
  42. return sum(pd.Timestamp(trade["entry_time"], tz="UTC") >= cutoff for trade in trades)
  43. def live_rows() -> list[dict[str, object]]:
  44. candles = load_live_candles("ETH-USDT-SWAP", "15m")
  45. result = run_live_variant(candles, LiveVariant(0.0005, 1))
  46. frame = live_cost_equity_frame(result, dict((PRIMARY_COST, 0.0021))[PRIMARY_COST] if False else 0.0021)
  47. end = pd.to_datetime(candles[-1].ts, unit="ms", utc=True)
  48. return [
  49. {
  50. "strategy": "live_bb_squeeze_mxbuf0.0005",
  51. "window_days": days,
  52. "total_return": scoped_return(frame, days),
  53. "trades": count_trades(result.trades, days, end),
  54. "last_candle": end.isoformat(),
  55. }
  56. for days in WINDOWS
  57. ]
  58. def tgate_rows() -> list[dict[str, object]]:
  59. eth = load_tgate_candles("ETH-USDT-SWAP", "15m")
  60. btc = load_tgate_candles("BTC-USDT-SWAP", "15m")
  61. eth, btc = _align_pair(eth, btc)
  62. variant = TGateVariant(96, 960, 0.25, 0.01, 0.035, "both", "btc-up", 0.006, 0.25, 24, 96, "btc_against", 0.0, 1, 0.006, 0.25, 0.008)
  63. result, gate_stats = run_tgate_variant(eth, btc, variant)
  64. frame = tgate_cost_equity_frame(result, 0.0021)
  65. end = pd.to_datetime(eth[-1].ts, unit="ms", utc=True)
  66. return [
  67. {
  68. "strategy": "bb_squeeze_t_gated_tre96",
  69. "window_days": days,
  70. "total_return": scoped_return(frame, days),
  71. "trades": count_trades(result.trades, days, end),
  72. "last_candle": end.isoformat(),
  73. "reentry_entries_full": gate_stats["reentry_entries"],
  74. }
  75. for days in WINDOWS
  76. ]
  77. def crash_follow_rows() -> list[dict[str, object]]:
  78. eth = load_frame(SYMBOL)
  79. btc = load_frame(BTC_SYMBOL)
  80. frame = joined_frames(resample(eth, "1H"), resample(btc, "1H"))
  81. spec = Spec("crash_follow", "1H", 20, 120, 8, 0.035, 0.02, 0.06, 96, "btc_riskoff")
  82. equity, trades = run_spec(spec, frame)
  83. end = equity.index[-1]
  84. rows = []
  85. for days in WINDOWS:
  86. metrics = period_metrics(equity, trades, pd.DateOffset(days=days))
  87. rows.append(
  88. {
  89. "strategy": "crash_follow_short",
  90. "window_days": days,
  91. "total_return": metrics["total_return"],
  92. "max_drawdown": metrics["max_drawdown"],
  93. "trades": metrics["trades"],
  94. "profit_factor": metrics["profit_factor"],
  95. "win_rate": metrics["win_rate"],
  96. "last_candle": end.isoformat(),
  97. }
  98. )
  99. return rows
  100. def markdown_table(frame: pd.DataFrame) -> str:
  101. rows = [list(frame.columns), ["---" for _ in frame.columns]]
  102. rows.extend(frame.astype(object).where(pd.notna(frame), "").values.tolist())
  103. return "\n".join("| " + " | ".join(str(value).replace("|", "\\|") for value in row) + " |" for row in rows)
  104. def main() -> int:
  105. rows = live_rows() + tgate_rows() + crash_follow_rows()
  106. summary = pd.DataFrame(rows)
  107. OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
  108. summary.to_csv(SUMMARY_CSV, index=False)
  109. display = summary.copy()
  110. display["total_return"] = display["total_return"].map(lambda value: f"{value:.2%}")
  111. if "max_drawdown" in display:
  112. display["max_drawdown"] = display["max_drawdown"].map(lambda value: "" if pd.isna(value) else f"{value:.2%}")
  113. if "profit_factor" in display:
  114. display["profit_factor"] = display["profit_factor"].map(lambda value: "" if pd.isna(value) else f"{value:.3f}")
  115. if "win_rate" in display:
  116. display["win_rate"] = display["win_rate"].map(lambda value: "" if pd.isna(value) else f"{value:.2%}")
  117. REPORT_MD.write_text(
  118. "# Current Strategy Recent Activity\n\n"
  119. "Scope: current live BB squeeze, selected T-gated BB squeeze observer, and crash-follow short observer.\n\n"
  120. f"{markdown_table(display)}\n",
  121. encoding="utf-8",
  122. )
  123. print(f"wrote {SUMMARY_CSV} and {REPORT_MD}")
  124. print(summary.to_string(index=False))
  125. return 0
  126. if __name__ == "__main__":
  127. raise SystemExit(main())