"""Deterministic mock provider for dry runs and tests. It parses the canonical user message (mid, public PV, private signal, holdings) and returns a JSON decision from a simple, *seeded* value-plus-noise rule — including a configurable share of malformed or invalid answers to exercise the validation path. Token counts are approximated by word counts and priced at zero.""" from __future__ import annotations import json import re import time import numpy as np from . import ProviderResult class MockProvider: name = "mock" def __init__(self, seed: int = 0, invalid_rate: float = 0.05, latency_s: float = 0.0, herd_bias: float = 0.0, **_: object) -> None: self.rng = np.random.Generator(np.random.PCG64(seed)) self.invalid_rate = invalid_rate self.latency_s = latency_s self.herd_bias = herd_bias # common tilt applied to every mock agent (shared-model correlation knob) self.calls = 0 @staticmethod def _grab(pattern: str, text: str, default: float | None = None) -> float | None: m = re.search(pattern, text) return float(m.group(1)) if m else default def complete(self, system: str, user: str, model: str, **opts) -> ProviderResult: self.calls += 1 t0 = time.perf_counter() if self.latency_s: time.sleep(self.latency_s) r = self.rng.random() if r < self.invalid_rate / 2: text = "I think the market will go up so I will buy." # not JSON elif r < self.invalid_rate: text = json.dumps({"action": "place", "side": "buy", "order_type": "limit", "quantity": -5, "limit_price": None, "cancel_order_ids": [], "confidence": 2.0, "rationale": "bad"}) else: mid = self._grab(r"mid ([0-9]+\.?[0-9]*)", user, None) pv = self._grab(r"public information: ([0-9]+\.?[0-9]*)", user, None) sig = self._grab(r"estimated at ([+-]?[0-9.]+) cents", user, 0.0) or 0.0 xhat = self._grab(r"public estimate ([+-]?[0-9.]+)", user, 0.0) or 0.0 held = int(self._grab(r"holdings (-?[0-9]+) shares", user, 0) or 0) free = self._grab(r"\(free ([0-9]+\.?[0-9]*)\)", user, 0.0) or 0.0 if mid is None or pv is None: text = json.dumps({"action": "hold", "side": None, "order_type": None, "quantity": None, "limit_price": None, "cancel_order_ids": [], "confidence": 0.5, "rationale": "no data"}) else: v_hat = pv + 0.5 * (sig - xhat) / (1 + 0.0025 - 0.98) / 100.0 m = (v_hat - mid) / mid + self.herd_bias + 0.004 * self.rng.standard_normal() if abs(m) < 0.005: dec = {"action": "hold", "side": None, "order_type": None, "quantity": None, "limit_price": None, "cancel_order_ids": [], "confidence": 0.4, "rationale": "price close to value"} else: q = int(min(50, max(1, round(abs(m) * 1500)))) if m > 0: q = min(q, int(free // (mid * 1.001))) if mid > 0 else 0 dec = {"action": "place", "side": "buy", "order_type": "limit" if self.rng.random() < 0.6 else "market", "quantity": q, "limit_price": round(mid * 1.002, 2), "cancel_order_ids": [], "confidence": 0.6, "rationale": f"value {v_hat:.2f} above mid {mid:.2f}"} else: q = min(q, max(held, 0)) dec = {"action": "place", "side": "sell", "order_type": "limit" if self.rng.random() < 0.6 else "market", "quantity": q, "limit_price": round(mid * 0.998, 2), "cancel_order_ids": [], "confidence": 0.6, "rationale": f"value {v_hat:.2f} below mid {mid:.2f}"} if dec["quantity"] <= 0: dec = {"action": "hold", "side": None, "order_type": None, "quantity": None, "limit_price": None, "cancel_order_ids": [], "confidence": 0.3, "rationale": "constrained"} text = json.dumps(dec) return ProviderResult(text=text, input_tokens=len(system.split()) + len(user.split()), output_tokens=len(text.split()), latency_s=time.perf_counter() - t0, model=model)