Go's Pipeline and context for a Streaming Volatility-Regime Sizer
Fan-in realized-vol estimators, deadline-bounded regime reads, and the atomic size-multiplier guard.
The Ops lesson said the regime is a stream, not a snapshot, and that a stalled feed must degrade to a known-conservative default rather than a silent stale number. Go's pipeline, context, and atomic.Pointer are exactly the three tools that turn that sentence into a running program.
Today's Ops lesson built a sizing discipline that reads a volatility regime and a cross-book correlation continuously, and it named one operational requirement that the quant prose cannot satisfy on its own: the regime read must be fresh, it must be bounded so a slow feed cannot hang a size decision under stress, and the hot sizing path must be able to read the current multiplier without contending on a lock. Those three requirements map cleanly onto three Go idioms. The pipeline pattern fans several estimators into one merged stream. The context package bounds a read by a deadline and cancels the stragglers. And atomic.Pointer publishes an immutable snapshot of the regime so readers never tear and never block.
§ IFrame
The shape is a producer-consumer system with an asymmetry. On the producing side, several volatility and correlation estimators each read their own feed and emit samples at their own pace. On the consuming side, one sizing path reads the current regime multiplier many times per second and cannot afford to wait. Between them sits a regime computer that merges the estimator samples, folds them into a single RegimeState, and publishes it.
Two failure modes govern the design. A slow estimator must never block the merge, because in a shock the slow feed is the one under load. And a stale estimate must never masquerade as a fresh one, because the whole point of the Ops discipline is that a stalled feed degrades to conservative, not to yesterday's calm number. Go gives us a direct tool for each: a deadline for the first, a freshness stamp checked on read for the second.
§ IILanguage Idiom
The pipeline pattern
A Go pipeline is a series of stages connected by channels, where each stage is a goroutine that receives on an inbound channel and sends on an outbound one. Bodner's concurrency treatment (Learning Go, Ch 10, pp. 232–236) lays out the canonical shape: a source stage, zero or more transform stages, and a sink, with a fan-in stage merging several inbound channels into one when the work is parallel. Our estimators are the sources, the merge is the fan-in, and the regime computer is the sink that folds and publishes.
context for cancellation and deadlines
The context package (Learning Go, Ch 12, pp. 282–289) is Go's idiom for carrying a cancellation signal and a deadline across an API boundary. A context.Context threads through every stage; when the top-level context is cancelled, every goroutine selecting on ctx.Done() unwinds. context.WithTimeout derives a child context that cancels itself after a duration, which is exactly the deadline-bounded read the Ops lesson demands: a size decision that cannot get a fresh regime within its budget proceeds on the conservative default instead of waiting.
atomic.Pointer for lock-free publication
The 06-19 regime-engine lesson established the pattern this lesson reuses: publish an immutable state struct through an atomic.Pointer[T], so a writer swaps in a whole new snapshot with Store and readers pull the current one with Load without a mutex. Because the pointed-to struct is never mutated after publication, a reader's snapshot is internally consistent by construction. The hot sizing path does a single atomic Load and never contends with the writer.
§ IIICode Worked Example
Start with the value the pipeline carries. A VolSample is one estimator's reading, tagged with its source and the time it was produced. The RegimeState is the published snapshot the sizer reads.
type VolSample struct {
Source string
RealizedVol float64
CorrToMkt float64
At time.Time
}
type RegimeState struct {
Multiplier float64
Haircut float64
FreshAt time.Time
}
Each estimator is a source stage: a goroutine that emits samples until its context is cancelled. The select on ctx.Done() is what makes the stage cooperatively cancellable, so shutting down the top-level context unwinds every estimator without a leaked goroutine.
func estimator(ctx context.Context, source string, feed <-chan float64, out chan<- VolSample) {
for {
select {
case <-ctx.Done():
return
case px, ok := <-feed:
if !ok {
return
}
out <- VolSample{Source: source, RealizedVol: ewmaVol(px), CorrToMkt: corr(px), At: time.Now()}
}
}
}
The fan-in merges several estimator outputs into one channel. Each inbound channel gets its own forwarding goroutine, and a sync.WaitGroup closes the merged channel once every source has drained. This is the standard fan-in from the concurrency chapter, and it is what lets the regime computer read from a single channel regardless of how many estimators are running.
func fanIn(ctx context.Context, sources ...<-chan VolSample) <-chan VolSample {
merged := make(chan VolSample)
var wg sync.WaitGroup
wg.Add(len(sources))
for _, s := range sources {
go func(c <-chan VolSample) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case v, ok := <-c:
if !ok {
return
}
select {
case merged <- v:
case <-ctx.Done():
return
}
}
}
}(s)
}
go func() { wg.Wait(); close(merged) }()
return merged
}
The regime computer is the sink. It folds incoming samples into a running view and republishes a fresh RegimeState through the atomic pointer. The multiplier is the vol-target ratio clamped to a floor, and the haircut rises with the average correlation to the market factor, exactly the two quantities the Ops lesson defined.
func (s *Sizer) run(ctx context.Context, in <-chan VolSample) {
for {
select {
case <-ctx.Done():
return
case v, ok := <-in:
if !ok {
return
}
mult := clamp(baselineVol/v.RealizedVol, floorMult, 1.0)
hair := haircutFromCorr(v.CorrToMkt)
s.state.Store(&RegimeState{Multiplier: mult, Haircut: hair, FreshAt: v.At})
}
}
}
The sizer's read path is the deadline-bounded piece. It loads the current state, and if that state is older than the freshness budget it does not use it. The caller passes a context with a timeout; if a fresh state does not arrive within the budget the read returns the conservative default. This is the degrade-to-conservative rule made literal.
func (s *Sizer) Regime(ctx context.Context, maxStale time.Duration) RegimeState {
deadline := time.NewTicker(2 * time.Millisecond)
defer deadline.Stop()
for {
if st := s.state.Load(); st != nil && time.Since(st.FreshAt) <= maxStale {
return *st
}
select {
case <-ctx.Done():
return RegimeState{Multiplier: floorMult, Haircut: maxHaircut, FreshAt: time.Now()}
case <-deadline.C:
}
}
}
Two properties make this correct under stress. The read never blocks longer than the context deadline, so a stalled estimator cannot hang a size decision. And when the deadline fires, the returned state is the conservative floor — smallest multiplier, largest haircut — so a feed outage biases the book toward less risk, never toward stale calm. The call site sizes with one line: weight := target * sz.Regime(ctx, 500*time.Millisecond).Multiplier.
Regime returns the floor multiplier and the max haircut. Absence of a fresh regime is not a neutral default — it is the most conservative one. A feed outage during a shock makes the book smaller, which is the only safe direction to fail.
§ IVConnection to Today's Ops Lesson
The Ops lesson specified the regime as a stream with three demands. This program satisfies each one structurally. The fan-in pipeline is the stream — several estimators, one merged read, no single slow feed able to block the merge. The context deadline is the bound — a size decision that cannot get a fresh regime in its budget proceeds on the default rather than waiting through a shock. And the freshness stamp checked on Load is the anti-stale rule — a regime older than maxStale is treated as absent, and absence degrades to the conservative floor. The Ops prose said a stalled feed must degrade to a known-conservative default rather than a silent stale number; the Regime method is that sentence compiled.
§ VPrior-Lesson Reach
Two prior Go lessons supply the parts. The June 29 lesson used context deadline propagation and sync/atomic counters to enforce a per-tenant token budget; this lesson reuses the same deadline discipline for the read path and the same atomic publication for the shared state. The June 19 lesson built a regime state engine on atomic.Pointer with lock-free reads; this lesson takes that publication pattern and feeds it from a fan-in pipeline instead of a single writer. The progression is deliberate: from a single atomic-published state, to a bounded read of it, to a fan-in that keeps it fresh from many sources.
§ VIClosing
The Ops discipline is only as good as the freshness of the regime it reads, and freshness under stress is a concurrency property, not a quant one. Go's pipeline fans the estimators, context bounds the read, and atomic.Pointer publishes a snapshot the hot path can load without a lock. The result is a sizer that never blocks on a slow feed and never trusts a stale one, which is the exact operational contract the sizing rule needs to survive the shock it was built for.
A stalled feed should make the book smaller, never make it blind. Bound the read, stamp the state, and let absence mean caution.