Skip to content
Merged
8 changes: 7 additions & 1 deletion docs/architecture/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"title": "Architecture",
"pages": ["start-here", "overview", "contracts", "stability"]
"pages": [
"start-here",
"overview",
"contracts",
"sports-matching",
"stability"
]
}
126 changes: 126 additions & 0 deletions docs/architecture/sports-matching.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: Sports proposition matching
description: Compare MLB winner propositions while preserving settlement differences and unknown rules
---

`neural.sports` adds a versioned, offline contract for MLB team-winner markets.
It separates **the sporting proposition** from **the settlement policy**.
Matching teams and a game does not establish economically equivalent contracts.

## API and existing consumers

```python
from neural.sports import SportsMarket, compare_sports_markets

left = SportsMarket.from_dict(left_payload)
right = SportsMarket.from_dict(right_payload)
comparison = compare_sports_markets(left, right)
print(comparison.to_dict())
```

The output contains `status`, `proposition`, `settlement`, `differences`, and
`unknowns`. Each status is `compatible`, `different`, or `unknown`. Field names
identify the reason; the input contracts retain each rule value and its source
for display. The function makes no network calls or settlement decisions.

This is additive to `StrategySpec` and `neural.contracts` v1. Their schemas,
hashes and replay behavior remain unchanged. Existing `NormalizedMarket`
consumers can retain `SportsMarket.to_dict()` under
`market.metadata["sports_proposition"]`; keep `market_id` equal to the native
identifier already used by the adapter. No broker abstraction or dependency is
introduced. Novig is accepted as a metadata venue, not as an executable adapter.

## Version 1 identity

Wire payloads require every documented dataclass field, reject extra fields,
and use `schema_version: "1.0.0"`. `sport`, `league`, and `market_type` are fixed
to `baseball`, `mlb`, and `winner`. Identity fields are:

| Fields | Meaning |
| --- | --- |
| `venue`, `event_id`, `market_id`, `outcome_id` | Original venue and native identifiers; never replaced with canonical IDs |
| `raw_home_team_id`, `raw_away_team_id` | Original venue team IDs, or null when absent |
| `canonical_event_id`, `home_team_id`, `away_team_id` | Caller-resolved identities in a shared namespace; null when unresolved |
| `outcome_team_id` | The selected team-wins proposition, not a venue-specific yes/no label |
| `game_date` | Original official local schedule date (`YYYY-MM-DD`), retained after postponement |
| `game_number` | 1 or 2, explicitly established; null when unknown |
| `period` | `full_game` for the supported comparison; other named segments remain representable but excluded |

The canonical event ID must identify one game, not a matchup or date bucket.
Use an authoritative common game identifier or an explicitly reviewed mapping.
An adapter must not invent mappings by lowercasing team names, translating an
unknown game number to 1, or truncating a UTC start timestamp into a local game
date. A rescheduled start time belongs in venue metadata; it does not silently
create a new canonical game. Home/away changes require mapping review.

Every canonical field must agree and be known before settlement comparison.
Two missing values produce `unknown`. Game 1 and game 2 are different even if
an upstream mapper accidentally reuses the event ID. Opposite team outcomes
are different. Partial-game markets are excluded even when both periods agree.

## Rule evidence and compatibility

`SettlementRules` contains `terms` and `complete`. Each `RuleEvidence` contains:

- `name`: one of `winner`, `extra_innings`, `forfeit`, `postponement`,
`cancellation`, `shortened_game`, `settlement_source`, `exceptional_payout`,
`venue_change`, or `replay`.
- `value`: a reviewed semantic policy identifier. Preserve material exceptions,
deadlines, source hierarchies, and payout procedures in its meaning. Raw text
similarity is not semantic equality.
- `source_url`, `source_sha256`, `retrieved_at`: HTTPS source, exact retrieved
content digest (null if unavailable), and timezone-aware observation time.
The digest is a content version, not a claimed publisher revision number.
- `scope`: `listed_contract`, `series`, `guidance`, or explicitly synthetic
`fixture`.

`complete` defaults to false. Set it only after reviewing the listed contract
and applicable supplemental rules against every v1 dimension. If an exception
cannot be represented faithfully, leave review incomplete. Series PDFs and
general FAQs can surface documented differences, but cannot establish complete
listed-contract compatibility. A missing rule or snapshot also prevents it.

