openoutcry 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. openoutcry-0.1.0/PKG-INFO +42 -0
  2. openoutcry-0.1.0/README.md +23 -0
  3. openoutcry-0.1.0/openoutcry/Cargo.toml +25 -0
  4. openoutcry-0.1.0/openoutcry/GOVERNANCE.md +93 -0
  5. openoutcry-0.1.0/openoutcry/README.md +84 -0
  6. openoutcry-0.1.0/openoutcry/contract/conformance/empty-portfolio-start.json +12 -0
  7. openoutcry-0.1.0/openoutcry/contract/conformance/legacy-decision-shape.json +18 -0
  8. openoutcry-0.1.0/openoutcry/contract/conformance/normal-multi-symbol.json +31 -0
  9. openoutcry-0.1.0/openoutcry/contract/conformance/signed-weight-short.json +23 -0
  10. openoutcry-0.1.0/openoutcry/contract/conformance/single-symbol.json +17 -0
  11. openoutcry-0.1.0/openoutcry/contract/decision.schema.json +46 -0
  12. openoutcry-0.1.0/openoutcry/contract/observation.schema.json +64 -0
  13. openoutcry-0.1.0/openoutcry/examples/reference-agent.py +41 -0
  14. openoutcry-0.1.0/openoutcry/examples/reference-agent.ts +36 -0
  15. openoutcry-0.1.0/openoutcry/examples/score-a-trajectory.rs +48 -0
  16. openoutcry-0.1.0/openoutcry/src/contract.rs +20 -0
  17. openoutcry-0.1.0/openoutcry/src/lib.rs +74 -0
  18. openoutcry-0.1.0/openoutcry/tests/conformance.rs +102 -0
  19. openoutcry-0.1.0/openoutcry/tests/smoke.rs +47 -0
  20. openoutcry-0.1.0/openoutcry-py/.gitignore +7 -0
  21. openoutcry-0.1.0/openoutcry-py/Cargo.lock +235 -0
  22. openoutcry-0.1.0/openoutcry-py/Cargo.toml +24 -0
  23. openoutcry-0.1.0/openoutcry-py/README.md +23 -0
  24. openoutcry-0.1.0/openoutcry-py/src/lib.rs +211 -0
  25. openoutcry-0.1.0/openoutcry-py/tests/test_gym.py +127 -0
  26. openoutcry-0.1.0/openoutcry-py/tests/test_verifiers.py +48 -0
  27. openoutcry-0.1.0/pyproject.toml +34 -0
  28. openoutcry-0.1.0/python/openoutcry/__init__.py +17 -0
  29. openoutcry-0.1.0/python/openoutcry/gym.py +162 -0
  30. openoutcry-0.1.0/python/openoutcry/verifiers_env.py +134 -0
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: openoutcry
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Topic :: Office/Business :: Financial :: Investment
7
+ Classifier: Topic :: Scientific/Engineering
8
+ Requires-Dist: numpy>=1.21
9
+ Requires-Dist: gymnasium>=1.0
10
+ Requires-Dist: verifiers ; extra == 'verifiers'
11
+ Provides-Extra: verifiers
12
+ Summary: OpenOutcry — a leak-free, point-in-time Gym for trading agents (Python distribution).
13
+ Keywords: trading,agent,environment,backtest,reinforcement-learning,gymnasium
14
+ Author: General Liquidity, Inc.
15
+ License: MIT OR Apache-2.0
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
18
+
19
+ # openoutcry (Python)
20
+
21
+ Python distribution of **OpenOutcry** — a leak-free, point-in-time *Gym for trading
22
+ agents*. A pyo3 binding over the Rust environment plus a `gymnasium`-compatible
23
+ wrapper and a PrimeIntellect `verifiers` environment.
24
+
25
+ ```python
26
+ from openoutcry import OpenOutcryEnv
27
+
28
+ env = OpenOutcryEnv(n_symbols=4, n_days=120, seed=7)
29
+ obs, info = env.reset()
30
+ done = False
31
+ while not done:
32
+ action = env.action_space.sample() # target-weight vector
33
+ obs, reward, terminated, truncated, info = env.step(action)
34
+ done = terminated or truncated
35
+ ```
36
+
37
+ The native binding (`openoutcry.openoutcry_py.TradingEnv`) exchanges the
38
+ language-agnostic wire JSON at its boundary: `reset() -> str` and
39
+ `step(decision_json) -> (obs_json, reward, done, info_json)`.
40
+
41
+ Build from source with [maturin](https://www.maturin.rs): `python -m maturin develop`.
42
+
@@ -0,0 +1,23 @@
1
+ # openoutcry (Python)
2
+
3
+ Python distribution of **OpenOutcry** — a leak-free, point-in-time *Gym for trading
4
+ agents*. A pyo3 binding over the Rust environment plus a `gymnasium`-compatible
5
+ wrapper and a PrimeIntellect `verifiers` environment.
6
+
7
+ ```python
8
+ from openoutcry import OpenOutcryEnv
9
+
10
+ env = OpenOutcryEnv(n_symbols=4, n_days=120, seed=7)
11
+ obs, info = env.reset()
12
+ done = False
13
+ while not done:
14
+ action = env.action_space.sample() # target-weight vector
15
+ obs, reward, terminated, truncated, info = env.step(action)
16
+ done = terminated or truncated
17
+ ```
18
+
19
+ The native binding (`openoutcry.openoutcry_py.TradingEnv`) exchanges the
20
+ language-agnostic wire JSON at its boundary: `reset() -> str` and
21
+ `step(decision_json) -> (obs_json, reward, done, info_json)`.
22
+
23
+ Build from source with [maturin](https://www.maturin.rs): `python -m maturin develop`.
@@ -0,0 +1,25 @@
1
+ [package]
2
+ name = "openoutcry"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ license = "MIT OR Apache-2.0"
6
+ description = "OpenOutcry — leak-free point-in-time trading-agent environment (a Gym for trading agents)."
7
+ repository = "https://github.com/general-liquidity/openoutcry"
8
+ keywords = ["trading", "agent", "environment", "backtest", "simulation"]
9
+ categories = ["science", "finance", "simulation"]
10
+ readme = "README.md"
11
+
12
+ [dependencies]
13
+ sharpebench-sim = "0.0.7"
14
+ sharpebench-protocol = "0.0.7"
15
+ sharpebench-core = "0.0.7"
16
+
17
+ [dev-dependencies]
18
+ serde_json = "1"
19
+
20
+ # Lints enforced by the manifest (not just CI's `-D warnings`), so a plain
21
+ # `cargo clippy` fails the same way CI does. Crates opt in via `[lints] workspace = true`.
22
+ [lints.clippy]
23
+ all = { level = "deny", priority = -1 }
24
+ todo = "deny"
25
+ dbg_macro = "deny"
@@ -0,0 +1,93 @@
1
+ # OpenOutcry Agent Interface — Governance
2
+
3
+ The contract is the product. Agents are **external** programs in any language that
4
+ read a `MarketObservation` (JSON) and reply with a `Decision` (JSON). The whole
5
+ adoption story is that this surface stays tiny and stable, so any vendor can build
6
+ against it once and not have it move under them. This document is the promise that
7
+ backs that.
8
+
9
+ The authoritative artifacts are:
10
+
11
+ - `contract/observation.schema.json` and `contract/decision.schema.json` — JSON
12
+ Schema (draft 2020-12) that non-Rust implementers validate against.
13
+ - `src/contract.rs` — `CONTRACT_VERSION`, the single source of truth for the wire
14
+ version.
15
+ - `contract/conformance/*.json` + `tests/conformance.rs` — the conformance kit.
16
+
17
+ ## 1. Additive-only evolution
18
+
19
+ The contract evolves **additively**. The only backwards-compatible change is adding
20
+ a **new field that is optional with a default** — every field a producer may omit
21
+ must deserialize on the consumer to a sensible default, so an agent written against
22
+ an older version keeps parsing newer observations and the harness keeps parsing
23
+ older decisions.
24
+
25
+ Concretely, in Rust this means the field carries `#[serde(default)]` (or
26
+ `#[serde(default = "…")]`). Today's optional-with-default fields are `fundamentals`
27
+ and `news` on `SymbolSnapshot`, `confidence` and `rationale` on `Order`, and
28
+ `reasoning` on `Decision`. They are **not** in the schemas' `required` lists, by
29
+ construction.
30
+
31
+ The following are **breaking** and are forbidden on the v1 surface:
32
+
33
+ - removing a field,
34
+ - renaming a field,
35
+ - retyping a field (e.g. `number` → `string`, widening an enum's existing variant),
36
+ - making a previously optional field required, or
37
+ - removing or renaming an `action` enum value.
38
+
39
+ A breaking change does not mutate `MarketObservation` / `Decision` in place. It ships
40
+ a **parallel namespace** — `ObservationV2` / `DecisionV2` with their own schemas and
41
+ their own `CONTRACT_VERSION` major — and the two run side by side through the
42
+ deprecation window below. Adding a *new* `action` variant is itself breaking for
43
+ consumers that exhaustively match, so it also goes through V2, not an additive bump.
44
+
45
+ ## 2. `CONTRACT_VERSION` semantics
46
+
47
+ `CONTRACT_VERSION` (currently `"1.0"`) versions the **wire shape only**. It is
48
+ deliberately decoupled from the `openoutcry` crate version and the npm / PyPI package
49
+ versions, which move on their own release cadence.
50
+
51
+ - **Major** (`1.0` → `2.0`): a breaking change shipped as a parallel `…V2` namespace.
52
+ - **Minor** (`1.0` → `1.1`): reserved for a *batch* of additive fields significant
53
+ enough to advertise. A single additive field needs no bump at all — existing agents
54
+ are unaffected by definition, and the conformance badge stays valid.
55
+
56
+ A package release never, on its own, bumps `CONTRACT_VERSION`. Bug fixes, new baseline
57
+ agents, docs, and extra re-exports change the package version and leave the contract
58
+ version frozen.
59
+
60
+ ## 3. Deprecation window
61
+
62
+ When a major (`V2`) lands, the previous major is **supported in parallel for at least
63
+ two minor package releases (no less than 90 days)**, whichever is longer. During the
64
+ window:
65
+
66
+ 1. both namespaces deserialize and run; the harness accepts either,
67
+ 2. the superseded version is marked `#[deprecated]` in Rust and flagged as deprecated
68
+ in the schema `description`, and
69
+ 3. the changelog states the removal release up front.
70
+
71
+ Only after the window closes may the old namespace be removed — and that removal is
72
+ itself a major package release.
73
+
74
+ ## 4. Conformance badge
75
+
76
+ An implementation that passes the conformance kit
77
+ (`contract/conformance/*.json` exercised by `tests/conformance.rs`, or the equivalent
78
+ validation against the published JSON Schemas) may state:
79
+
80
+ > **conforms to the OpenOutcry Agent Interface v1.0**
81
+
82
+ To earn it, an agent must, for every observation in the kit:
83
+
84
+ 1. parse the `MarketObservation` against `observation.schema.json`,
85
+ 2. emit a `Decision` that validates against `decision.schema.json` — every `action`
86
+ in the enum, every `target_weight` finite, and every order `symbol` a subset of
87
+ the observed symbols, and
88
+ 3. round-trip the legacy decision shape (no `confidence` / `rationale` / `reasoning`)
89
+ without error, proving the additive-only discipline holds.
90
+
91
+ The badge names the **contract** version it was earned against (`v1.0`), not the
92
+ package version. It remains valid across additive (non-bumping) changes and must be
93
+ re-earned against a new major.
@@ -0,0 +1,84 @@
1
+ # OpenOutcry
2
+
3
+ **A leak-free, point-in-time environment for trading agents — and the language-agnostic contract they speak.**
4
+
5
+ OpenOutcry is the open-outcry trading floor for agents: the harness hands the agent a point-in-time
6
+ `Observation`, the agent returns a `Decision`, repeat. Look-ahead is *structurally impossible* (the
7
+ environment owns the time cursor and never hands out a future bar), and trajectories are
8
+ recompute-from-raw-decisions, so an agent cannot lie about its returns.
9
+
10
+ The strategic bet is **interface ownership**: if every trading agent in the open ecosystem conforms to
11
+ the OpenOutcry `Observation`/`Decision` contract, then [SharpeBench](https://crates.io/crates/sharpebench-core)
12
+ is the natural scorer and the whole funnel — env → trajectory → score → leaderboard — runs on one
13
+ standard. The interface *is* the product; the simulator is the credibility behind it.
14
+
15
+ ## The agent contract (the standard)
16
+
17
+ An agent is just a program that reads an `Observation` and writes a `Decision` — in any language, over
18
+ stdio (newline-JSON) or HTTP (`POST /decide`):
19
+
20
+ ```jsonc
21
+ // Observation (harness → agent)
22
+ { "date": "2025-01-02", "cash": 1.0,
23
+ "symbols": [{ "symbol": "AAPL", "close_history": [187.2, 188.0, 190.4] }],
24
+ "portfolio": [] }
25
+
26
+ // Decision (agent → harness)
27
+ { "orders": [{ "symbol": "AAPL", "action": "buy", "target_weight": 0.5 }] }
28
+ ```
29
+
30
+ The wire shape is versioned (`CONTRACT_VERSION`), evolves **additively only** (new fields are optional
31
+ with defaults), and is pinned by published JSON Schemas + a conformance kit. See
32
+ [`GOVERNANCE.md`](./GOVERNANCE.md) and [`contract/`](./contract/).
33
+
34
+ ## The Gym lifecycle
35
+
36
+ The same engine SharpeBench runs *closed* (`run_backtest`), OpenOutcry exposes *open* — the caller drives it:
37
+
38
+ ```rust
39
+ use openoutcry::{TradingEnv, Dataset, CostModel, Window, BuyAndHold, Agent};
40
+
41
+ let data = Dataset::synthetic(4, 120, 1);
42
+ let mut env = TradingEnv::new(data, Window { start: 20, end: 120 }, CostModel::default(), 7);
43
+ let mut agent = BuyAndHold;
44
+ let mut obs = env.reset();
45
+ loop {
46
+ let decision = agent.decide(&obs);
47
+ let step = env.step(decision); // -> { observation, reward, done, info }
48
+ obs = step.observation;
49
+ if step.done { break; }
50
+ }
51
+ ```
52
+
53
+ Both stepping surfaces call one shared `step_once` body, so a trajectory the env produces is
54
+ **byte-identical** to the equivalent `run_backtest` (enforced by `env_step_matches_run_backtest`).
55
+
56
+ ## env → SharpeBench score
57
+
58
+ Run with capture, hand the trajectory to a *separate verifier* that recomputes the submission from the
59
+ raw decisions + frozen data alone, then score:
60
+
61
+ ```
62
+ cargo run -p openoutcry --example score-a-trajectory
63
+ ```
64
+
65
+ (see [`examples/score-a-trajectory.rs`](./examples/score-a-trajectory.rs)). Tamper with the trajectory
66
+ and the honest replay recomputes to different returns — this is the trust hinge of the whole ecosystem.
67
+
68
+ ## Distribution
69
+
70
+ OpenOutcry ships from one Rust engine to every surface, with a language-agnostic wire contract on top so
71
+ agents can be written in anything:
72
+
73
+ - **Rust** — `openoutcry` (this crate).
74
+ - **TypeScript / npm** — `@general-liquidity/openoutcry` (the engine compiled to WASM).
75
+ - **Python / PyPI** — `openoutcry`, with a `gymnasium.Env` adapter and a PrimeIntellect `verifiers`
76
+ environment so it plugs into the RL-training stacks directly.
77
+
78
+ Reference agents in Rust, TypeScript, and Python double as the conformance smoke tests
79
+ ([`examples/`](./examples/)).
80
+
81
+ ## Status
82
+
83
+ Incubating inside the SharpeBench workspace (it depends on the published `sharpebench-sim` engine).
84
+ It graduates to its own repository at distribution time, consuming the engine as a versioned crate.
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "cold start: full cash, no holdings, snapshots omit optional fundamentals/news",
3
+ "observation": {
4
+ "date": "2025-01-02",
5
+ "cash": 1.0,
6
+ "symbols": [
7
+ { "symbol": "SPY", "close_history": [468.1, 470.0, 472.5] },
8
+ { "symbol": "QQQ", "close_history": [402.3, 404.9, 401.0] }
9
+ ],
10
+ "portfolio": []
11
+ }
12
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "legacy decision shape: orders omit confidence/rationale and the decision omits reasoning",
3
+ "observation": {
4
+ "date": "2025-02-10",
5
+ "cash": 1.0,
6
+ "symbols": [
7
+ { "symbol": "GLD", "close_history": [188.0, 189.5, 190.1] },
8
+ { "symbol": "SLV", "close_history": [22.1, 22.4, 22.0] }
9
+ ],
10
+ "portfolio": []
11
+ },
12
+ "legacy_decision": {
13
+ "orders": [
14
+ { "symbol": "GLD", "action": "buy", "target_weight": 0.5 },
15
+ { "symbol": "SLV", "action": "close", "target_weight": 0.0 }
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "normal multi-symbol observation with fundamentals, news and held positions",
3
+ "observation": {
4
+ "date": "2025-03-14",
5
+ "cash": 1.0,
6
+ "symbols": [
7
+ {
8
+ "symbol": "AAPL",
9
+ "close_history": [186.4, 187.2, 188.0, 190.4, 191.1],
10
+ "fundamentals": { "pe": 28.4, "revenue_yoy": 0.06 },
11
+ "news": ["Apple unveils new chip", "Services revenue at record"]
12
+ },
13
+ {
14
+ "symbol": "MSFT",
15
+ "close_history": [402.1, 405.6, 409.0, 411.3, 408.7],
16
+ "fundamentals": { "pe": 35.1, "revenue_yoy": 0.12 },
17
+ "news": ["Azure growth accelerates"]
18
+ },
19
+ {
20
+ "symbol": "NVDA",
21
+ "close_history": [870.0, 905.4, 889.2, 921.0, 950.5],
22
+ "fundamentals": { "pe": 61.0, "revenue_yoy": 1.22 },
23
+ "news": []
24
+ }
25
+ ],
26
+ "portfolio": [
27
+ { "symbol": "AAPL", "shares": 1.5, "avg_price": 180.0 },
28
+ { "symbol": "MSFT", "shares": 0.8, "avg_price": 395.0 }
29
+ ]
30
+ }
31
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "exercises the full decision surface: sell/hold actions and a signed (short) target weight",
3
+ "observation": {
4
+ "date": "2025-05-05",
5
+ "cash": 1.0,
6
+ "symbols": [
7
+ { "symbol": "TSLA", "close_history": [240.0, 235.5, 230.1] },
8
+ { "symbol": "F", "close_history": [12.1, 12.0, 11.8] },
9
+ { "symbol": "GM", "close_history": [44.0, 44.6, 45.2] }
10
+ ],
11
+ "portfolio": [
12
+ { "symbol": "TSLA", "shares": 0.5, "avg_price": 250.0 }
13
+ ]
14
+ },
15
+ "legacy_decision": {
16
+ "orders": [
17
+ { "symbol": "TSLA", "action": "sell", "target_weight": -0.3, "confidence": 0.7, "rationale": "downtrend" },
18
+ { "symbol": "F", "action": "hold", "target_weight": 0.0 },
19
+ { "symbol": "GM", "action": "buy", "target_weight": 0.5 }
20
+ ],
21
+ "reasoning": "tilt short autos ex-GM"
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "single-symbol universe with one open position",
3
+ "observation": {
4
+ "date": "2025-06-20",
5
+ "cash": 0.25,
6
+ "symbols": [
7
+ {
8
+ "symbol": "BTC",
9
+ "close_history": [61000.0, 62450.0, 60900.5, 63100.0],
10
+ "news": ["ETF inflows continue"]
11
+ }
12
+ ],
13
+ "portfolio": [
14
+ { "symbol": "BTC", "shares": 0.012, "avg_price": 59000.0 }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openoutcry.dev/contract/v1/decision.schema.json",
4
+ "title": "Decision",
5
+ "description": "What an agent returns at one decision step: per-instrument orders plus an optional free-text rationale. OpenOutcry Agent Interface v1.0.",
6
+ "type": "object",
7
+ "required": ["orders"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "orders": {
11
+ "type": "array",
12
+ "description": "Per-instrument instructions. May be empty (a hold).",
13
+ "items": { "$ref": "#/$defs/Order" }
14
+ },
15
+ "reasoning": {
16
+ "type": "string",
17
+ "description": "Free-text rationale captured into the trajectory. Optional; defaults to empty."
18
+ }
19
+ },
20
+ "$defs": {
21
+ "Order": {
22
+ "type": "object",
23
+ "required": ["symbol", "action", "target_weight"],
24
+ "additionalProperties": false,
25
+ "properties": {
26
+ "symbol": { "type": "string" },
27
+ "action": {
28
+ "description": "Discrete action label (sizing is carried by target_weight).",
29
+ "enum": ["buy", "sell", "hold", "close"]
30
+ },
31
+ "target_weight": {
32
+ "type": "number",
33
+ "description": "Target portfolio weight for this symbol, signed for shorts."
34
+ },
35
+ "confidence": {
36
+ "type": "number",
37
+ "description": "Stated conviction in [0, 1], scored for calibration. Optional; defaults to 0.5."
38
+ },
39
+ "rationale": {
40
+ "type": "string",
41
+ "description": "Optional one-line rationale for this order. Defaults to empty."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openoutcry.dev/contract/v1/observation.schema.json",
4
+ "title": "MarketObservation",
5
+ "description": "What an agent sees at one point-in-time decision step. All data is point-in-time: close_history, fundamentals and news only ever hold information available at or before `date`. OpenOutcry Agent Interface v1.0.",
6
+ "type": "object",
7
+ "required": ["date", "cash", "symbols", "portfolio"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "date": {
11
+ "type": "string",
12
+ "description": "ISO-8601 date of the decision point."
13
+ },
14
+ "cash": {
15
+ "type": "number",
16
+ "description": "Cash available to the agent."
17
+ },
18
+ "symbols": {
19
+ "type": "array",
20
+ "description": "Point-in-time snapshot per tradable instrument.",
21
+ "items": { "$ref": "#/$defs/SymbolSnapshot" }
22
+ },
23
+ "portfolio": {
24
+ "type": "array",
25
+ "description": "The agent's current holdings.",
26
+ "items": { "$ref": "#/$defs/PositionState" }
27
+ }
28
+ },
29
+ "$defs": {
30
+ "SymbolSnapshot": {
31
+ "type": "object",
32
+ "required": ["symbol", "close_history"],
33
+ "additionalProperties": false,
34
+ "properties": {
35
+ "symbol": { "type": "string" },
36
+ "close_history": {
37
+ "type": "array",
38
+ "description": "Trailing closes up to and including `date` (oldest first).",
39
+ "items": { "type": "number" }
40
+ },
41
+ "fundamentals": {
42
+ "type": "object",
43
+ "description": "Named fundamental fields (e.g. `pe`, `revenue_yoy`). Optional; defaults to empty.",
44
+ "additionalProperties": { "type": "number" }
45
+ },
46
+ "news": {
47
+ "type": "array",
48
+ "description": "Headlines published on or before `date`. Optional; defaults to empty.",
49
+ "items": { "type": "string" }
50
+ }
51
+ }
52
+ },
53
+ "PositionState": {
54
+ "type": "object",
55
+ "required": ["symbol", "shares", "avg_price"],
56
+ "additionalProperties": false,
57
+ "properties": {
58
+ "symbol": { "type": "string" },
59
+ "shares": { "type": "number" },
60
+ "avg_price": { "type": "number" }
61
+ }
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env python3
2
+ """Reference OpenOutcry agent (Python) — the simplest thing that honors the contract.
3
+
4
+ Transport: stdio. Reads one MarketObservation (JSON) per line on stdin and writes one
5
+ Decision (JSON) per line on stdout. Strategy: equal-weight buy-and-hold — the baseline
6
+ every real agent must beat. Fork it, replace ``decide``.
7
+
8
+ python examples/reference-agent.py # then feed it MarketObservation JSON lines
9
+ """
10
+ import json
11
+ import sys
12
+
13
+
14
+ def decide(obs):
15
+ """MarketObservation -> Decision. Replace this body with your strategy."""
16
+ symbols = obs["symbols"]
17
+ weight = 1.0 / max(len(symbols), 1)
18
+ orders = [
19
+ {"symbol": s["symbol"], "action": "buy", "target_weight": weight,
20
+ "confidence": 0.5, "rationale": "equal-weight hold"}
21
+ for s in symbols
22
+ ]
23
+ return {"orders": orders, "reasoning": "equal-weight buy-and-hold"}
24
+
25
+
26
+ def main():
27
+ for raw in sys.stdin:
28
+ line = raw.strip()
29
+ if not line:
30
+ continue
31
+ try:
32
+ decision = decide(json.loads(line))
33
+ except Exception:
34
+ # Any bad input degrades to an empty-orders hold — never crashes the harness.
35
+ decision = {"orders": [], "reasoning": "parse error -> hold"}
36
+ sys.stdout.write(json.dumps(decision) + "\n")
37
+ sys.stdout.flush()
38
+
39
+
40
+ if __name__ == "__main__":
41
+ main()
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ // Reference OpenOutcry agent (TypeScript) — the simplest thing that honors the contract.
3
+ //
4
+ // Transport: stdio. Reads one MarketObservation (JSON) per line on stdin and writes
5
+ // one Decision (JSON) per line on stdout. Strategy: equal-weight buy-and-hold — the
6
+ // baseline every real agent must beat. Fork it, replace `decide`.
7
+ //
8
+ // node examples/reference-agent.ts # then feed it MarketObservation JSON lines
9
+
10
+ import { createInterface } from "node:readline";
11
+
12
+ interface Order { symbol: string; action: string; target_weight: number; confidence: number; rationale: string; }
13
+ interface Decision { orders: Order[]; reasoning: string; }
14
+
15
+ /** MarketObservation -> Decision. Replace this body with your strategy. */
16
+ function decide(obs: { symbols: { symbol: string }[] }): Decision {
17
+ const weight = 1.0 / Math.max(obs.symbols.length, 1);
18
+ const orders = obs.symbols.map((s) => ({
19
+ symbol: s.symbol, action: "buy", target_weight: weight, confidence: 0.5, rationale: "equal-weight hold",
20
+ }));
21
+ return { orders, reasoning: "equal-weight buy-and-hold" };
22
+ }
23
+
24
+ const rl = createInterface({ input: process.stdin });
25
+ rl.on("line", (raw) => {
26
+ const line = raw.trim();
27
+ if (line === "") return;
28
+ let decision: Decision;
29
+ try {
30
+ decision = decide(JSON.parse(line));
31
+ } catch {
32
+ // Any bad input degrades to an empty-orders hold — never crashes the harness.
33
+ decision = { orders: [], reasoning: "parse error -> hold" };
34
+ }
35
+ process.stdout.write(JSON.stringify(decision) + "\n");
36
+ });
@@ -0,0 +1,48 @@
1
+ //! The end-to-end ecosystem path: **env → trajectory → SharpeBench score.**
2
+ //!
3
+ //! Run an agent in OpenOutcry while capturing only its raw decisions, then hand the
4
+ //! trajectory to a *separate verifier* that recomputes the submission from those
5
+ //! decisions + the frozen data alone (the agent cannot lie about its returns), and
6
+ //! finally score it with SharpeBench's `CompositeScore`.
7
+ //!
8
+ //! `cargo run -p openoutcry --example score-a-trajectory`
9
+
10
+ use openoutcry::{
11
+ replay_submission, run_backtest_capture, AgentTrajectory, BuyAndHold, CostModel, Dataset,
12
+ Window, CONTRACT_VERSION,
13
+ };
14
+ use sharpebench_core::{score_agent, ScoreConfig};
15
+
16
+ fn main() {
17
+ let data = Dataset::synthetic(4, 160, 7);
18
+ let window = Window {
19
+ start: 20,
20
+ end: 160,
21
+ };
22
+ let costs = CostModel::default();
23
+
24
+ // 1) env → trajectory: run the agent with capture — the artifact holds ONLY the
25
+ // raw per-step decisions + the window/seed coordinates, no self-reported metric.
26
+ let (_run, run_traj) = run_backtest_capture(&data, &mut BuyAndHold, window, 1, costs);
27
+ let trajectory = AgentTrajectory {
28
+ agent_id: "buy-and-hold".to_string(),
29
+ runs: vec![run_traj],
30
+ in_sample_trials: 0,
31
+ };
32
+
33
+ // 2) verify: recompute the scoreable submission from the decisions + frozen data
34
+ // alone. Tamper with the trajectory and this recomputes to different returns.
35
+ let submission = replay_submission(&data, &trajectory, costs);
36
+
37
+ // 3) score: deflated Sharpe / pass^k / process gate — the SharpeBench verdict.
38
+ let score = score_agent(&submission, &ScoreConfig::default());
39
+
40
+ println!(
41
+ "OpenOutcry contract v{CONTRACT_VERSION} — scored '{}':",
42
+ submission.agent_id
43
+ );
44
+ println!(
45
+ "{}",
46
+ serde_json::to_string_pretty(&score).expect("score serializes")
47
+ );
48
+ }
@@ -0,0 +1,20 @@
1
+ //! The frozen wire contract version.
2
+
3
+ /// The version of the **OpenOutcry Agent Interface** — the JSON wire shape an
4
+ /// agent and the harness exchange ([`MarketObservation`](crate::MarketObservation)
5
+ /// in, [`Decision`](crate::Decision) out).
6
+ ///
7
+ /// This is **not** the crate / npm / PyPI package version. It tracks only the
8
+ /// *shape* of the contract and moves independently: shipping bug fixes, new
9
+ /// baselines, or extra re-exports bumps the package version but leaves
10
+ /// `CONTRACT_VERSION` untouched.
11
+ ///
12
+ /// It bumps **only** on a breaking wire change. Additive changes — a new field
13
+ /// that is optional-with-default, so every existing agent keeps parsing — do
14
+ /// **not** bump it (that is the whole additive-only discipline; see
15
+ /// `GOVERNANCE.md`). Removing or retyping a field is a major bump and requires a
16
+ /// parallel `…V2` namespace rather than mutating this surface in place.
17
+ ///
18
+ /// Implementers in any language target this version: passing the conformance kit
19
+ /// earns "conforms to the OpenOutcry Agent Interface v1.0".
20
+ pub const CONTRACT_VERSION: &str = "1.0";