1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
//! # ChainView
//!
//! A [`ratatui`](https://docs.rs/ratatui) terminal UI for options traders:
//! real-time option chains, Greeks, and volatility surfaces (**Live** mode) and
//! IronCondor backtest result-bundle rendering (**Replay** mode). The market-data
//! clients and all the options math live upstream; this crate is the terminal
//! around them: provider adapters, normalization, and the render loop.
//!
//! `chainview` ships as **both a binary and a library**. The binary is the stock
//! terminal (`cargo install chainview`); the library exposes the
//! **semver-governed provider port**, so any developer can plug their own
//! market-data venue or broker into ChainView with no fork ([ADR-0006]).
//!
//! # The provider port: the external-integration surface
//!
//! The port an external adapter compiles against is the [`Provider`] trait, the
//! [`ProviderCapabilities`] self-declaration (built through its
//! [`builder`](ProviderCapabilities::builder)) with its dimension enums
//! ([`ChainCapability`] / [`GreeksCapability`] / [`OptionStreamCapability`] /
//! [`ChainPollCapability`] / [`AuthKind`]), and every normalized domain type the
//! trait emits: [`ChainFetch`] (with [`ExpirySource`] / [`AliasCatalog`]),
//! [`OptionChain`] / [`ExpirationDate`] (`optionstratlib`), [`UnderlyingRef`],
//! [`QuoteUpdate`], [`GreeksRow`], [`DepthLadder`], [`MarketUpdate`],
//! [`Instrument`] / [`InstrumentKey`] / [`ContractSpecFingerprint`],
//! [`SubscriptionRequest`], [`SubscriptionHandle`], [`MarketUpdateSink`],
//! [`ProviderError`], and [`ProviderId`]. Every one is re-exported from this crate
//! root — including the scalar field types the emitted values carry
//! ([`Positive`], [`Decimal`], [`OptionStyle`], [`ExpirationDate`], and the
//! [`DateTime`]`<`[`Utc`]`>` timestamps) — so an external adapter names each
//! port type through `chainview::` (`docs/03-data-providers.md` §11.1). Two
//! companion dependencies remain the adapter's own: `async_trait` (the trait
//! is `#[async_trait]`, so implementing it needs the macro) and
//! `optionstratlib` when the adapter *builds* an [`OptionChain`] itself.
//!
//! An external developer writes a thin binary that depends on `chainview` and
//! registers their adapter through the app builder:
//!
//! ```no_run
//! use async_trait::async_trait;
//! use chainview::{
//! ChainFetch, ChainViewApp, ChainViewError, ExpirationDate, MarketUpdateSink,
//! Provider, ProviderCapabilities, ProviderError, ProviderId, SubscriptionHandle,
//! SubscriptionRequest, UnderlyingRef,
//! };
//!
//! struct MyBroker {
//! id: ProviderId,
//! }
//!
//! #[async_trait]
//! impl Provider for MyBroker {
//! fn id(&self) -> ProviderId {
//! self.id.clone()
//! }
//!
//! fn capabilities(&self) -> ProviderCapabilities {
//! // Declare EXACTLY what the upstream backs: the UI gates screens off
//! // this, never off the id. Every dimension defaults to its least-capable
//! // value, so adding a future optional dimension is a source-compatible
//! // minor bump.
//! ProviderCapabilities::builder().build()
//! }
//!
//! async fn discover(&self) -> Result<Vec<UnderlyingRef>, ProviderError> {
//! Ok(vec![UnderlyingRef::new("BTC")])
//! }
//!
//! async fn fetch_chain(
//! &self,
//! _underlying: &str,
//! _expiration: &ExpirationDate,
//! ) -> Result<ChainFetch, ProviderError> {
//! // A chain-producing adapter assembles a normalized `ChainFetch` here;
//! // an overlay-only feed returns `Unsupported`.
//! Err(ProviderError::Unsupported("overlay-only: no chain discovery"))
//! }
//!
//! async fn subscribe(
//! &self,
//! _req: SubscriptionRequest,
//! _sink: MarketUpdateSink,
//! ) -> Result<SubscriptionHandle, ProviderError> {
//! // Drive an adapter-owned reconnect loop that pushes normalized
//! // `MarketUpdate`s into `_sink`; return a handle that cancels it.
//! Ok(SubscriptionHandle::new(|| { /* cancel the upstream stream */ }))
//! }
//! }
//!
//! fn main() -> Result<(), ChainViewError> {
//! let broker = MyBroker { id: ProviderId::new("mybroker")? };
//! ChainViewApp::builder()
//! .with_builtins() // the gate-clear bundled venues (Deribit)
//! .register(broker) // your own venue; the id is read from `provider.id()`
//! .run() // a reserved/duplicate id is a typed startup error, never a panic
//! }
//! ```
//!
//! # What is semver-governed
//!
//! The port is a **public, semver-governed surface** (`docs/SEMVER.md`): a change
//! to the [`Provider`] trait signature or any port type is a **major** bump;
//! adding a new *optional* capability dimension is **minor**. That minor is
//! source-compatible only because [`ProviderCapabilities`] and its enums are
//! `#[non_exhaustive]` and an adapter builds them through
//! [`ProviderCapabilities::builder`], never a struct literal. An external adapter
//! pins a `chainview` major and compiles against a stable port for that major's
//! lifetime.
//!
//! # Reserved ids and configuration namespacing
//!
//! The six built-in ids in [`RESERVED_PROVIDER_IDS`]
//! (`deribit`/`tastytrade`/`dxlink`/`ig`/`alpaca`/`ibkr`) are reserved: an external
//! registration that reuses one is [`RegistryError::ReservedId`], and a duplicate
//! id is [`RegistryError::DuplicateId`] — both typed startup errors, never a
//! panic. Growing the reserved set later is a **major** bump (it can invalidate a
//! working external id) and is announced one minor ahead. Every provider —
//! built-in or external — reads its non-secret settings from `providers.<id>.*`
//! and its credentials from `CHAINVIEW_<ID>_*` (the id transliterated to a
//! shell-safe segment through a total bijection, `docs/07-configuration.md` §5.1);
//! the reserved-id rule guarantees an external provider can never shadow a
//! built-in's namespace.
//!
//! # Security boundary and scope
//!
//! An externally registered provider is **outside ChainView's credential audit
//! boundary** — its author owns its credential hygiene ([ADR-0006] §7,
//! `docs/SECURITY.md` §5). What ChainView still guarantees by construction is that
//! **its own code never logs what crosses the port**: the port carries only
//! normalized domain types (no credentials), and [`ProviderError`] is
//! structurally redaction-safe. Dynamic/plugin loading (`dlopen`) is **out of
//! scope for v1** — an adapter is a compile-time Rust dependency, not a loaded
//! object (Rust has no stable ABI).
//!
//! # Status
//!
//! Pre-1.0 and in active development: the public API — including the provider
//! port — may change until `v1.0.0`, after which the SemVer rules above are
//! binding. Follow progress at <https://github.com/joaquinbejar/ChainView>.
//!
//! [ADR-0006]: https://github.com/joaquinbejar/ChainView/blob/main/docs/adr/0006-open-provider-extension.md
pub
pub
pub
pub
pub
pub
pub
pub
// In-crate Part B integration tests (issue #22) that require `pub(crate)`
// internals a `tests/*.rs` (a separate crate seeing only the public API) cannot
// reach — the live-path golden render (assembled `ChainStore` merge + the
// `pub(crate)` chain-matrix `draw` + the recorded-fixture assembler + the
// `#[cfg(test)]` golden helper), the id-agnostic render-parity proof, and the
// draw-path no-I/O assertion. The public-surface faux-provider conformance and
// the layering arch test live under `tests/` (`docs/TESTING.md` §7).
// In-crate replay-path integration tests + the committed replay render goldens
// (issue #37, the v0.3 acceptance gate). Like `tests_integration`, these live
// in-crate because they use the `pub(crate)` render-golden harness
// (`assert_golden`/`buffer_to_text`) and the `pub(crate)` two-level key dispatch —
// none on the semver-governed surface (`docs/TESTING.md` §7/§4).
// In-crate v0.4 acceptance-gate capability-matrix reconcile (issue #46): the
// single executable table asserting every bundled adapter's live
// `ProviderCapabilities` equals its documented `docs/03-data-providers.md` §8 row
// (cell by cell), with the gated adapters checked under their feature and the
// IG-deferred row marked N/A. In-crate because it reads each adapter's
// crate-private `<id>_capabilities()`, none on the public surface
// (`docs/TESTING.md` §5, `docs/03-data-providers.md` §8).
// Bench-only support surface (issue #21), compiled ONLY under the `bench` Cargo
// feature. It exposes the constructors the `benches/*` targets need — a
// populated render `App`, a seeded `ChainStore`, a scripted `MarketUpdate`
// burst, and the Deribit `ticker.`/`book.` → coalescing-merge harness — through
// the crate's own public types, so a `benches/*.rs` (a separate crate that sees
// only the public API) can reach the three hot paths WITHOUT the pure-render
// `chain::draw` or the `ChainStore` being promoted to the default public
// surface. It is an INTERNAL, UNSTABLE harness with NO SemVer guarantee: even
// under `--features bench` it is EXCLUDED from the semver-governed public API and
// may change or be removed in any release without notice (see the module docs and
// `docs/SEMVER.md`). Because it is `#[cfg(feature = "bench")]`, a normal build
// never compiles it and the default public surface is unchanged
// (`docs/06-performance.md` §4, `docs/TESTING.md` §11).
// Fuzz-only harness surface (issue #53, docs/TESTING.md §13.4), compiled ONLY
// under the `fuzz` Cargo feature. It exposes the byte-in entry point the
// separate `fuzz/` cargo-fuzz crate's `fuzz_provider_normalize` target needs to
// reach the `pub(crate)` Deribit normalize seam (raw upstream DTOs stop at
// `src/providers/*`). Like `bench_support`, it is `#[cfg(feature = "fuzz")]` and
// OFF by default, so a normal build never compiles it and the semver-governed
// public API is unchanged. The replay decode surface it complements is already
// public (`BundleReader`), so the `fuzz_replay_decode` target needs nothing here
// (docs/SECURITY.md §7).
// The application state machine + fan-in (`docs/02-tui-architecture.md` §3, §4):
// the `App`, the `Live | Replay` `Mode`, the mode-scoped `LiveScreen`/
// `ReplayScreen`, the composite source/overlay bindings and per-screen state, and
// the capability-read reachability helper. Plus the open-provider entry points
// (`docs/02-tui-architecture.md` §11, ADR-0006): `ChainViewApp::builder()` and
// its `ChainViewAppBuilder`, so an external thin binary can register its own
// `Box<dyn Provider>` and `run()`. The application-owned `ProviderRegistry` is
// deliberately NOT re-exported — external code composes through the builder, and
// the UI never receives the registry. Public so the app builder (#12) and the
// render loop (#13) can name and drive them.
pub use ;
// The closed event set folded by the state machine and the render -> data
// command channel (`docs/02-tui-architecture.md` §4).
pub use ;
// The pure draw dispatch and the synchronous, event-driven render loop
// (`docs/02-tui-architecture.md` §7, §8, §9): `render` (pure over `&App`), the root
// layout, the loop driver, the bounded `AppEvent` channel, and the tick/input task
// seams the supervisor (#11) owns. These are the render-loop **composition
// internals**; they are exposed provisionally so the loop is reachable while it has
// no runtime caller yet (its `registry::run` composition seam is wired in #15).
// NOTE: this is NOT the ADR-0006 external-extension surface — ADR-0006's external
// model is `ChainViewApp::builder()…run()`, not a hand-rolled loop. Nothing here is
// semver-frozen pre-v1.0; #15's `run()` becomes the canonical driver and revisits
// whether these stay public or narrow to `pub(crate)`.
pub use ;
pub use ViewState;
pub use ;
// The chain-matrix view models (`src/ui/chain.rs`, issue #18,
// `docs/01-domain-model.md` §8): `ChainRow`/`LegView`, projected from the domain
// `OptionChain` at draw time and borrowed, never owned. Public so the render
// goldens (#19) and downstream screens (#25) can name the projected shapes.
pub use ;
// The `GraphData` → ratatui dataset adapter (`src/ui/graph.rs`, issue #23,
// `docs/05-views-and-ux.md` §4): the fallible projection of an `optionstratlib`
// `GraphData` into a ratatui chart shape (borrowed `(f64, f64)` points + `[f64; 2]`
// axis bounds + precomputed labels), and the cache handle that keeps `GraphData`
// construction off the draw path. Public so the payoff screen (#27), the replay
// screens (#35), and the vol surface (#47) can hold the cache on their state and
// name the projected shapes.
pub use ;
// The theme + render surface: the `NO_COLOR`-aware `Theme`, the
// help-overlay/status/keybar renderers, the `StrikeRelation` K/S bucket and its
// markers/spans, the responsive chain column-drop policy, and the too-small guard
// (`docs/05-views-and-ux.md` §7, §8, issue #14). Public so the chain matrix (#18)
// and the render goldens (#19) can name and reuse the markers, styles, and column
// policy.
pub use ;
// The single-source keybinding map — pure data + resolution in the **application**
// layer (`src/app/keymap.rs`, `docs/05-views-and-ux.md` §3, issue #14). Both
// `App::dispatch_key_global` and the help overlay (`src/ui/theme.rs`) read this one
// table, so dispatch and documentation cannot drift. Public so the chain matrix
// (#18) and the render goldens (#19) can name and reuse the keymap.
pub use ;
pub use ;
pub use ;
pub use ;
// The replay-mode domain types (`src/replay/mod.rs`, issues #29/#30,
// `docs/01-domain-model.md` §9, `docs/04-replay-mode.md` §2/§3): ChainView's
// typed, read-only views of the IronCondor result bundle — the permissive
// `BundleManifest`, the narrow `CapitalConfig` projection, the four strict
// Parquet-backed rows (`Fill`/`EquityPoint`/`PositionRow`/`GreeksAttribution`),
// the closed `PositionSide`/`ExecMode` enums, the `contract_id` grammar
// constants, and the `BundleReader`/`LoadedBundle` surface. Money is integer
// cents; the only `f64` is `EquityPoint::drawdown`. `OptionStyle` is re-exported
// from `optionstratlib` below.
//
// Issue #30 adds the reader body and the untrusted-input hardening spine:
// `BundleReader::{open, open_with_ceilings, load, load_cancellable}`, the
// `ResourceCeilings` config knobs and their documented `MAX_*` /
// `DECODED_OVERHEAD_PERMILLE` defaults, and the `SUPPORTED_SCHEMA` gate. Issue #31
// wires the typed per-column decode (`src/replay/tables.rs`) into that batched,
// budget-measured loop, so `load` returns the four tables populated and sorted;
// the cross-table validation chain (#32) is still written against this surface.
// The public surface gains no new item beyond the `BundleError::Schema` decode
// variant — the decoders themselves are `pub(crate)`.
//
// Issue #32 lands the post-decode validation chain (wired inside `load`, so the
// reader surface is unchanged) plus the cross-repo equivalence oracle:
// `compare_bundles` returns `Ok(())` or the first typed `BundleDivergence`, and
// `ORACLE_ABS_TOL`/`ORACLE_REL_TOL` are the combined-tolerance constants that must
// match IronCondor's copy exactly (`docs/04-replay-mode.md` §5). The validation
// checks and the `contract_id` parser stay module-private — only the oracle is
// public, for the cross-repo agreement check.
//
// Issue #33 adds the timeline scrub model (`src/replay/timeline.rs`,
// `docs/04-replay-mode.md` §4, `docs/01-domain-model.md` §10): `TimelineCursor`
// (O(1) `StepBy` / O(log n) `Step` seeks over the integer `step` clock, the
// post-fill open-position set, and the as-of slices), plus the domain `Playback` /
// `PlaybackSpeed` playback model and `TimelineCursor::advance_playback`. The cursor
// consumes `event::SeekTo`.
//
// Issue #34 (the app-state wiring) collapsed the earlier `app::Playback` stub into
// this single domain `Playback` — there is now exactly one playback type, exported
// bare from the crate root (the earlier transitional re-export alias is gone), and
// the app-state field (`ReplayState::play`) and the tick fold reference this type.
pub use ;
// The PUBLIC, semver-governed provider port surface (`docs/03-data-providers.md`
// §2, §11.1): the trait, the capability self-declaration + its builder + every
// dimension enum, and the port helper types. The emitted domain types
// (`ChainFetch`/`ExpirySource`/`AliasCatalog`, `MarketUpdate`, `ProviderError`,
// `ProviderId`) are re-exported above from their home layers.
pub use ;
// The terminal lifecycle surface (`docs/02-tui-architecture.md` §6, ADR-0001):
// the RAII restore guard and the panic-hook restore installer. Public so an
// external thin binary (ADR-0006) can drive the same guaranteed restore. These
// stay the stable restore entrypoints for hand-rolled external binaries and are
// intentionally NOT narrowed to `pub(crate)` once `ChainViewApp::builder().run()`
// (issue #11) owns the guard internally.
pub use ;
// The provider port and the domain speak `optionstratlib`'s chain-model and
// numeric vocabulary (`docs/01-domain-model.md` §3–§4,
// `docs/03-data-providers.md` §11.1, ADR-0006 §5): `OptionChain` is the chain a
// `ChainFetch` wraps, `ExpirationDate` is the `Provider::fetch_chain` /
// `UnderlyingRef` expiry type, and `Positive` / `Decimal` / `OptionStyle` are the
// numeric/style types the emitted `QuoteUpdate` / `GreeksRow` / `InstrumentKey`
// carry. Re-export all five at the crate root so an external adapter can name
// every type in the port's signatures through `chainview::` alone, without a
// direct `optionstratlib` dependency (a chain-PRODUCING adapter still depends on
// it to BUILD an `OptionChain`). These are part of the semver-governed port
// surface (`docs/SEMVER.md`, provider-port versioning).
pub use OptionChain;
pub use ;
pub use ;
// The timestamp scalar every emitted event/identity value carries
// (`QuoteUpdate`/`GreeksRow`/`DepthLadder` received/event times,
// `InstrumentKey::expiration_utc`, `ExpirySource::expiration_utc`). No exported
// fn produces one, so without this re-export a chain-producing or streaming
// external adapter would need a direct `chrono` dependency to construct the
// values the port emits (#43 review).
pub use ;