openoutcry 0.2.0

OpenOutcry — leak-free point-in-time trading-agent environment (a Gym for trading agents).
Documentation

OpenOutcry

A leak-free, point-in-time environment for trading agents — and the language-agnostic contract they speak.

OpenOutcry is the open-outcry trading floor for agents: the harness hands the agent a point-in-time Observation, the agent returns a Decision, repeat. Look-ahead is structurally impossible (the environment owns the time cursor and never hands out a future bar), and trajectories are recompute-from-raw-decisions, so an agent cannot lie about its returns.

The strategic bet is interface ownership: if every trading agent in the open ecosystem conforms to the OpenOutcry Observation/Decision contract, then SharpeBench is the natural scorer and the whole funnel — env → trajectory → score → leaderboard — runs on one standard. The interface is the product; the simulator is the credibility behind it.

The agent contract (the standard)

An agent is just a program that reads an Observation and writes a Decision — in any language, over stdio (newline-JSON) or HTTP (POST /decide):

// Observation (harness → agent)
{ "date": "2025-01-02", "cash": 1.0,
  "symbols": [{ "symbol": "AAPL", "close_history": [187.2, 188.0, 190.4] }],
  "portfolio": [] }

// Decision (agent → harness)
{ "orders": [{ "symbol": "AAPL", "action": "buy", "target_weight": 0.5 }] }

The wire shape is versioned (CONTRACT_VERSION), evolves additively only (new fields are optional with defaults), and is pinned by published JSON Schemas + a conformance kit. See GOVERNANCE.md and contract/.

The Gym lifecycle

The same engine SharpeBench runs closed (run_backtest), OpenOutcry exposes open — the caller drives it:

use openoutcry::{TradingEnv, Dataset, CostModel, Window, BuyAndHold, Agent};

let data = Dataset::synthetic(4, 120, 1);
let mut env = TradingEnv::new(data, Window { start: 20, end: 120 }, CostModel::default(), 7);
let mut agent = BuyAndHold;
let mut obs = env.reset();
loop {
    let decision = agent.decide(&obs);
    let step = env.step(decision);   // -> { observation, reward, done, info }
    obs = step.observation;
    if step.done { break; }
}

Both stepping surfaces call one shared step_once body, so a trajectory the env produces is byte-identical to the equivalent run_backtest (enforced by env_step_matches_run_backtest).

env → SharpeBench score

Run with capture, hand the trajectory to a separate verifier that recomputes the submission from the raw decisions + frozen data alone, then score:

cargo run -p openoutcry --example score-a-trajectory

(see examples/score-a-trajectory.rs). Tamper with the trajectory and the honest replay recomputes to different returns — this is the trust hinge of the whole ecosystem.

Distribution

OpenOutcry ships from one Rust engine to every surface, with a language-agnostic wire contract on top so agents can be written in anything:

  • Rustopenoutcry (this crate).
  • TypeScript / npm@general-liquidity/openoutcry (the engine compiled to WASM).
  • Python / PyPIopenoutcry, with a gymnasium.Env adapter and a PrimeIntellect verifiers environment so it plugs into the RL-training stacks directly.

Reference agents in Rust, TypeScript, and Python double as the conformance smoke tests (examples/).

Status

Incubating inside the SharpeBench workspace (it depends on the published sharpebench-sim engine). It graduates to its own repository at distribution time, consuming the engine as a versioned crate.