Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -747,10 +747,12 @@ def run_strategy():
)
if not _runtime_target_enabled_env():
return jsonify({"ok": True, "status": "skipped", "skip_reason": "runtime_target_disabled"}), 200
skip_for_market, skip_payload = _should_skip_for_market_hours()
if skip_for_market and skip_payload is not None:
return jsonify(skip_payload), 200
try:
if not _runtime_settings().runtime_target_enabled:
return jsonify({"ok": True, "status": "skipped", "skip_reason": "runtime_target_disabled"}), 200
skip_for_market, skip_payload = _should_skip_for_market_hours()
if skip_for_market and skip_payload is not None:
return jsonify(skip_payload), 200
result = _run_strategy_cycle_with_report()
return jsonify(result), _strategy_result_http_status(result)
except (FirstradePlatformError, EnvironmentError, ValueError) as exc:
Expand Down
53 changes: 53 additions & 0 deletions tests/test_request_handling.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import json

import pytest

pytest.importorskip("flask")
Expand All @@ -9,6 +11,12 @@

@pytest.fixture(autouse=True)
def _assume_market_open_for_http_tests(monkeypatch, request):
monkeypatch.delenv("QSL_RUNTIME_TARGET_JSON", raising=False)
monkeypatch.setenv("RUNTIME_TARGET_JSON", json.dumps({
"platform_id": "firstrade", "strategy_profile": "ibit_smart_dca", "dry_run_only": False,
}))
monkeypatch.setattr(main, "get_project_id", lambda: "test-project")
monkeypatch.setattr("runtime_config_support._get_credential", lambda *_args: None)
if request.node.name == "test_run_endpoint_skips_when_market_closed":
return
monkeypatch.setattr(main, "_should_skip_for_market_hours", lambda: (False, None))
Expand Down Expand Up @@ -197,6 +205,51 @@ def test_run_endpoint_calls_strategy_cycle_when_gate_enabled(monkeypatch):
assert response.get_json() == {"ok": True, "action_done": False}


@pytest.mark.parametrize("enabled", [False, True])
@pytest.mark.parametrize("state", [None, "ACTIVE_LKG", "ROLLBACK_LKG", "RECONCILE_ONLY", "PAUSED"])
def test_run_endpoint_enforces_runtime_continuity_before_runtime_calls(monkeypatch, enabled, state):
from quant_platform_kit.common.live_continuity import runtime_target_fingerprint

target = {
"platform_id": "firstrade",
"strategy_profile": "ibit_smart_dca",
"dry_run_only": False,
"execution_mode": "live",
}
if state is not None:
target["live_continuity"] = {
"state": state,
"baseline_kind": "legacy_authorized",
"baseline_id": "test-baseline",
"baseline_target_sha256": runtime_target_fingerprint(target),
"captured_at": "2026-08-30",
}
monkeypatch.setenv("RUNTIME_TARGET_JSON", json.dumps(target))
monkeypatch.setenv("FIRSTRADE_RUN_STRATEGY_ON_HTTP", "true")
monkeypatch.setenv("FIRSTRADE_DRY_RUN_ONLY", "false")
monkeypatch.setenv("RUNTIME_TARGET_ENABLED", str(enabled).lower())
allowed = enabled and state in {None, "ACTIVE_LKG", "ROLLBACK_LKG"}
calls = []

def strategy_cycle(**_kwargs):
calls.append("strategy")
return {"ok": True, "action_done": False}

def fail_broker(*_args, **_kwargs):
pytest.fail("HTTP admission test must not construct a broker client")

monkeypatch.setattr(main, "_run_strategy_cycle_with_report", strategy_cycle)
monkeypatch.setattr(main, "FirstradeBrokerClient", fail_broker)
response = main.app.test_client().post("/run")

assert response.status_code == 200
assert calls == (["strategy"] if allowed else [])
assert response.get_json() == (
{"ok": True, "action_done": False} if allowed else
{"ok": True, "status": "skipped", "skip_reason": "runtime_target_disabled"}
)


def test_dry_run_admission_blocks_before_strategy_cycle(monkeypatch):
monkeypatch.setenv("QSL_PAPER_ADMISSION_ENABLED", "true")
monkeypatch.setattr(
Expand Down