Skip to content

Commit 454ec64

Browse files
authored
Wire artifact fallback settings
Pass opt-in last-valid fallback settings through Firstrade runtime/config and Cloud Run env sync, and pin shared strategy packages to the artifact fallback capable versions.
1 parent b2c5955 commit 454ec64

6 files changed

Lines changed: 100 additions & 3 deletions

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ jobs:
6464
FIRSTRADE_SMOKE_SYMBOL: ${{ vars.FIRSTRADE_SMOKE_SYMBOL }}
6565
FIRSTRADE_FEATURE_SNAPSHOT_PATH: ${{ vars.FIRSTRADE_FEATURE_SNAPSHOT_PATH }}
6666
FIRSTRADE_FEATURE_SNAPSHOT_MANIFEST_PATH: ${{ vars.FIRSTRADE_FEATURE_SNAPSHOT_MANIFEST_PATH }}
67+
FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_MODE: ${{ vars.FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_MODE }}
68+
FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_CACHE_DIR: ${{ vars.FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_CACHE_DIR }}
69+
FIRSTRADE_FEATURE_SNAPSHOT_MAX_STALE_DAYS: ${{ vars.FIRSTRADE_FEATURE_SNAPSHOT_MAX_STALE_DAYS }}
6770
FIRSTRADE_GCS_STATE_BUCKET: ${{ vars.FIRSTRADE_GCS_STATE_BUCKET }}
6871
FIRSTRADE_PERSIST_ACCOUNT_SNAPSHOT: ${{ vars.FIRSTRADE_PERSIST_ACCOUNT_SNAPSHOT }}
6972
FIRSTRADE_PERSIST_STRATEGY_RUNS: ${{ vars.FIRSTRADE_PERSIST_STRATEGY_RUNS }}
@@ -78,6 +81,8 @@ jobs:
7881
FIRSTRADE_MARKET_SIGNAL_CONSUMPTION_AUDIT_URI: ${{ vars.FIRSTRADE_MARKET_SIGNAL_CONSUMPTION_AUDIT_URI }}
7982
FIRSTRADE_MARKET_SIGNAL_CACHE_DIR: ${{ vars.FIRSTRADE_MARKET_SIGNAL_CACHE_DIR }}
8083
FIRSTRADE_MARKET_SIGNAL_REQUIRED: ${{ vars.FIRSTRADE_MARKET_SIGNAL_REQUIRED }}
84+
FIRSTRADE_MARKET_SIGNAL_FALLBACK_MODE: ${{ vars.FIRSTRADE_MARKET_SIGNAL_FALLBACK_MODE }}
85+
FIRSTRADE_MARKET_SIGNAL_MAX_STALE_DAYS: ${{ vars.FIRSTRADE_MARKET_SIGNAL_MAX_STALE_DAYS }}
8186
STRATEGY_PLUGIN_ALERT_CHANNELS: ${{ vars.STRATEGY_PLUGIN_ALERT_CHANNELS }}
8287
STRATEGY_PLUGIN_ALERT_EMAIL_RECIPIENTS: ${{ vars.STRATEGY_PLUGIN_ALERT_EMAIL_RECIPIENTS }}
8388
STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_EMAIL: ${{ vars.STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_EMAIL }}
@@ -540,13 +545,18 @@ jobs:
540545
add_optional_env FIRSTRADE_SMOKE_SYMBOL
541546
add_optional_env FIRSTRADE_FEATURE_SNAPSHOT_PATH
542547
add_optional_env FIRSTRADE_FEATURE_SNAPSHOT_MANIFEST_PATH
548+
add_optional_env FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_MODE
549+
add_optional_env FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_CACHE_DIR
550+
add_optional_env FIRSTRADE_FEATURE_SNAPSHOT_MAX_STALE_DAYS
543551
add_optional_env FIRSTRADE_STRATEGY_CONFIG_PATH
544552
add_optional_env FIRSTRADE_STRATEGY_PLUGIN_MOUNTS_JSON
545553
add_optional_env FIRSTRADE_MARKET_SIGNAL_HANDOFF_INDEX_URI
546554
add_optional_env FIRSTRADE_MARKET_SIGNAL_HANDOFF_MANIFEST_URI
547555
add_optional_env FIRSTRADE_MARKET_SIGNAL_CONSUMPTION_AUDIT_URI
548556
add_optional_env FIRSTRADE_MARKET_SIGNAL_CACHE_DIR
549557
add_optional_env FIRSTRADE_MARKET_SIGNAL_REQUIRED
558+
add_optional_env FIRSTRADE_MARKET_SIGNAL_FALLBACK_MODE
559+
add_optional_env FIRSTRADE_MARKET_SIGNAL_MAX_STALE_DAYS
550560
add_optional_env STRATEGY_PLUGIN_ALERT_CHANNELS
551561
add_optional_env STRATEGY_PLUGIN_ALERT_EMAIL_RECIPIENTS
552562
add_optional_env STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_EMAIL

