Files
siloqy/prod/clean_arch/violet/shadow_live_factors.py

80 lines
2.4 KiB
Python

"""VIOLET launcher shadow helpers for live BLUE factor sourcing.
These helpers stay separate from the launcher module so they can be unit-tested
without importing the full launcher import chain.
"""
from __future__ import annotations
import logging
import os
LOGGER = logging.getLogger(__name__)
def build_shadow_live_source(
*,
client_factory=None,
selector_factory=None,
source_factory=None,
scan_history_factory=None,
):
"""Create the read-only BLUE live-factor mirror for the shadow path."""
if client_factory is None or selector_factory is None or source_factory is None or scan_history_factory is None:
import hazelcast
from .alpha_wrappers import VioletAssetSelector
from .live_blue_source import LiveBlueScanHistory, source_live_blue_sizing_factors
client_factory = client_factory or (lambda: hazelcast.HazelcastClient(
cluster_name=os.environ.get("HZ_CLUSTER", "dolphin"),
cluster_members=[os.environ.get("HZ_HOST", "localhost:5701")],
))
selector_factory = selector_factory or VioletAssetSelector
source_factory = source_factory or source_live_blue_sizing_factors
scan_history_factory = scan_history_factory or LiveBlueScanHistory
client = client_factory()
return {
"client": client,
"scan_history": scan_history_factory(),
"selector": selector_factory(),
"live_source": source_factory,
}
def shadow_decision_step(
shadow: dict,
payload: dict,
*,
scan_number: int,
now_ns: int,
vel_div: float,
vol_ok: bool,
) -> bool:
"""Run one shadow decision against the live BLUE factor plane."""
shadow["engine"].observe(payload, scan_number)
live_source = shadow.get("live_source")
factors = None
if live_source is not None:
live_result = live_source(
shadow["client"],
scan_history=shadow["scan_history"],
selector=shadow["selector"],
)
shadow["last_live_source"] = live_result
factors = live_result.factors
if factors is None:
return False
decision = shadow["engine"].decide(
now_ns=now_ns,
scan_number=scan_number,
capital=shadow["capital"],
vel_div=vel_div,
vol_ok=vol_ok,
factors=factors,
)
if decision is None:
return False
return shadow["journal"].journal(decision, mono_ns=now_ns)