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 docs/operator_runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,20 @@ It is responsible for:

- consuming upstream live-pool artifacts and Firestore summary payloads
- validating freshness, contract shape, and fallback eligibility
- preserving the accepted upstream `symbols` order when passing the pool into strategy code
- executing orders, persisting runtime state, and emitting minimal operator alerts

It is not responsible for:

- monthly research reporting
- monthly live-pool selection, ranking, or local reranking
- upstream release summaries or review packages
- maintaining a second copy of the upstream publish narrative

## Normal Live Flow

1. Load runtime credentials and Firestore state.
2. Resolve the upstream strategy artifact in this order:
2. Resolve the upstream ordered strategy artifact in this order:
- fresh upstream Firestore payload
- last known good upstream payload from state
- validated local upstream file fallback
Expand All @@ -58,13 +60,13 @@ It is not responsible for:
Runtime output should stay operational:

- current upstream source and degraded status
- upstream official pool and current local execution pool logged as separate concepts
- upstream official pool order and current local execution pool logged as separate concepts
- current execution targets and intents
- explicit gating / no-trade reasons and side-effect suppression counts
- zero-trade diagnostics grouped by BTC core / trend sleeve and gate
- exceptions, circuit breakers, and alert-worthy failures

The monthly execution pool is locked to the accepted upstream `version` / `as_of_date`. It is rebuilt when upstream release metadata changes and otherwise reused across cycles.
The monthly execution pool is locked to the accepted upstream `version` / `as_of_date`. It refreshes when upstream release metadata changes and otherwise reuses the accepted ordered artifact pool; BinancePlatform should not rebuild the monthly pool with local ranking logic.

## Runtime Trigger Model

Expand Down
30 changes: 30 additions & 0 deletions tests/test_trend_pool_loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@


def install_test_stubs():
if "numpy" not in sys.modules:
numpy_module = types.ModuleType("numpy")
numpy_module.arange = lambda *args, **kwargs: []
numpy_module.exp = lambda value: value
numpy_module.log = lambda value: value
sys.modules["numpy"] = numpy_module

if "pandas" not in sys.modules:
pandas_module = types.ModuleType("pandas")
pandas_module.isna = lambda value: value is None
sys.modules["pandas"] = pandas_module

if "binance" not in sys.modules:
binance_module = types.ModuleType("binance")
client_module = types.ModuleType("binance.client")
Expand Down Expand Up @@ -119,6 +131,24 @@ def test_validate_trend_pool_payload_rejects_stale_payload(self):
self.assertFalse(result["ok"])
self.assertIn("stale", " ".join(result["errors"]))

def test_validate_trend_pool_payload_preserves_ordered_symbols_in_symbol_map(self):
payload = build_payload()
payload["symbols"] = ["BCHUSDT", "ETHUSDT", "LTCUSDT", "SOLUSDT", "XRPUSDT"]

result = main.validate_trend_pool_payload(
payload,
source_label="test",
now_utc=datetime(2026, 3, 14, tzinfo=timezone.utc),
max_age_days=30,
acceptable_modes=["core_major"],
expected_pool_size=5,
enforce_freshness=True,
)

self.assertTrue(result["ok"])
self.assertEqual(list(result["symbol_map"]), payload["symbols"])
self.assertEqual(list(result["payload"]["symbol_map"]), payload["symbols"])

def test_strategy_artifact_env_aliases_override_legacy_trend_pool_settings(self):
with patch.dict(
os.environ,
Expand Down
10 changes: 8 additions & 2 deletions trend_pool_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,19 @@ def validate_trend_pool_payload(
source_project = "unknown"
warnings.append(t("source_project_missing_unknown"))

ordered_symbol_map = {
symbol: symbol_map[symbol]
for symbol in symbols
if symbol in symbol_map
}

normalized_payload = {
"as_of_date": as_of_date.isoformat() if as_of_date is not None else "",
"version": version,
"mode": mode,
"pool_size": len(symbols),
"symbols": symbols,
"symbol_map": symbol_map,
"symbol_map": ordered_symbol_map,
"source_project": source_project,
}

Expand All @@ -220,7 +226,7 @@ def validate_trend_pool_payload(
"warnings": warnings,
"source_label": str(source_label),
"payload": normalized_payload,
"symbol_map": symbol_map,
"symbol_map": ordered_symbol_map,
"symbols": symbols,
"pool_size": len(symbols),
"as_of_date": normalized_payload["as_of_date"],
Expand Down