market_signal_runtime.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,15 @@ def resolve_external_market_signal_inputs(
4444
cache_dir=_market_signal_cache_dir(runtime_settings),
4545
as_of=_market_signal_as_of(as_of),
4646
client_factory=client_factory,
47+
fallback_mode=_market_signal_fallback_mode(runtime_settings),
48+
fallback_max_stale_days=_market_signal_max_stale_days(runtime_settings),
4749
)
4850
logger(
4951
"market_signal_inputs_loaded | "
5052
f"profile={strategy_profile} reference_type={metadata.get('reference_type')} "
5153
f"source_uri={metadata.get('source_uri') or reference} "
52-
f"materialized_count={metadata.get('materialized_count')}"
54+
f"materialized_count={metadata.get('materialized_count')} "
55+
f"fallback_used={bool(metadata.get('artifact_fallback_used'))}"
5356
)
5457
return dict(market_inputs)
5558

@@ -81,6 +84,17 @@ def _market_signal_cache_dir(runtime_settings: Any) -> Path:
8184
return Path(configured or DEFAULT_MARKET_SIGNAL_CACHE_DIR)
8285

8386

87+
def _market_signal_fallback_mode(runtime_settings: Any) -> str:
88+
return _optional_string(getattr(runtime_settings, "market_signal_fallback_mode", None)) or "none"
89+
90+
91+
def _market_signal_max_stale_days(runtime_settings: Any) -> int:
92+
value = getattr(runtime_settings, "market_signal_max_stale_days", None)
93+
if value is None or str(value).strip() == "":
94+
return 3
95+
return max(0, int(value))
96+
97+
8498
def _market_signal_as_of(value: Any) -> str | None:
8599
if value is None:
86100
return None

