203 lines
7.2 KiB
Python
203 lines
7.2 KiB
Python
|
|
"""Fuzz generation for the mock stack."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from dataclasses import dataclass
|
||
|
|
from datetime import datetime, timedelta, timezone
|
||
|
|
import math
|
||
|
|
import random
|
||
|
|
from typing import Iterable, Iterator, List, Optional
|
||
|
|
|
||
|
|
from prod.clean_arch.ports.data_feed import MarketSnapshot
|
||
|
|
from prod.clean_arch.dita import DecisionConfig, DecisionEngine, IntentEngine, DitaObservabilityNamespace
|
||
|
|
|
||
|
|
from .mock_stack import ChaosProfile, MockClickHouse, MockHazelcast, MockLogSink, MockNetwork, MockTradingStack, NetworkProfile
|
||
|
|
|
||
|
|
|
||
|
|
DEFAULT_SYMBOLS = (
|
||
|
|
"BTCUSDT",
|
||
|
|
"ETHUSDT",
|
||
|
|
"BNBUSDT",
|
||
|
|
"SOLUSDT",
|
||
|
|
"XRPUSDT",
|
||
|
|
"ADAUSDT",
|
||
|
|
"DOGEUSDT",
|
||
|
|
"TRXUSDT",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class FuzzConfig:
|
||
|
|
"""High-volume fuzz config."""
|
||
|
|
|
||
|
|
transactions: int = 1_000_000
|
||
|
|
seed: int = 42
|
||
|
|
bad_input_rate: float = 0.01
|
||
|
|
network_drop_rate: float = 0.001
|
||
|
|
network_duplicate_rate: float = 0.001
|
||
|
|
price_sigma: float = 0.015
|
||
|
|
symbol_pool: tuple[str, ...] = DEFAULT_SYMBOLS
|
||
|
|
capture_limit: int = 2_000
|
||
|
|
aggressive: bool = False
|
||
|
|
hang_entry_rate: float = 0.0
|
||
|
|
hang_exit_rate: float = 0.0
|
||
|
|
stale_account_rate: float = 0.0
|
||
|
|
duplicate_terminal_rate: float = 0.0
|
||
|
|
missing_terminal_rate: float = 0.0
|
||
|
|
orphan_close_rate: float = 0.0
|
||
|
|
reorder_account_rate: float = 0.0
|
||
|
|
runtime_namespace: str = "pink"
|
||
|
|
anomaly_sensor_key: str | None = None
|
||
|
|
mirror_legacy_sensor_key: bool = False
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class FuzzReport:
|
||
|
|
"""Summary of a fuzz run."""
|
||
|
|
|
||
|
|
transactions: int
|
||
|
|
capital_final: float
|
||
|
|
equity_final: float
|
||
|
|
open_notional_final: float
|
||
|
|
policy_events: int
|
||
|
|
trade_events: int
|
||
|
|
account_events: int
|
||
|
|
logs_emitted: int
|
||
|
|
network_dropped: int
|
||
|
|
network_duplicated: int
|
||
|
|
anomaly_counts: dict
|
||
|
|
anomaly_origin_counts: dict
|
||
|
|
injected_anomaly_counts: dict
|
||
|
|
emergent_anomaly_counts: dict
|
||
|
|
anomaly_sensor_payload: dict
|
||
|
|
anomaly_samples: List[dict]
|
||
|
|
sample_policy_events: List[dict]
|
||
|
|
|
||
|
|
|
||
|
|
def generate_snapshot_stream(cfg: FuzzConfig) -> Iterator[MarketSnapshot]:
|
||
|
|
"""Stream randomized snapshots with occasional poison inputs."""
|
||
|
|
rng = random.Random(cfg.seed)
|
||
|
|
price_map = {sym: 100.0 + 25.0 * i for i, sym in enumerate(cfg.symbol_pool)}
|
||
|
|
ts = datetime.now(timezone.utc)
|
||
|
|
|
||
|
|
for i in range(cfg.transactions):
|
||
|
|
symbol = rng.choice(cfg.symbol_pool)
|
||
|
|
base = price_map[symbol]
|
||
|
|
drift = rng.gauss(0.0, cfg.price_sigma)
|
||
|
|
price = max(0.01, base * (1.0 + drift))
|
||
|
|
price_map[symbol] = price
|
||
|
|
|
||
|
|
vdiv = rng.uniform(-0.2, 0.1)
|
||
|
|
irp = rng.uniform(-1.0, 1.0)
|
||
|
|
eigenvalues = [1.0, 0.9, 0.8]
|
||
|
|
|
||
|
|
if rng.random() < cfg.bad_input_rate:
|
||
|
|
poison = rng.choice(["nan", "inf", "-inf", "zero", "none", "unicode"])
|
||
|
|
if poison == "nan":
|
||
|
|
price = float("nan")
|
||
|
|
elif poison == "inf":
|
||
|
|
price = float("inf")
|
||
|
|
elif poison == "-inf":
|
||
|
|
price = float("-inf")
|
||
|
|
elif poison == "zero":
|
||
|
|
price = 0.0
|
||
|
|
elif poison == "none":
|
||
|
|
vdiv = None # type: ignore[assignment]
|
||
|
|
elif poison == "unicode":
|
||
|
|
symbol = symbol + "🐬"
|
||
|
|
|
||
|
|
yield MarketSnapshot(
|
||
|
|
timestamp=ts + timedelta(milliseconds=i),
|
||
|
|
symbol=symbol,
|
||
|
|
price=price,
|
||
|
|
bid=price * 0.9995 if math.isfinite(price) else price,
|
||
|
|
ask=price * 1.0005 if math.isfinite(price) else price,
|
||
|
|
eigenvalues=eigenvalues,
|
||
|
|
eigenvectors=None,
|
||
|
|
velocity_divergence=vdiv,
|
||
|
|
irp_alignment=irp,
|
||
|
|
scan_number=i,
|
||
|
|
source="fuzz",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def fuzz_stack(cfg: Optional[FuzzConfig] = None) -> FuzzReport:
|
||
|
|
"""Run the full mocked stack against fuzzed market snapshots."""
|
||
|
|
cfg = cfg or FuzzConfig()
|
||
|
|
if cfg.aggressive:
|
||
|
|
cfg = FuzzConfig(
|
||
|
|
transactions=cfg.transactions,
|
||
|
|
seed=cfg.seed,
|
||
|
|
bad_input_rate=max(cfg.bad_input_rate, 0.08),
|
||
|
|
network_drop_rate=max(cfg.network_drop_rate, 0.03),
|
||
|
|
network_duplicate_rate=max(cfg.network_duplicate_rate, 0.03),
|
||
|
|
price_sigma=max(cfg.price_sigma, 0.05),
|
||
|
|
symbol_pool=cfg.symbol_pool,
|
||
|
|
capture_limit=cfg.capture_limit,
|
||
|
|
aggressive=cfg.aggressive,
|
||
|
|
hang_entry_rate=max(cfg.hang_entry_rate, 0.01),
|
||
|
|
hang_exit_rate=max(cfg.hang_exit_rate, 0.03),
|
||
|
|
stale_account_rate=max(cfg.stale_account_rate, 0.03),
|
||
|
|
duplicate_terminal_rate=max(cfg.duplicate_terminal_rate, 0.03),
|
||
|
|
missing_terminal_rate=max(cfg.missing_terminal_rate, 0.015),
|
||
|
|
orphan_close_rate=max(cfg.orphan_close_rate, 0.015),
|
||
|
|
reorder_account_rate=max(cfg.reorder_account_rate, 0.02),
|
||
|
|
)
|
||
|
|
policy = DecisionEngine(DecisionConfig())
|
||
|
|
intent = IntentEngine(DecisionConfig())
|
||
|
|
stack = MockTradingStack(
|
||
|
|
decision_engine=policy,
|
||
|
|
intent_engine=intent,
|
||
|
|
capital=25_000.0,
|
||
|
|
runtime_namespace=cfg.runtime_namespace,
|
||
|
|
strategy_namespace=cfg.runtime_namespace,
|
||
|
|
event_namespace=cfg.runtime_namespace,
|
||
|
|
observability=DitaObservabilityNamespace(
|
||
|
|
runtime_namespace=cfg.runtime_namespace,
|
||
|
|
anomaly_sensor_key=cfg.anomaly_sensor_key,
|
||
|
|
mirror_legacy_key=cfg.mirror_legacy_sensor_key,
|
||
|
|
state_map=f"DOLPHIN_STATE_{cfg.runtime_namespace.upper()}" if cfg.runtime_namespace.lower() != "pink" else "DOLPHIN_STATE_PINK",
|
||
|
|
),
|
||
|
|
network=MockNetwork(
|
||
|
|
NetworkProfile(
|
||
|
|
drop_rate=cfg.network_drop_rate,
|
||
|
|
duplicate_rate=cfg.network_duplicate_rate,
|
||
|
|
),
|
||
|
|
seed=cfg.seed,
|
||
|
|
),
|
||
|
|
hazelcast=MockHazelcast(),
|
||
|
|
clickhouse=MockClickHouse(capture_limit=cfg.capture_limit),
|
||
|
|
logs=MockLogSink(capture_limit=cfg.capture_limit),
|
||
|
|
chaos=ChaosProfile(
|
||
|
|
hang_entry_rate=cfg.hang_entry_rate,
|
||
|
|
hang_exit_rate=cfg.hang_exit_rate,
|
||
|
|
stale_account_rate=cfg.stale_account_rate,
|
||
|
|
duplicate_terminal_rate=cfg.duplicate_terminal_rate,
|
||
|
|
missing_terminal_rate=cfg.missing_terminal_rate,
|
||
|
|
orphan_close_rate=cfg.orphan_close_rate,
|
||
|
|
reorder_account_rate=cfg.reorder_account_rate,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
for snapshot in generate_snapshot_stream(cfg):
|
||
|
|
stack.step(snapshot)
|
||
|
|
result = stack.summary(cfg.transactions)
|
||
|
|
return FuzzReport(
|
||
|
|
transactions=result.steps,
|
||
|
|
capital_final=result.capital_final,
|
||
|
|
equity_final=result.equity_final,
|
||
|
|
open_notional_final=result.open_notional_final,
|
||
|
|
policy_events=result.decision_events,
|
||
|
|
trade_events=result.trade_events,
|
||
|
|
account_events=result.account_events,
|
||
|
|
logs_emitted=result.logs_emitted,
|
||
|
|
network_dropped=result.network_dropped,
|
||
|
|
network_duplicated=result.network_duplicated,
|
||
|
|
anomaly_counts=result.anomaly_counts,
|
||
|
|
anomaly_origin_counts=result.anomaly_origin_counts,
|
||
|
|
injected_anomaly_counts=result.injected_anomaly_counts,
|
||
|
|
emergent_anomaly_counts=result.emergent_anomaly_counts,
|
||
|
|
anomaly_sensor_payload=result.anomaly_sensor_payload,
|
||
|
|
anomaly_samples=result.anomaly_samples,
|
||
|
|
sample_policy_events=result.sample_policy_events,
|
||
|
|
)
|