piker/.agents/skills/piker-fsp-expert/architecture.md

118 lines
4.2 KiB
Markdown
Raw Normal View History

# FSP Runtime Architecture
## Current Topology
```text
provider backend
-> datad feed bus
-> source Flume
-> rt ShmArray (typically 1 second)
-> hist ShmArray (typically 60 seconds)
-> samplerd sample and backfill events
chart actor
-> FspAdmin
-> chart.fsp_N worker actor
-> cascade()
-> connect_streams()
-> destination Flume / ShmArray
-> targeted Viz updates
```
The source path is implemented mainly by `piker.data.feed`,
`piker.data.flows`, `piker.data._sampling`, and `piker.tsp._history`. The FSP
path is implemented by `piker.fsp._api`, `piker.fsp._engine`, and operator
modules such as `_volume` and `_momo`. Worker and graphics lifecycle currently
live in `piker.ui._fsp` and `piker.ui._display`.
## Operator Contract
`@fsp` wraps an async generator. Its first yield is historical output computed
from the source array. Later yields are `(field, value)` realtime mutations.
`connect_streams()` converts the first yield into the destination structured
array, aligns time and absolute bounds, then updates the current destination
row from realtime quote events.
`samplerd` owns row advancement. At a new source sample, the cascade copies or
zeros the previous destination row and aligns destination timestamps to the
source. Quote-time mutations and sample-time appends are distinct operations.
## SHM Synchronization
Treat each array as an absolute half-open range:
```text
[first, last)
```
For a normal source history prepend:
```text
before: src=[10000, 12000) dst=[10000, 12000)
after: src=[ 8000, 12000) dst=[10000, 12000)
```
The current step is unchanged, but destination history is missing 2,000 rows.
The destination must be recomputed or incrementally repaired.
For a normal one-step source lead:
```text
src=[8000, 12001) dst=[8000, 12000)
```
The cascade should append exactly one destination row, not recompute history.
Use `_first.value` and `_last.value` for synchronization. `ShmArray.index` is
the last bound modulo capacity and is not a complete absolute position.
Snapshot bounds once per check; shared first and last counters do not form a
transactional pair.
## Backfill Event Ordering
1. `start_backfill()` receives an older provider frame.
2. `shm_push_in_between()` writes the frame and publishes the earlier first
bound.
3. `notify_backfill()` asks `samplerd` to broadcast the market and timeframe.
4. Relevant cascades treat the event as a history revision. A prepend changes
bounds; an in-place gap repair may not.
5. Each cascade cancels its current compute task and waits for completion.
6. The historical phase recomputes against the newest readable source range.
7. Destination bounds are published and the UI receives an `fsp_update`.
8. Only `Viz` objects backed by that destination token redraw.
Multiple provider frames may arrive while a recomputation runs. The cascade
must reject mixed-bound bootstrap results, replay in-place revisions, recheck
bounds after restart, and converge on the newest source range.
## Actor-Local State
`Fsp._flow_registry`, attached SHM handles, caches, and nurseries are local to
each actor. Never assume worker siblings share warmed registries or Python
objects. Graph dependencies currently rely on startup order, as demonstrated
by `flow_rates` starting only after `dolla_vlm`.
## Cross-Subsystem Boundaries
- Data ingest should normalize identity and event-time semantics before an FSP
consumes a stream.
- UI renderers should consume revision/range information without controlling
computation correctness.
- Clearing may consume a derived signal, but EMS remains responsible for order
intent, status, routing, fills, positions, and accounting.
- FSP output exposed as a feed needs the same fan-out, identity, lifecycle,
and backpressure contracts as provider feeds.
## Architectural Gaps
The current protocol infers history revisions from shared bounds. A future
revision message should carry at least:
```text
(fqme, timeframe, revision, first, last, start_ts, end_ts)
```
Destination publication should identify the source revision used. This allows
coalescing, range repair, stale-result rejection, and deterministic graph
dependency startup without turning every prepend into a global wakeup.