Skip to content

Commit 14aa84c

Browse files
committed
Archive runtime reports via QuantPlatformKit
1 parent 6ed953a commit 14aa84c

7 files changed

Lines changed: 115 additions & 7 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ jobs:
7575
TG_TOKEN: ${{ secrets.TG_TOKEN }}
7676
GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }}
7777
NOTIFY_LANG: ${{ vars.NOTIFY_LANG }}
78+
EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }}
7879
VALIDATE_ONLY: ${{ github.event.inputs.validate_only || 'false' }}
7980

8081
- name: 5. Capture execution timestamp

application/cycle_service.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import os
77

8+
from quant_platform_kit.common.runtime_reports import persist_runtime_report
89
from runtime_logging import RuntimeLogContext, emit_runtime_log
910

1011

@@ -259,6 +260,19 @@ def run_live_cycle(
259260
report = execute_cycle(runtime)
260261
output_printer("\n".join(report.get("log_lines", [])))
261262
report_path = report_writer(report)
263+
persisted_local_path = report_path
264+
persisted_gcs_uri = None
265+
try:
266+
persisted = persist_runtime_report(
267+
report,
268+
output_path=report_path,
269+
gcs_prefix_uri=os.getenv("EXECUTION_REPORT_GCS_URI"),
270+
gcp_project_id=os.getenv("GCP_PROJECT_ID") or os.getenv("GOOGLE_CLOUD_PROJECT"),
271+
)
272+
persisted_local_path = persisted.local_path or report_path
273+
persisted_gcs_uri = persisted.gcs_uri
274+
except Exception as persist_exc:
275+
output_printer(f"failed to persist archived execution report: {persist_exc}")
262276
report_status = str(report.get("status", "unknown"))
263277
status_event = {
264278
"ok": "strategy_cycle_completed",
@@ -271,7 +285,8 @@ def run_live_cycle(
271285
severity="INFO" if report_status in {"ok", "aborted"} else "ERROR",
272286
printer=output_printer,
273287
status=report_status,
274-
report_path=report_path,
288+
report_path=persisted_local_path,
289+
report_gcs_uri=persisted_gcs_uri,
275290
total_equity_usdt=report.get("total_equity_usdt"),
276291
trend_equity_usdt=report.get("trend_equity_usdt"),
277292
degraded_mode_level=report.get("degraded_mode_level"),
@@ -282,4 +297,4 @@ def run_live_cycle(
282297
if report.get("status") != "ok" and exit_fn is not None:
283298
exit_fn(1)
284299

285-
return report, report_path
300+
return report, persisted_local_path

requirements-lock.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.0
1+
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.1
22
crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@v0.4.0
33
python-binance==1.0.35
44
pandas==2.3.3
55
numpy==2.0.2
66
requests==2.32.5
77
google-cloud-firestore==2.24.0
8+
google-cloud-storage
89
functions-framework==3.10.1

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.0
1+
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.1
22
crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@v0.4.0
33
python-binance
44
pandas
55
numpy
66
requests
77
google-cloud-firestore
8+
google-cloud-storage
89
functions-framework

runtime_support.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import os
12
from dataclasses import dataclass, field
23
from datetime import datetime, timezone
34
from typing import Any, Callable, Optional
45

6+
from quant_platform_kit.common.runtime_reports import build_runtime_report_base
7+
58

69
@dataclass
710
class ExecutionRuntime:
@@ -31,7 +34,19 @@ def __post_init__(self):
3134

3235

3336
def build_execution_report(runtime):
34-
return {
37+
report = build_runtime_report_base(
38+
platform="binance",
39+
deploy_target=os.getenv("LOG_DEPLOY_TARGET", "vps"),
40+
service_name=os.getenv("SERVICE_NAME", "binance-platform"),
41+
strategy_profile=os.getenv("STRATEGY_PROFILE", "crypto_leader_rotation"),
42+
strategy_domain=os.getenv("STRATEGY_DOMAIN", "crypto"),
43+
run_id=str(runtime.run_id),
44+
run_source="github_actions" if os.getenv("GITHUB_RUN_ID") or os.getenv("GITHUB_ACTIONS") else "runtime",
45+
dry_run=bool(runtime.dry_run),
46+
started_at=runtime.now_utc,
47+
status="ok",
48+
)
49+
report.update({
3550
"status": "ok",
3651
"run_id": str(runtime.run_id),
3752
"dry_run": bool(runtime.dry_run),
@@ -59,7 +74,8 @@ def build_execution_report(runtime):
5974
"circuit_breaker_triggered": False,
6075
"degraded_mode_level": None,
6176
"upstream_pool_symbols": [],
62-
}
77+
})
78+
return report
6379

6480

6581
def append_report_error(report, message, *, stage="runtime"):

tests/test_cycle_service.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,56 @@ def test_run_live_cycle_emits_structured_runtime_events(self):
100100
self.assertEqual(end_log["event"], "strategy_cycle_completed")
101101
self.assertEqual(end_log["status"], "ok")
102102

103+
def test_run_live_cycle_uses_shared_runtime_report_archive(self):
104+
observed = {}
105+
106+
with tempfile.TemporaryDirectory() as tmp_dir:
107+
output_path = os.path.join(tmp_dir, "execution_report.json")
108+
with patch.dict(
109+
os.environ,
110+
{
111+
"STRATEGY_PROFILE": "crypto_leader_rotation",
112+
"SERVICE_NAME": "binance-quant",
113+
"EXECUTION_REPORT_GCS_URI": "gs://demo-bucket/runtime-reports",
114+
"GCP_PROJECT_ID": "demo-project",
115+
},
116+
clear=False,
117+
):
118+
with patch(
119+
"application.cycle_service.persist_runtime_report",
120+
lambda report, **kwargs: observed.update(
121+
{
122+
"status": report["status"],
123+
"kwargs": kwargs,
124+
}
125+
)
126+
or SimpleNamespace(
127+
local_path=kwargs.get("output_path"),
128+
gcs_uri="gs://demo-bucket/runtime-reports/binance/crypto_leader_rotation/2026-04/run-001.json",
129+
),
130+
):
131+
report, persisted_path = run_live_cycle(
132+
runtime_builder=lambda: SimpleNamespace(run_id="run-001", dry_run=False),
133+
execute_cycle=lambda _runtime: {
134+
"status": "ok",
135+
"log_lines": [],
136+
"error_summary": {"errors": []},
137+
},
138+
output_printer=lambda _text: None,
139+
report_writer=lambda report: write_execution_report(
140+
report,
141+
reports_dir=tmp_dir,
142+
filename="execution_report.json",
143+
),
144+
)
145+
146+
self.assertEqual(report["status"], "ok")
147+
self.assertEqual(persisted_path, output_path)
148+
self.assertEqual(observed["status"], "ok")
149+
self.assertEqual(observed["kwargs"]["output_path"], output_path)
150+
self.assertEqual(observed["kwargs"]["gcs_prefix_uri"], "gs://demo-bucket/runtime-reports")
151+
self.assertEqual(observed["kwargs"]["gcp_project_id"], "demo-project")
152+
103153
def test_run_live_cycle_calls_exit_on_error(self):
104154
observed = {"exit_code": None}
105155

tests/test_runtime_support.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1+
import os
2+
import sys
13
import unittest
4+
from pathlib import Path
5+
from unittest.mock import patch
6+
7+
8+
ROOT = Path(__file__).resolve().parents[1]
9+
if str(ROOT) not in sys.path:
10+
sys.path.insert(0, str(ROOT))
11+
QPK_SRC = ROOT.parent / "QuantPlatformKit" / "src"
12+
if str(QPK_SRC) not in sys.path:
13+
sys.path.insert(0, str(QPK_SRC))
214

315
from runtime_support import ExecutionRuntime, build_execution_report, record_gating_event
416

@@ -17,10 +29,22 @@ def test_report_contains_enrichment_fields(self):
1729

1830
def test_report_preserves_existing_fields(self):
1931
runtime = ExecutionRuntime(dry_run=False, run_id="test-002")
20-
report = build_execution_report(runtime)
32+
with patch.dict(
33+
os.environ,
34+
{
35+
"STRATEGY_PROFILE": "crypto_leader_rotation",
36+
"SERVICE_NAME": "binance-runtime",
37+
"LOG_DEPLOY_TARGET": "vps",
38+
},
39+
clear=False,
40+
):
41+
report = build_execution_report(runtime)
2142
self.assertEqual(report["status"], "ok")
2243
self.assertEqual(report["run_id"], "test-002")
2344
self.assertFalse(report["dry_run"])
45+
self.assertEqual(report["schema_version"], "runtime_report.v1")
46+
self.assertEqual(report["platform"], "binance")
47+
self.assertEqual(report["strategy_profile"], "crypto_leader_rotation")
2448
self.assertIn("buy_sell_intents", report)
2549
self.assertIn("log_lines", report)
2650

0 commit comments

Comments
 (0)