For matching propositions, a known policy difference yields `different`, even
if other fields remain unknown; those unknowns are still returned. Equal rule
values require full source coverage to produce `compatible`. Source URLs need
not be identical across venues: compare reviewed meanings, retain both sources.
Synthetic rules require `fixture:`-prefixed native event, market and outcome
IDs, plus a `fixture:`-prefixed canonical event ID when known. Copying fixture
rules onto real market identities is rejected. Synthetic rules can compare
with synthetic rules, but cannot certify real contract evidence. Source URLs
must have valid HTTPS DNS names or unscoped IPv6 hosts and valid ports. IPv6
zone identifiers are unsupported for rule sources. Wire text must encode as UTF-8.
`compatible` describes these reviewed v1 dimensions; it is
neither a legal guarantee nor a risk-free arbitrage claim.

## Offline fixture demonstration

```bash
uv run python examples/sports_matching_demo.py
uv run pytest --no-cov tests/contracts/test_sports_matching.py
```

The JSON fixture uses synthetic game, team and native venue IDs throughout.
The ordinary-winner compatible pair uses identical **synthetic** rules. Other
cases demonstrate forfeit differences, postponed-game differences, doubleheader
separation, partial-game exclusion, missing rules and an unknown game number.
The demo tests matching and rule comparison, not game settlement or fill quality.

Rule-reference cases retain source hashes observed September 10, 2026:

