build_high_frequency_portfolio_observation_intent.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. from __future__ import annotations
  2. import json
  3. import sys
  4. from datetime import UTC, datetime
  5. from pathlib import Path
  6. import pandas as pd
  7. sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
  8. from scripts import explore_ultrashort as explore
  9. from scripts import search_cross_symbol_high_frequency_portfolios as hf
  10. REPORT_DIR = Path("reports/ultrashort")
  11. OUTPUT_JSON = REPORT_DIR / "high-frequency-portfolio-observation-intent.json"
  12. OUTPUT_MD = REPORT_DIR / "high-frequency-portfolio-observation-intent.md"
  13. PORTFOLIO = "risk-3-hf00486"
  14. QUALIFIED_PATH = REPORT_DIR / "high-frequency-portfolio-qualified.csv"
  15. YEARS = 10.0
  16. def iso_from_ms(ts: int) -> str:
  17. return datetime.fromtimestamp(ts / 1000, UTC).isoformat().replace("+00:00", "Z")
  18. def load_portfolio() -> tuple[list[str], dict[str, float], dict[str, object]]:
  19. row = pd.read_csv(QUALIFIED_PATH).set_index("portfolio").loc[PORTFOLIO]
  20. weights = {str(key): float(value) for key, value in json.loads(str(row["weights_json"])).items()}
  21. return str(row["legs"]).split(";"), weights, row.to_dict()
  22. def run_leg(leg: hf.LegSpec) -> dict[str, object]:
  23. data = {}
  24. for symbol in ("BTC-USDT-SWAP", "ETH-USDT-SWAP") if leg.pair else (leg.symbol,):
  25. data[(symbol, leg.bar)] = hf.load_candles(symbol, leg.bar, YEARS)
  26. result = leg.run(data)
  27. latest_candle = result.candles[-1]
  28. latest_entry = result.entries[-1] if result.entries else None
  29. latest_exit = result.exits[-1] if result.exits else None
  30. position = result.open_position
  31. return {
  32. "leg": leg.key,
  33. "symbol": leg.symbol,
  34. "family": leg.family,
  35. "bar": leg.bar,
  36. "latest_candle_time": iso_from_ms(latest_candle.ts),
  37. "trade_count": result.trade_count,
  38. "open_position": None
  39. if position is None
  40. else {
  41. "side": str(position["side"]),
  42. "entry_time": iso_from_ms(int(position["entry_time"])),
  43. "entry_price": float(position["entry_price"]),
  44. },
  45. "latest_entry": None
  46. if latest_entry is None
  47. else {
  48. "time": iso_from_ms(int(latest_entry["ts"])),
  49. "side": str(latest_entry["side"]),
  50. "price": float(latest_entry["price"]),
  51. },
  52. "latest_exit": None
  53. if latest_exit is None
  54. else {
  55. "time": iso_from_ms(int(latest_exit["ts"])),
  56. "side": str(latest_exit["side"]),
  57. "price": float(latest_exit["price"]),
  58. },
  59. }
  60. def build_payload() -> dict[str, object]:
  61. leg_names, weights, metrics = load_portfolio()
  62. specs = {leg.key: leg for leg in hf.build_legs()}
  63. legs = [run_leg(specs[name]) for name in leg_names]
  64. active = [leg for leg in legs if leg["open_position"] is not None]
  65. long_unit = sum(weights[str(leg["leg"])] for leg in active if leg["open_position"]["side"] == "long")
  66. short_unit = sum(weights[str(leg["leg"])] for leg in active if leg["open_position"]["side"] == "short")
  67. return {
  68. "mode": "readonly_observation_intent",
  69. "created_at": datetime.now(UTC).isoformat(timespec="seconds").replace("+00:00", "Z"),
  70. "submitted_orders": 0,
  71. "private_key_required": False,
  72. "strategy_family": "cross_symbol_high_frequency_portfolio",
  73. "portfolio": PORTFOLIO,
  74. "source_report": "reports/ultrashort/high-frequency-portfolio-report.md",
  75. "risk_limits": {
  76. "no_order_submission": True,
  77. "no_cancel_submission": True,
  78. "blocked_for_live_trading": True,
  79. "reason": "high-frequency portfolio requires read-only observation before any live promotion",
  80. },
  81. "portfolio_metrics": {
  82. "total_return": float(metrics["total_return"]),
  83. "annualized_return": float(metrics["annualized_return"]),
  84. "max_drawdown": float(metrics["max_drawdown"]),
  85. "calmar": float(metrics["calmar"]),
  86. "trades_per_month": float(metrics["trades_per_month"]),
  87. "ret_1y": float(metrics["ret_1y"]),
  88. "ret_6m": float(metrics["ret_6m"]),
  89. "ret_3m": float(metrics["ret_3m"]),
  90. },
  91. "weights": weights,
  92. "legs": legs,
  93. "target": {
  94. "long_unit": long_unit,
  95. "short_unit": short_unit,
  96. "net_unit": long_unit - short_unit,
  97. "active_legs": [str(leg["leg"]) for leg in active],
  98. },
  99. }
  100. def markdown(payload: dict[str, object]) -> str:
  101. lines = [
  102. "# High-frequency Portfolio Observation Intent",
  103. "",
  104. "Read-only observation intent. No order or cancel request was submitted.",
  105. "",
  106. f"- Portfolio: `{payload['portfolio']}`",
  107. f"- Net unit: `{payload['target']['net_unit']:.6f}`",
  108. f"- Long unit: `{payload['target']['long_unit']:.6f}`",
  109. f"- Short unit: `{payload['target']['short_unit']:.6f}`",
  110. "",
  111. "| leg | weight | open_side | latest_candle | latest_entry | latest_exit |",
  112. "| --- | ---: | --- | --- | --- | --- |",
  113. ]
  114. weights = payload["weights"]
  115. for leg in payload["legs"]:
  116. position = leg["open_position"]
  117. lines.append(
  118. f"| `{leg['leg']}` | {weights[leg['leg']]:.6f} | {position['side'] if position else 'flat'} | "
  119. f"{leg['latest_candle_time']} | {leg['latest_entry']['time'] if leg['latest_entry'] else ''} | "
  120. f"{leg['latest_exit']['time'] if leg['latest_exit'] else ''} |"
  121. )
  122. lines.extend(["", "## Intent JSON", "", "```json", json.dumps(payload, indent=2, sort_keys=True), "```", ""])
  123. return "\n".join(lines)
  124. def main() -> int:
  125. payload = build_payload()
  126. REPORT_DIR.mkdir(parents=True, exist_ok=True)
  127. OUTPUT_JSON.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
  128. OUTPUT_MD.write_text(markdown(payload), encoding="utf-8")
  129. print(json.dumps(payload, indent=2, sort_keys=True))
  130. return 0
  131. if __name__ == "__main__":
  132. raise SystemExit(main())