SPB Git

spb/wp27_uqo

Public
4.1 KB · 97 lines python
1"""Frozen policy library: a nearest-neighbour replay of *recorded* LLM decisions.23Every online LLM decision is logged with a feature vector of its observation; the library maps a new4observation to the recorded decision of the nearest neighbour in feature space (standardized Euclidean5distance) and rescales the quantity to the current account. This is NOT an adaptive agent: it is a6frozen, non-reasoning approximation used only (i) to scale populations to 1,000+ traders at zero API7cost and (ii) as the 'cached' arm of the cached-vs-online comparison. Runs using it are labelled8provider = 'library'."""9from __future__ import annotations1011import json12from pathlib import Path1314import numpy as np15import pandas as pd1617from ..agents.base import Observation1819FEATURES = ["mispricing", "momentum", "signal", "inv_ratio", "cash_ratio", "spread_bp", "has_open"]202122def features(obs: Observation, asset: int = 0) -> np.ndarray:23    b = obs.books[asset]24    tc = obs.tick_cents[asset]25    mid = max(b.mid, 1.0)26    h = obs.mid_history[asset]27    mom = (h[-1] / h[0] - 1.0) if len(h) > 1 and h[0] > 0 else 0.028    pv = obs.public_value[asset] / tc29    sig = obs.private_signals.get(asset, 0.0)30    pos_val = obs.positions.get(asset, 0) * mid * tc31    eq = max(obs.equity, 1.0)32    spread = ((b.best_ask - b.best_bid) / mid * 1e4) if (b.best_ask is not None and b.best_bid is not None) else 50.033    return np.array([(pv - mid) / mid, mom, sig / 10.0, pos_val / eq, obs.free_cash / eq, spread / 10.0, float(bool(obs.open_orders))])343536class DecisionLibrary:37    def __init__(self, X: np.ndarray, decisions: list[dict]) -> None:38        self.X = X39        self.mu = X.mean(axis=0)40        self.sd = X.std(axis=0) + 1e-941        self.Z = (X - self.mu) / self.sd42        self.decisions = decisions4344    @classmethod45    def from_runs(cls, run_dirs: list[str | Path]) -> "DecisionLibrary":46        X, D = [], []47        for rd in run_dirs:48            p = Path(rd) / "llm_decisions.parquet"49            if not p.exists():50                continue51            df = pd.read_parquet(p)52            df = df[df["valid"] & df["features"].notna()] if "features" in df else df.iloc[0:0]53            for _, r in df.iterrows():54                X.append(json.loads(r["features"]))55                D.append({"action": r["action"], "side": r["side"], "order_type": r["order_type"], "quantity": int(r["quantity"]),56                          "rel_price": float(r["rel_price"]) if "rel_price" in r and pd.notna(r["rel_price"]) else 0.0,57                          "rationale": "library replay"})58        if not X:59            raise ValueError("no recorded decisions with features in the given runs")60        return cls(np.array(X, dtype=float), D)6162    def save(self, path: str | Path) -> None:63        np.savez(path, X=self.X, decisions=json.dumps(self.decisions))6465    @classmethod66    def load(cls, path: str | Path) -> "DecisionLibrary":67        z = np.load(path, allow_pickle=False)68        return cls(z["X"], json.loads(str(z["decisions"])))6970    def lookup(self, f: np.ndarray, rng: np.random.Generator, k: int = 5) -> dict:71        z = (f - self.mu) / self.sd72        d = ((self.Z - z) ** 2).sum(axis=1)73        idx = np.argpartition(d, min(k, len(d) - 1))[:k]74        return dict(self.decisions[int(rng.choice(idx))])7576    def __len__(self) -> int:77        return len(self.decisions)787980class LibraryProvider:81    """Provider-shaped wrapper so the LLMPolicy pipeline (validation, logging) is reused unchanged."""82    name = "library"8384    def __init__(self, library_path: str, seed: int = 0, **_: object) -> None:85        self.lib = DecisionLibrary.load(library_path)86        self.rng = np.random.Generator(np.random.PCG64(seed))8788    def complete_obs(self, obs: Observation, mid_units: float) -> str:89        dec = self.lib.lookup(features(obs), self.rng)90        if dec["action"] == "place" and dec["order_type"] == "limit":91            dec["limit_price"] = round(mid_units * (1.0 + dec.get("rel_price", 0.0)), 2)92        else:93            dec["limit_price"] = None94        dec.setdefault("cancel_order_ids", [])95        dec.setdefault("confidence", None)96        return json.dumps(dec)97
Files · main
Menu

Theme