- [Kalshi BASEBALLGAMEWIN terms](https://assets.kalshi.com/contract_terms/BASEBALLGAMEWIN.pdf)
distinguish forfeits before first pitch from those after play starts and use
a 48-hour postponement window. The fixture marks this as series evidence.
- [Polymarket US listed TB–ATL market](https://gateway.polymarket.us/v1/market/slug/aec-mlb-tb-atl-2026-09-10)
names MLB, includes extra innings, and specifies a rescheduling date within
two weeks or last fair market price. Its response hash includes mutable market
data. The fixture does not infer unlisted forfeit or shortened-game rules from
the general FAQ.
- [Novig contract directory](https://support.novig.com/en/articles/16083642-contracts)
links the MLB Winner Series PDF. Its rule-reference fixture records the linked
PDF URL without temporary access parameters and the PDF content hash. The
series voids a forfeit without an on-field result; postponement terms include
season-dependent windows and date-update exceptions.

These are bounded policy examples, not verified market mappings or a complete
current rules database. The recorded inputs do not claim three-venue equivalence.
7 changes: 7 additions & 0 deletions docs/trading/durable-paper.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ single buy/exit cycle, no settlement or freshness certification. Model identity
is part of each job; incompatible future models must fail rather than silently
reinterpret queued work. No hosted worker or Vaticor UI is deployed here.

The current model is `neural-paper/2` (`neural.paper.PAPER_MODEL`). The model
participates in the job ID, so resubmitting model-1 inputs under model 2 creates
a new job instead of returning an old result. Completed model-1 jobs remain
available through `inspect`. Queued model-1 jobs fail explicitly as unsupported;
submit the original strategy, recording and assumptions again to run model 2.
Old results are not silently recalculated or relabeled with the new model.

```sh
pytest tests/test_paper_worker.py -q
```
Expand Down
90 changes: 84 additions & 6 deletions docs/trading/recorded-paper.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ description: Run one reproducible offline price-rule cycle without exchange acce
# Recorded-book paper simulation

`neural.paper` runs a validated StrategySpec over a complete `kalshi-book/1`
recording from `neural.kalshi_stream`. No credentials, network calls, live
orders, or automatic persistence. This is separate from the legacy paper client.
recording or a synthetic `neural-book/1` recording for Kalshi or Polymarket US.
It makes no credential or network calls. The same simulator and durable paper
queue handle both venues. This is separate from the legacy paper client.

```sh
python -m neural.paper --strategy examples/strategy-price-rule.json \
Expand All @@ -20,6 +21,9 @@ Pass zero explicitly only for an idealized zero-fee experiment.

Python callers use `simulate_recording(spec, path, initial_cash="100",
fee_per_contract="0.01")`. The returned dictionary is JSON serializable.
The current model is `neural-paper/2`, available as `neural.paper.PAPER_MODEL`.
Model 2 versions source-causal fills and the synthetic sports recording boundary;
its report and durable job identities differ from model 1 even for identical input.

## Execution assumptions

Expand All @@ -32,11 +36,16 @@ fee_per_contract="0.01")`. The returned dictionary is JSON serializable.
fee. Cash, position and acquisition-cost exposure caps apply before entry.
A single pending intent prevents overlapping reservations. Fees apply both ways.
- Disconnect cancels pending intent, preserving holdings. Pending orders expire
after 30 receive-time seconds (configurable). This is **not** a source freshness
or market-open check. Source timestamps, queue priority, impact, other traders,
actual venue fees and account-wide multi-strategy risk are not modeled.
after 30 receive-time seconds (configurable). This order deadline is separate
from source freshness: normalized books require a source timestamp no more
than 30 seconds before observation. A source-dated next book must be strictly
newer than the pending intent's receipt time, or the intent cancels with
`source_not_after_intent`. Legacy books without source timestamps retain their
receipt-time behavior. Market opening, queue priority, impact, other traders, actual
venue fees and account-wide multi-strategy risk are not modeled.
- Paper lot is 0.01 contracts; this is a model assumption, not venue certification.
Kalshi recordings only; Polymarket US remains a later adapter milestone.
Full depth in a synthetic recording is a fixture assertion, not evidence of
liquidity available at a venue.

The entire recording must validate before a report is returned. Corrupt tails,
sequence gaps, regressing receive times, crossed books, ticker mismatches and
Expand All @@ -58,3 +67,72 @@ pytest tests/test_paper_recording.py -q

Passing this simulation does not establish profitability, live exchange
compatibility, approval to trade, or a completed hosted MVP.

## Same strategy across synthetic sports venues

The committed fixtures describe the same synthetic MLB full-game winner
proposition with different native venue IDs. The price thresholds, quantity and
risk caps remain identical. Only `venue` and `market_id` change in StrategySpec.

```sh
python -m neural.paper \
--strategy examples/recordings/synthetic-polymarket-us-strategy.json \
--recording examples/recordings/synthetic-polymarket-us.jsonl \
--cash 10 --fee-per-contract 0.01
```

Repeat with `synthetic-kalshi-strategy.json` and `synthetic-kalshi.jsonl`.
Both fixtures yield cash `10.52` and realized PnL `0.52`, including the explicit
`0.01` fee assumption on each traded contract in each direction. These are
constructed examples, not measured venue performance or current fee schedules.
Submitting either pair through `PaperJobs.submit` uses the same durable queue.

The new `neural.recordings` boundary autodetects both formats.
`describe_recording(path, max_events=10000)` validates the entire recording and
returns venue/native market, selected outcome, event/book/reset counts,
reconnect/disconnect counts, observation start/end, final book source timestamp,
provenance and the attached `SportsMarket`. `replay_book_recording(path)` yields
the existing stream-event shape; consumers must exhaust it to validate EOF.
Replay parses normalized metadata and rows from one open file handle. Simulation
and summary generation also compare that header with their accepted metadata,
rejecting path replacement that would mix sports identity with another book.

## Normalized recording contract

`neural-book/1` is an offline synthetic format. It does not add a Polymarket US
collector or turn a BBO quote into depth. Every JSONL line must end with a newline.

- First line: exactly `version`, `kind: "header"`, `venue`, `market_id`, `outcome`,
`sports_market`, and `provenance: "synthetic"`. The sports contract must have
the same venue and native market. `outcome` must be `"yes"`: the attached
sports identity is the named YES team-wins proposition. Normalized NO-side
recordings are rejected because the sports contract cannot identify an
opposite binary side. Legacy Kalshi recordings still support YES and NO.
- Session boundaries: `version`, `kind: "reset"`, `received_at`, and `reason`
(`connecting` or `disconnected`). Start connected, alternate boundaries, end
disconnected. Reconnect cancels pending orders and resets sequence tracking.
- Book lines: `version`, `kind: "book"`, `received_at`, `source_at`, `sequence`,
`quality: "full_depth"`, `bids`, and `asks`. Both ladders describe the selected
outcome and contain `[price, quantity]` decimal-string pairs. Bids descend and
asks ascend with unique prices, positive quantities and prices in `[0,1]`.
Each session starts at sequence 1 and increments without gaps.

Missing, one-sided, crossed, unordered, stale, future-dated or malformed books
fail the entire replay. Source timestamps cannot regress within a session;
observation timestamps cannot regress across the file. Prices and sizes retain
up to 18 fractional digits without converting through binary floats.

Normalized reports add `sports_market`, recording identity/provenance and
source timestamps/full-depth quality in book trace rows. They explicitly leave
`market_compatibility.status` as `unknown`: one recording alone cannot establish
cross-venue equivalence. Compare two attached contracts with
`compare_sports_markets(SportsMarket.from_dict(left),
SportsMarket.from_dict(right)).to_dict()` to show `compatible`, `different` or
`unknown` and reasons. Compatible fixture rules do not certify live contracts.
Legacy Kalshi recordings without source timestamps retain their trading behavior
and report shape; the new model version changes content identities. Source-dated
books also use the causal fill guard.

```sh
pytest tests/test_recordings.py tests/test_paper_recording.py tests/test_paper_worker.py -q
```
12 changes: 12 additions & 0 deletions examples/recordings/synthetic-kalshi-strategy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"venue": "kalshi",
"market_id": "fixture:kalshi:winner-1",
"outcome": "yes",
"entry_price": "0.45",
"exit_price": "0.65",
"quantity": "2",
"max_position": "2",
"max_exposure_usd": "1",
"schema_version": "1.0.0",
"kind": "price_rule"
}
7 changes: 7 additions & 0 deletions examples/recordings/synthetic-kalshi.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"version":"neural-book/1","kind":"header","venue":"kalshi","market_id":"fixture:kalshi:winner-1","outcome":"yes","sports_market":{"venue":"kalshi","event_id":"fixture:kalshi:event-1","market_id":"fixture:kalshi:winner-1","outcome_id":"fixture:kalshi:home-wins","raw_home_team_id":"fixture:kalshi:ATL","raw_away_team_id":"fixture:kalshi:TB","canonical_event_id":"fixture:mlb:2026-09-10:tb-atl:1","home_team_id":"mlb:atl","away_team_id":"mlb:tb","outcome_team_id":"mlb:atl","game_date":"2026-09-10","game_number":1,"period":"full_game","rules":{"terms":[{"name":"winner","value":"official_game_winner","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"extra_innings","value":"included","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"forfeit","value":"official_awarded_winner","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"postponement","value":"within_48_hours_of_original_start","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"cancellation","value":"fair_value","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"shortened_game","value":"official_game_result","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"settlement_source","value":"mlb_only","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"exceptional_payout","value":"fair_value","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"venue_change","value":"contract_stands","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"},{"name":"replay","value":"original_result","source_url":"https://example.invalid/nrcl-99/synthetic-rules-v1.json","source_sha256":"1dbef9a76551f101a76bf16c0612594dc964ec81795d1b30ae5b3919f9706217","retrieved_at":"2026-09-10T00:00:00Z","scope":"fixture"}],"complete":true},"schema_version":"1.0.0","sport":"baseball","league":"mlb","market_type":"winner"},"provenance":"synthetic"}
{"version":"neural-book/1","kind":"reset","received_at":"2026-09-10T18:00:00Z","reason":"connecting"}
{"version":"neural-book/1","kind":"book","received_at":"2026-09-10T18:00:01Z","source_at":"2026-09-10T18:00:01Z","sequence":1,"quality":"full_depth","bids":[["0.30","1.25"],["0.25","1.75"]],"asks":[["0.40","0.75"],["0.42","2.25"]]}
{"version":"neural-book/1","kind":"book","received_at":"2026-09-10T18:00:02Z","source_at":"2026-09-10T18:00:02Z","sequence":2,"quality":"full_depth","bids":[["0.30","1.25"],["0.25","1.75"]],"asks":[["0.40","0.75"],["0.42","2.25"]]}
{"version":"neural-book/1","kind":"book","received_at":"2026-09-10T18:00:03Z","source_at":"2026-09-10T18:00:03Z","sequence":3,"quality":"full_depth","bids":[["0.70","1.25"],["0.68","1.75"]],"asks":[["0.80","0.75"],["0.82","2.25"]]}
{"version":"neural-book/1","kind":"book","received_at":"2026-09-10T18:00:04Z","source_at":"2026-09-10T18:00:04Z","sequence":4,"quality":"full_depth","bids":[["0.70","1.25"],["0.68","1.75"]],"asks":[["0.80","0.75"],["0.82","2.25"]]}
{"version":"neural-book/1","kind":"reset","received_at":"2026-09-10T18:00:05Z","reason":"disconnected"}
12 changes: 12 additions & 0 deletions examples/recordings/synthetic-polymarket-us-strategy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"venue": "polymarket_us",
"market_id": "fixture:polymarket_us:winner-1",
"outcome": "yes",
"entry_price": "0.45",
"exit_price": "0.65",
"quantity": "2",
"max_position": "2",
"max_exposure_usd": "1",
"schema_version": "1.0.0",
"kind": "price_rule"
}
Loading
Loading