Skip to main content

Copy execution transparency (Phase 3)

Per-copy execution facts — latency, realized slippage, fill vs failure — captured every copy attempt, surfaced as per-profile transparency report. Off by default; enable with App:Copy:TransparencyEnabled=true. When off, copy engine byte-for-byte unchanged: host emits to no-op sink, nothing written.

How it works​

CopyEngineHost ──Record(fact)──▶ ICopyEventSink
│
(transparency off) NullCopyEventSink → discards (default; zero hot-path cost)
(transparency on) ChannelCopyEventSink → bounded in-memory channel (DropOldest)
│
▼
CopyExecutionDrainer (BackgroundService)
│ batches every App drain interval
▼
CopyExecution append-only table ◀── GET /api/copy/profiles/{id}/transparency
  • Hot path stays free of I/O. Host calls ICopyEventSink.Record(...) — non-blocking, never-throwing enqueue. Never awaits, never touches DB, never blocks order execution.
  • Loss preferred over back-pressure. Channel bounded (CopyExecutionChannelCapacity) with DropOldest: if DB drainer stalls, oldest transparency rows dropped rather than delay a copy. Transparency = best-effort telemetry, not trading dependency.
  • Out-of-band persistence. CopyExecutionDrainer drains channel in batches (CopyExecutionDrainBatchSize) on CopyExecutionDrainInterval, writes CopyExecution rows through scoped DataContext. Final flush on shutdown.
  • Facts, not commands. CopyExecution = append-only log (like InstanceLog/AuditLog), not aggregate. Read model queries it directly (CQRS-lite), aggregates in memory.

What is recorded​

One CopyExecutionRecord per copy attempt on one destination:

KindWhenCarries
Openedcopy order placedsymbol, side, wire volume, master price, realized slippage (points), latency (ms)
Failedcopy open threw/rejectedsymbol, side, master volume/price, latency, failure reason (exception type)

(Closed/Skipped/Reconciled exist in enum for future expansion.)

The report​

GET /api/copy/profiles/{id}/transparency (owner-scoped) returns, over most recent 500 facts:

  • Summary — total, opened, failed, fill rate, average latency (ms), average slippage (points).
  • Recent — raw recent facts (destination, source position, symbol, side, volume, master price, slippage, latency, reason, timestamp).

Configuration (App:Copy)​

SettingDefaultEffect
TransparencyEnabledfalseTurn per-copy fact capture + drainer on for node.

Channel capacity, drain batch size, drain interval = CopyDefaults constants (CopyExecutionChannelCapacity / CopyExecutionDrainBatchSize / CopyExecutionDrainInterval).

Tests​

  • Unit (CopyTransparencyTests) — successful open emits Opened fact with right symbol/side/volume/latency; rejected open emits Failed fact with reason. Driven through capturing sink.
  • Integration (CopyExecutionDrainerTests, real Postgres) — drainer persists buffered facts to CopyExecution log; empty sink writes nothing.
  • DST — host change fire-and-forget with no-op default sink, so deterministic copy stress suite stays green (23/23).