1"""Deterministic mock provider for dry runs and tests.23It parses the canonical user message (mid, public PV, private signal, holdings) and returns a JSON4decision from a simple, *seeded* value-plus-noise rule — including a configurable share of malformed5or invalid answers to exercise the validation path. Token counts are approximated by word counts and6priced at zero."""7from __future__ import annotations89import json10import re11import time1213import numpy as np1415from . import ProviderResult161718class MockProvider:19 name = "mock"2021 def __init__(self, seed: int = 0, invalid_rate: float = 0.05, latency_s: float = 0.0,22 herd_bias: float = 0.0, **_: object) -> None:23 self.rng = np.random.Generator(np.random.PCG64(seed))24 self.invalid_rate = invalid_rate25 self.latency_s = latency_s26 self.herd_bias = herd_bias # common tilt applied to every mock agent (shared-model correlation knob)27 self.calls = 02829 @staticmethod30 def _grab(pattern: str, text: str, default: float | None = None) -> float | None:31 m = re.search(pattern, text)32 return float(m.group(1)) if m else default3334 def complete(self, system: str, user: str, model: str, **opts) -> ProviderResult:35 self.calls += 136 t0 = time.perf_counter()37 if self.latency_s:38 time.sleep(self.latency_s)39 r = self.rng.random()40 if r < self.invalid_rate / 2:41 text = "I think the market will go up so I will buy." # not JSON42 elif r < self.invalid_rate:43 text = json.dumps({"action": "place", "side": "buy", "order_type": "limit", "quantity": -5,44 "limit_price": None, "cancel_order_ids": [], "confidence": 2.0, "rationale": "bad"})45 else:46 mid = self._grab(r"mid ([0-9]+\.?[0-9]*)", user, None)47 pv = self._grab(r"public information: ([0-9]+\.?[0-9]*)", user, None)48 sig = self._grab(r"estimated at ([+-]?[0-9.]+) cents", user, 0.0) or 0.049 xhat = self._grab(r"public estimate ([+-]?[0-9.]+)", user, 0.0) or 0.050 held = int(self._grab(r"holdings (-?[0-9]+) shares", user, 0) or 0)51 free = self._grab(r"\(free ([0-9]+\.?[0-9]*)\)", user, 0.0) or 0.052 if mid is None or pv is None:53 text = json.dumps({"action": "hold", "side": None, "order_type": None, "quantity": None,54 "limit_price": None, "cancel_order_ids": [], "confidence": 0.5, "rationale": "no data"})55 else:56 v_hat = pv + 0.5 * (sig - xhat) / (1 + 0.0025 - 0.98) / 100.057 m = (v_hat - mid) / mid + self.herd_bias + 0.004 * self.rng.standard_normal()58 if abs(m) < 0.005:59 dec = {"action": "hold", "side": None, "order_type": None, "quantity": None, "limit_price": None,60 "cancel_order_ids": [], "confidence": 0.4, "rationale": "price close to value"}61 else:62 q = int(min(50, max(1, round(abs(m) * 1500))))63 if m > 0:64 q = min(q, int(free // (mid * 1.001))) if mid > 0 else 065 dec = {"action": "place", "side": "buy", "order_type": "limit" if self.rng.random() < 0.6 else "market",66 "quantity": q, "limit_price": round(mid * 1.002, 2), "cancel_order_ids": [], "confidence": 0.6,67 "rationale": f"value {v_hat:.2f} above mid {mid:.2f}"}68 else:69 q = min(q, max(held, 0))70 dec = {"action": "place", "side": "sell", "order_type": "limit" if self.rng.random() < 0.6 else "market",71 "quantity": q, "limit_price": round(mid * 0.998, 2), "cancel_order_ids": [], "confidence": 0.6,72 "rationale": f"value {v_hat:.2f} below mid {mid:.2f}"}73 if dec["quantity"] <= 0:74 dec = {"action": "hold", "side": None, "order_type": None, "quantity": None, "limit_price": None,75 "cancel_order_ids": [], "confidence": 0.3, "rationale": "constrained"}76 text = json.dumps(dec)77 return ProviderResult(text=text, input_tokens=len(system.split()) + len(user.split()), output_tokens=len(text.split()),78 latency_s=time.perf_counter() - t0, model=model)79