runtime_config_support.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,13 @@ class PlatformRuntimeSettings:
6464
market_signal_consumption_audit_uri: str | None = None
6565
market_signal_cache_dir: str | None = None
6666
market_signal_required: bool = False
67+
market_signal_fallback_mode: str | None = None
68+
market_signal_max_stale_days: int | None = None
6769
feature_snapshot_path: str | None = None
6870
feature_snapshot_manifest_path: str | None = None
71+
feature_snapshot_fallback_mode: str | None = None
72+
feature_snapshot_fallback_cache_dir: str | None = None
73+
feature_snapshot_fallback_max_stale_days: int | None = None
6974
strategy_config_path: str | None = None
7075
strategy_config_source: str | None = None
7176
strategy_plugin_mounts_json: str | None = None
@@ -207,8 +212,36 @@ def load_platform_runtime_settings(
207212
"false",
208213
)
209214
),
215+
market_signal_fallback_mode=_first_non_empty(
216+
os.getenv("FIRSTRADE_MARKET_SIGNAL_FALLBACK_MODE"),
217+
os.getenv("MARKET_SIGNAL_FALLBACK_MODE"),
218+
),
219+
market_signal_max_stale_days=_optional_int(
220+
_first_non_empty(
221+
os.getenv("FIRSTRADE_MARKET_SIGNAL_MAX_STALE_DAYS"),
222+
os.getenv("FIRSTRADE_MARKET_SIGNAL_FALLBACK_MAX_STALE_DAYS"),
223+
os.getenv("MARKET_SIGNAL_MAX_STALE_DAYS"),
224+
os.getenv("MARKET_SIGNAL_FALLBACK_MAX_STALE_DAYS"),
225+
)
226+
),
210227
feature_snapshot_path=runtime_paths.feature_snapshot_path,
211228
feature_snapshot_manifest_path=runtime_paths.feature_snapshot_manifest_path,
229+
feature_snapshot_fallback_mode=_first_non_empty(
230+
os.getenv("FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_MODE"),
231+
os.getenv("FEATURE_SNAPSHOT_FALLBACK_MODE"),
232+
),
233+
feature_snapshot_fallback_cache_dir=_first_non_empty(
234+
os.getenv("FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_CACHE_DIR"),
235+
os.getenv("FEATURE_SNAPSHOT_FALLBACK_CACHE_DIR"),
236+
),
237+
feature_snapshot_fallback_max_stale_days=_optional_int(
238+
_first_non_empty(
239+
os.getenv("FIRSTRADE_FEATURE_SNAPSHOT_MAX_STALE_DAYS"),
240+
os.getenv("FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_MAX_STALE_DAYS"),
241+
os.getenv("FEATURE_SNAPSHOT_MAX_STALE_DAYS"),
242+
os.getenv("FEATURE_SNAPSHOT_FALLBACK_MAX_STALE_DAYS"),
243+
)
244+
),
212245
strategy_config_path=runtime_paths.strategy_config_path,
213246
strategy_config_source=runtime_paths.strategy_config_source,
214247
strategy_plugin_mounts_json=(
@@ -400,6 +433,13 @@ def _first_non_empty(*raw_values: str | None) -> str | None:
400433
return None
401434

402435

436+
def _optional_int(raw_value: str | None) -> int | None:
437+
value = str(raw_value or "").strip()
438+
if not value:
439+
return None
440+
return int(value)
441+
442+
403443
def _split_env_list(raw_value: str | None) -> tuple[str, ...]:
404444
if raw_value is None:
405445
return ()

strategy_runtime.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ def _evaluate_feature_snapshot_strategy(
123123
runtime_settings=FeatureSnapshotRuntimeSettings(
124124
feature_snapshot_path=self.runtime_settings.feature_snapshot_path,
125125
feature_snapshot_manifest_path=self.runtime_settings.feature_snapshot_manifest_path,
126+
feature_snapshot_fallback_mode=self.runtime_settings.feature_snapshot_fallback_mode,
127+
feature_snapshot_fallback_cache_dir=self.runtime_settings.feature_snapshot_fallback_cache_dir,
128+
feature_snapshot_fallback_max_stale_days=(
129+
self.runtime_settings.feature_snapshot_fallback_max_stale_days
130+
),
126131
strategy_config_path=self.runtime_settings.strategy_config_path,
127132
strategy_config_source=self.runtime_settings.strategy_config_source,
128133
dry_run_only=self.runtime_settings.dry_run_only,

tests/test_market_signal_runtime.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,27 @@ def test_ibit_required_reference_missing_raises():
4545
def test_ibit_handoff_index_reference_is_extracted(monkeypatch, tmp_path):
4646
calls: dict[str, object] = {}
4747

48-
def fake_extract(reference, *, reference_type, consumer, cache_dir, as_of, client_factory=None):
49-
calls["extract"] = (reference, reference_type, consumer, cache_dir, as_of, client_factory)
48+
def fake_extract(
49+
reference,
50+
*,
51+
reference_type,
52+
consumer,
53+
cache_dir,
54+
as_of,
55+
client_factory=None,
56+
fallback_mode=None,
57+
fallback_max_stale_days=None,
58+
):
59+
calls["extract"] = (
60+
reference,
61+
reference_type,
62+
consumer,
63+
cache_dir,
64+
as_of,
65+
client_factory,
66+
fallback_mode,
67+
fallback_max_stale_days,
68+
)
5069
return {"derived_indicators": {"BTC": {"mvrv_z_score": 1.0}}}, {
5170
"reference_type": reference_type,
5271
"source_uri": reference,
@@ -62,6 +81,8 @@ def fake_extract(reference, *, reference_type, consumer, cache_dir, as_of, clien
6281
market_signal_handoff_index_uri="gs://signals/platform_handoffs/index.json",
6382
market_signal_cache_dir=str(tmp_path),
6483
market_signal_required=False,
84+
market_signal_fallback_mode="last_valid",
85+
market_signal_max_stale_days=5,
6586
)
6687

6788
assert market_signal_runtime.resolve_external_market_signal_inputs(
@@ -79,4 +100,6 @@ def fake_extract(reference, *, reference_type, consumer, cache_dir, as_of, clien
79100
tmp_path,
80101
"2026-06-19",
81102
object,
103+
"last_valid",
104+
5,
82105
)

tests/test_sync_cloud_run_env_workflow.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ def test_sync_cloud_run_env_workflow_syncs_strategy_plugin_alert_settings():
5454
"FIRSTRADE_MARKET_SIGNAL_CONSUMPTION_AUDIT_URI",
5555
"FIRSTRADE_MARKET_SIGNAL_CACHE_DIR",
5656
"FIRSTRADE_MARKET_SIGNAL_REQUIRED",
57+
"FIRSTRADE_MARKET_SIGNAL_FALLBACK_MODE",
58+
"FIRSTRADE_MARKET_SIGNAL_MAX_STALE_DAYS",
59+
"FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_MODE",
60+
"FIRSTRADE_FEATURE_SNAPSHOT_FALLBACK_CACHE_DIR",
61+
"FIRSTRADE_FEATURE_SNAPSHOT_MAX_STALE_DAYS",
5762
):
5863
assert f"{name}: ${{{{ vars.{name} }}}}" in workflow
5964
assert f"add_optional_env {name}" in workflow

0 commit comments

Comments
 (0)