Skip to content

Commit c57b91a

Browse files
committed
Archive runtime reports via QuantPlatformKit
1 parent 093f90c commit c57b91a

4 files changed

Lines changed: 112 additions & 2 deletions

File tree

‎.github/workflows/sync-cloud-run-env.yml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
ACCOUNT_REGION: ${{ vars.ACCOUNT_REGION || 'HK' }}
3232
LONGPORT_SECRET_NAME: ${{ vars.LONGPORT_SECRET_NAME }}
3333
NOTIFY_LANG: ${{ vars.NOTIFY_LANG }}
34+
EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }}
3435
GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }}
3536
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
3637
steps:
@@ -134,6 +135,12 @@ jobs:
134135
secret_pairs+=("LONGPORT_APP_SECRET=${LONGPORT_APP_SECRET_SECRET_NAME}:latest")
135136
remove_env_vars+=("LONGPORT_APP_SECRET")
136137
138+
if [ -n "${EXECUTION_REPORT_GCS_URI:-}" ]; then
139+
env_pairs+=("EXECUTION_REPORT_GCS_URI=${EXECUTION_REPORT_GCS_URI}")
140+
else
141+
remove_env_vars+=("EXECUTION_REPORT_GCS_URI")
142+
fi
143+
137144
gcloud_args=(
138145
run services update "${CLOUD_RUN_SERVICE}"
139146
--region "${CLOUD_RUN_REGION}"
@@ -172,6 +179,7 @@ jobs:
172179
ACCOUNT_REGION: ${{ vars.ACCOUNT_REGION || 'SG' }}
173180
LONGPORT_SECRET_NAME: ${{ vars.LONGPORT_SECRET_NAME }}
174181
NOTIFY_LANG: ${{ vars.NOTIFY_LANG }}
182+
EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }}
175183
GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }}
176184
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
177185
steps:
@@ -275,6 +283,12 @@ jobs:
275283
secret_pairs+=("LONGPORT_APP_SECRET=${LONGPORT_APP_SECRET_SECRET_NAME}:latest")
276284
remove_env_vars+=("LONGPORT_APP_SECRET")
277285
286+
if [ -n "${EXECUTION_REPORT_GCS_URI:-}" ]; then
287+
env_pairs+=("EXECUTION_REPORT_GCS_URI=${EXECUTION_REPORT_GCS_URI}")
288+
else
289+
remove_env_vars+=("EXECUTION_REPORT_GCS_URI")
290+
fi
291+
278292
gcloud_args=(
279293
run services update "${CLOUD_RUN_SERVICE}"
280294
--region "${CLOUD_RUN_REGION}"

‎main.py‎

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os
77
import time
88
import traceback
9-
from datetime import datetime
9+
from datetime import datetime, timezone
1010

1111
from flask import Flask
1212

@@ -29,6 +29,12 @@
2929
build_sender,
3030
build_translator,
3131
)
32+
from quant_platform_kit.common.runtime_reports import (
33+
append_runtime_report_error,
34+
build_runtime_report_base,
35+
finalize_runtime_report,
36+
persist_runtime_report,
37+
)
3238
from runtime_logging import RuntimeLogContext, build_run_id, emit_runtime_log
3339
from quant_platform_kit.longbridge import (
3440
build_contexts,
@@ -114,6 +120,35 @@ def log_runtime_event(log_context, event, **fields):
114120
)
115121

116122

123+
def build_execution_report(log_context):
124+
return build_runtime_report_base(
125+
platform=log_context.platform,
126+
deploy_target=log_context.deploy_target,
127+
service_name=log_context.service_name,
128+
strategy_profile=STRATEGY_PROFILE,
129+
strategy_domain=RUNTIME_SETTINGS.strategy_domain,
130+
account_scope=log_context.account_scope,
131+
account_region=log_context.account_region,
132+
run_id=log_context.run_id,
133+
run_source="cloud_run",
134+
started_at=datetime.now(timezone.utc),
135+
summary={
136+
"managed_symbols": list(MANAGED_SYMBOLS),
137+
"account_prefix": ACCOUNT_PREFIX,
138+
},
139+
)
140+
141+
142+
def persist_execution_report(report):
143+
persisted = persist_runtime_report(
144+
report,
145+
base_dir=os.getenv("EXECUTION_REPORT_OUTPUT_DIR"),
146+
gcs_prefix_uri=os.getenv("EXECUTION_REPORT_GCS_URI"),
147+
gcp_project_id=PROJECT_ID,
148+
)
149+
return persisted.gcs_uri or persisted.local_path
150+
151+
117152
def is_filled_status(status):
118153
return notifications_is_filled_status(status)
119154

@@ -228,6 +263,7 @@ def resolve_rebalance_plan(*, indicators, account_state):
228263

229264
def run_strategy():
230265
log_context = RUNTIME_LOG_CONTEXT.with_run(build_run_id())
266+
report = build_execution_report(log_context)
231267
try:
232268
log_runtime_event(
233269
log_context,
@@ -253,6 +289,13 @@ def run_strategy():
253289
"outside_market_hours",
254290
message="Outside market hours; skip execution",
255291
)
292+
finalize_runtime_report(
293+
report,
294+
status="skipped",
295+
diagnostics={
296+
"skip_reason": "market_closed",
297+
},
298+
)
256299
print(with_prefix("Outside market hours; skip."), flush=True)
257300
return
258301
run_rebalance_cycle(
@@ -276,13 +319,21 @@ def run_strategy():
276319
estimate_max_purchase_quantity=estimate_max_purchase_quantity,
277320
submit_order_with_alert=submit_order_with_alert,
278321
)
322+
finalize_runtime_report(report, status="ok")
279323
log_runtime_event(
280324
log_context,
281325
"strategy_cycle_completed",
282326
message="Strategy execution completed",
283327
)
284328

285329
except Exception as exc:
330+
append_runtime_report_error(
331+
report,
332+
stage="strategy_cycle",
333+
message=str(exc),
334+
error_type=type(exc).__name__,
335+
)
336+
finalize_runtime_report(report, status="error")
286337
log_runtime_event(
287338
log_context,
288339
"strategy_cycle_failed",
@@ -294,6 +345,12 @@ def run_strategy():
294345
err = traceback.format_exc()
295346
print(with_prefix(f"Strategy error:\n{err}"), flush=True)
296347
send_tg_message(f"{t('error_title')}\n{err}")
348+
finally:
349+
try:
350+
report_path = persist_execution_report(report)
351+
print(f"execution_report {report_path}", flush=True)
352+
except Exception as persist_exc:
353+
print(f"failed to persist execution report: {persist_exc}", flush=True)
297354

298355
@app.route("/", methods=["POST", "GET"])
299356
def handle_trigger():

‎requirements.txt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
flask
22
gunicorn
3-
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.0
3+
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.1
44
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@v0.7.0
55
pandas
66
requests
77
pytz
88
pandas_market_calendars
99
google-cloud-secret-manager
10+
google-cloud-storage
1011

1112
google-auth
1213
longport

‎tests/test_request_handling.py‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ def run(self, *args, **kwargs):
5252
cloud_run_module = types.ModuleType("entrypoints.cloud_run")
5353
cloud_run_module.is_market_open_now = lambda: True
5454

55+
runtime_config_support_module = types.ModuleType("runtime_config_support")
56+
runtime_config_support_module.load_platform_runtime_settings = lambda **_kwargs: types.SimpleNamespace(
57+
project_id=None,
58+
secret_name="longport_token_hk",
59+
account_prefix="HK",
60+
service_name="longbridge-quant-semiconductor-rotation-income-hk",
61+
strategy_profile="semiconductor_rotation_income",
62+
strategy_domain="us_equity",
63+
account_region="HK",
64+
notify_lang="en",
65+
tg_token=None,
66+
tg_chat_id="shared-chat-id",
67+
)
68+
5569
qpk_longbridge_module = types.ModuleType("quant_platform_kit.longbridge")
5670
qpk_longbridge_module.build_contexts = lambda *args, **kwargs: ("quote-context", "trade-context")
5771
qpk_longbridge_module.calculate_rotation_indicators = lambda *args, **kwargs: {}
@@ -105,6 +119,7 @@ def run(self, *args, **kwargs):
105119
"flask": flask_module,
106120
"requests": requests_module,
107121
"entrypoints.cloud_run": cloud_run_module,
122+
"runtime_config_support": runtime_config_support_module,
108123
"quant_platform_kit.longbridge": qpk_longbridge_module,
109124
"google": google_module,
110125
"google.auth": google_auth_module,
@@ -191,6 +206,29 @@ def test_run_strategy_emits_structured_runtime_events(self):
191206
)
192207
self.assertTrue(all(run_id == "run-001" for run_id, _event, _fields in observed))
193208

209+
def test_run_strategy_persists_machine_readable_report(self):
210+
module = load_module()
211+
observed_reports = []
212+
213+
module.build_run_id = lambda: "run-001"
214+
module.emit_runtime_log = lambda *args, **kwargs: None
215+
module.is_market_open_now = lambda: True
216+
module.run_rebalance_cycle = lambda **_kwargs: None
217+
module.persist_execution_report = (
218+
lambda report: observed_reports.append(dict(report)) or "/tmp/runtime-report.json"
219+
)
220+
221+
module.run_strategy()
222+
223+
self.assertEqual(len(observed_reports), 1)
224+
report = observed_reports[0]
225+
self.assertEqual(report["platform"], "longbridge")
226+
self.assertEqual(report["run_source"], "cloud_run")
227+
self.assertEqual(report["status"], "ok")
228+
self.assertEqual(report["strategy_profile"], module.STRATEGY_PROFILE)
229+
self.assertEqual(report["account_scope"], module.ACCOUNT_REGION)
230+
self.assertEqual(report["summary"]["managed_symbols"], list(module.MANAGED_SYMBOLS))
231+
194232

195233
if __name__ == "__main__":
196234
unittest.main()

0 commit comments

Comments
 (0)