Skip to content

The Guide

The coordinates to where the machine is.

A machine that measures thoughts against reality. (A “thought” is a vector — the machine encodes a candle into a high-dimensional vector and measures whether that vector predicted value or destruction.) Did this thought produce value or destroy it? Built leaves to root from docs/proposals/2026/04/007-exit-proposes/.

This document defines every struct and its interface. No implementation. The wat files — s-expression specifications in Scheme-like syntax — implement what this document declares. The wat language is defined in LANGUAGE.md in the wat repo (~/work/holon/wat/LANGUAGE.md) — grammar, host forms, core forms, type annotations, structural types (struct, enum, newtype). The path assumes the standard workspace layout; the document stands alone without it.

Each section declares its dependencies. The order of sections IS the build order — leaves first, root last. Each file’s dependencies are already written before it appears.

Holon-rs primitives (provided by the substrate)

Section titled “Holon-rs primitives (provided by the substrate)”

These are NOT specified in this tree. They are provided by holon-rs.

  • atom(atom name) → Vector — name a thought
  • bind(bind a b) → Vector — compose two thoughts
  • bundle(bundle &vecs) → Vector — superpose many thoughts
  • cosine(cosine a b) → f64 — measure similarity
  • reckoner — the learning primitive. “Reckon” means both “to count” and “to judge.” Market observers use discrete mode (Up/Down classification). Exit observers use continuous mode (distance regression). A reckoner keeps accounts and delivers a verdict. It accumulates experience. Internally it builds a discriminant — the direction that separates outcomes. It reckons a verdict from a new input via cosine against the discriminant. Old experience decays. The verdict sharpens over time through recalibration. One primitive, multiple readout modes:
    • (make-reckoner config) → Reckoner
      • config is a reckoner-config enum (defined in construction order) containing dims, recalib-interval, and readout mode:
        • (labels "Up" "Down") → discrete. N labels. Classification.
        • (default-value 0.015) → continuous. Scalar. Regression.
    • (observe reckoner thought observation weight) — both modes. observation is a label (discrete) or a scalar (continuous). Not the Outcome enum — observation is the general term for what the reckoner learns from. Outcome is a specific kind of observation.
    • (predict reckoner thought) — both modes. returns Prediction — the reckoner’s verdict.
    • (decay reckoner factor) — both modes. Old experience fades.
    • (experience reckoner) → f64 — how much? 0.0 = ignorant.
    • (recalib-count reckoner) → usize — both modes.
    • holon-rs has both modes. Reckoner with ReckConfig::Discrete and ReckConfig::Continuous. The Reckoner is the only learning primitive in holon-rs.
    • Coordinates for later: circular readout (periodic values that wrap), ranked readout (orderings). Other readout modes are possible — the reckoner mechanism is general. These are future work, not current.
  • curve — measures how much edge a reckoner has earned. After many predictions resolve (correct or wrong), the curve answers: “when you predicted strongly, how often were you right?” Input: prediction strength. Output: accuracy. A continuous surface. How much edge, not whether edge.
    • (make-curve) → Curve
    • (record-prediction curve conviction correct?) — feed each resolved prediction
    • (edge-at curve conviction) → f64 — query: how accurate at this conviction level?
    • (proven? curve min-samples) → bool — enough data to trust? The curve self-evaluates — it reports amplitude and exponent from accumulated data. That is measurement, not learning. Resolved: the curve communicates via one scalar. The producer calls edge-at(conviction) and attaches the result to its message. The consumer encodes it as a scalar fact: (bind (atom "producer-edge") (encode-linear edge 1.0)). The consumer’s reckoner learns whether the edge predicts Grace. No meta-journal. No curve snapshot. No new primitives. One f64. The problem was never “how do I learn a curve” — it was “how do I communicate what a curve knows.” One number. The tools compose.
  • OnlineSubspace — learns what normal looks like. Measures how unusual a new input is (the residual). High residual = unusual. Low = boring.
    • (update subspace vector)
    • (anomalous-component subspace vector) → Vector
    • (residual subspace vector) → f64
    • (sample-count subspace) → usize
  • ScalarEncoder — continuous value → vector
    • (encode-log value) → Vector
    • (encode-linear value scale) → Vector
    • (encode-circular value period) → Vector
  • VectorManager — deterministic atom → vector allocation
    • (get-vector vm name) → Vector
  • Utility operations — from std/vectors.wat in the wat language, provided by holon-rs:
    • (amplify vec vec weight) → Vector — scale a vector by weight
    • (zeros) → Vector — zero vector at the current dimensionality
    • (negate vec) → Vector — flip sign
    • (difference a b) → Vector — subtract
    • (blend a b ratio) → Vector — weighted interpolation
    • (prototype vecs) → Vector — normalized average
    • (online-subspace dims k) → OnlineSubspace — constructor (k = principal components)
    • (discriminant reckoner label) → Vector | None — the learned separation direction

Before the structs. Before the constructors. The meanings. Each definition can only reference definitions above it. If you want the shapes first, skip to Forward declarations and return here when a name is unfamiliar.

  • Up / Down — direction labels. The market observer predicts: the price will go up or the price will go down. That’s the prediction. When a trade resolves, the actual direction is routed back to the market observer. The reckoner learns from reality.

  • Grace / Violence — accountability labels. “Did this trade produce value or destroy it?” Grace = profit. Violence = loss. More Grace, more capital. More Violence, less capital. The Grace/Violence ratio IS the answer to “do we trust this team of observers?”

  • Labels — Up/Down and Grace/Violence are labels. Labels are not booleans. They carry weight — how decisively the market answered. A strong Grace teaches harder than a marginal one. Two pairs for learning: Direction (Up/Down) and Accountability (Grace/Violence). A third pair for action: Side (Buy/Sell) — derived from Up/Down, used on proposals and trades. Note: Side × Direction forms a 2×2 grid where Buy+Up = Grace, Buy+Down = Violence, Sell+Down = Grace, Sell+Up = Violence. This is a THEOREM — true when the system is coherent — not a definition. Outcome is measured independently because incoherence (the system acting against its own prediction) is where the machine learns the most.

  • Candle — one period of market data. Raw: six data values (open, high, low, close, volume, timestamp) plus an asset pair for routing (source-asset, target-asset) — eight fields total on the RawCandle struct. Enriched: the raw data plus 100+ computed indicators (moving averages, oscillators, volatility, momentum, structure).

  • Indicator — a derived measurement from price history. RSI, MACD, ATR (Average True Range — a measure of volatility), Bollinger Bands. Each one is a streaming computation — it needs all prior candles to produce the current value. Indicators produce SCALARS, not zones. “RSI at 0.73” not “RSI is overbought.” The reckoner learns where the boundaries are.

  • Magic numbers — k_trail (trailing stop), k_stop (safety stop), k_tp (take-profit), k_runner_trail (runner trailing stop). When a trade is open, four distances matter: how far to trail the price, how far to let it move against you, how far to let it go before taking the win, and how far to trail the runner after principal recovery. Someone chose these as multipliers of ATR (defined above). They are the last magic — crutches returned when the system has no experience. As observations accumulate, the crutch is replaced by what the market said.

  • Discriminant — the direction in thought-space that separates two outcomes. The reckoner builds it from accumulated observations. “Which direction in 10,000 dimensions best separates Grace from Violence?” The discriminant IS that direction. Cosine against it → conviction.

  • Conviction — how strongly the reckoner predicts. The cosine between the thought and the discriminant. High conviction = many facts voting in the same direction. Low conviction = ambiguous.

  • Fact — a named observation about the world, composed from atoms. “RSI is at 0.73.” The composition IS a vector. The vector IS the fact.

  • ThoughtAST — a deferred fact. AST = Abstract Syntax Tree — a tree of operations described as data, not yet executed. The vocabulary produces these. The ThoughtEncoder evaluates them.

  • Thought — a bundle of facts. Many fact-vectors superposed into one vector. The thought is what an observer perceived about this candle. A “composed thought” is a market thought bundled with exit facts — it appears on PaperEntry, Proposal, TradeOrigin, Resolution, and TreasurySettlement. Same vector, stashed at different lifecycle points.

  • Lens — which vocabulary subset an observer thinks through. A momentum lens selects momentum-related facts. A regime lens selects regime-related facts. A generalist lens selects all facts. The lens IS the observer’s identity — it determines what thoughts the observer thinks.

  • N and M — N is the number of market observers (today: 6, one per MarketLens variant). M is the number of exit observers (today: 4, one per ExitLens variant). Every combination gets a broker. N×M = 24 brokers today. Each broker’s identity is the set {“market-lens”, “exit-lens”} — two names today, more later.

  • Observer — an entity that perceives and learns. It has a lens and accumulated experience. Two kinds: market observers predict direction (Up/Down) using a discrete reckoner. Exit observers estimate distance (optimal exit) using continuous reckoners.

  • Exit reckoners — the exit observer has four continuous reckoners, one per distance: trail, stop, tp, runner-trail. “For a thought like THIS, what distance did the market say was optimal?” Replaces magic numbers with measurement.

  • ScalarAccumulator — per-magic-number f64 learning (extraction mechanism detailed in the ExitObserver section below). Each scalar value is encoded as a vector (using ScalarEncoder, defined above in primitives). Grace outcomes accumulate into a Grace prototype. Violence outcomes accumulate into a Violence prototype. To extract: try candidate values, encode each, cosine against the Grace prototype. The candidate closest to Grace wins. “What value does Grace prefer overall?” Global per-pair — one answer regardless of thought.

  • Paper trade — a “what if.” A hypothetical trade that tracks what WOULD have happened. Both sides (buy and sell) are tracked simultaneously. When both sides resolve, the paper teaches: what distance would have been optimal? Papers are the fast learning stream — cheap, many, every candle. Papers and active trades are treated equally by the learning system. Both use whatever the reckoner knows at the time. Both start ignorant (crutch values). Both feed Grace/Violence back to the broker.

  • Prediction — what the reckoner returns when asked. Data, not action. An enum — two honest branches, no dead fields:

    • Discrete: a list of (label, score) pairs + conviction. The consumer picks.
    • Continuous: a scalar value + experience. Pattern-match to know which mode. The type tells you. The Discrete variant is keyed by label name (String) — the broker’s reckoner returns (“Grace”/“Violence”, score) pairs. The market observer’s returns (“Up”/“Down”, score) pairs. Same enum. Different label names.
  • Proof curve — the curve primitive (defined above) applied to a specific reckoner. How much edge? A continuous measure. 52.1% is barely there. 70% is screaming. The treasury funds proportionally. The entity earns a DEGREE of trust, not a binary gate. More edge, more capital. “Proof curve” and “curve” are the same thing — one is the primitive, the other is its name when applied.

  • Broker — binds a set of observers as a team. Any number — two today (market + exit), three tomorrow (market + exit + risk). The accountability primitive. It measures how successful the team is — Grace or Violence. It owns paper trades. When papers or real trades resolve, it routes outcomes to every observer in the set.

  • Residue — permanent gain. When a trade settles with Grace, the principal returns to available capital and the profit stays — that profit is the residue. Residue is never withdrawn by the enterprise. It compounds. The accumulation model: deploy, recover principal, keep the residue. The residue IS the growth.

  • Trade phases — a trade has a phase, not just a status. The phase transitions are a state machine:

    • Active — capital is reserved. Trailing stop, safety stop, and take-profit are live. The trade is running.
    • Active + stop-hit → Settled(Violence) — loss bounded by reservation. Principal minus loss returns to available.
    • Active + take-profit-hit → PrincipalRecovered — principal returns to available. The residue continues as a Runner.
    • Runner — residue rides with a wider trailing stop. Zero cost basis. House money. The runner stop distance is a fourth learnable scalar on the exit observer — k_trail_runner — wider than k_trail because the cost of being stopped out of a runner is zero.
    • Runner + runner-trail-hit → RunnerSettled(Grace) — residue is permanent gain. Returns to available. The phase is a value on the Trade struct. The treasury handles settlement differently depending on the phase. Two settlement events are possible per trade: principal recovery (partial) then runner exit (final). Designers: “the mechanism is designable now. The parameters will be learned. That is the whole point of having reckoners.”
  • Message protocol — every learned message carries three semantic values: (thought: Vector, prediction: Prediction, edge: f64). Thought = what you know. Prediction = what you think will happen. Edge = how accurate you are when you predict this strongly. Functions may return additional transport values (e.g. cache misses) alongside the protocol triple — those are plumbing, not content. edge ∈ [0.0, 1.0]. Raw accuracy from the curve at this conviction. 0.50 = noise. Above = correlated. Below = anti-correlated (the flip). The consumer encodes the edge as a fact and is free to gate, weight, sort, or ignore. Every producer that has learned from experience attaches a measure of that experience to its output. Opinions carry credibility. Data (candles, raw facts) does not.

  • Propagation — routing resolved outcomes through the broker to the observers that need to learn. Grace/Violence to the broker’s own record. The actual direction (Up/Down) to the market observer. Optimal distance to the exit observer.

  • Post — a trading post. The NYSE had specialist posts — each one handled one security, had its own specialists, its own order book. The (USDC, WBTC) post. The (SPY, SILVER) post. The (SOL, GOLD) post. Any asset pair. The post doesn’t care what the pair IS — it watches a stream of candles, acquires capital from the treasury, and the treasury holds it accountable. Grace or Violence. Each post has its own observers, its own brokers, its own indicator bank. No cross-talk between posts. The enterprise is the floor. Each post watches one market.

  • Denomination — what “value” means. The treasury counts in a denomination. USD today. Could be EUR, could be SOL.

  • TradeId — a newtype over usize. Not a raw integer — a distinct type that the compiler enforces. The treasury’s key for active trades. Assigned at funding time. Maps back to (post-idx, slot-idx) via trade-origins.

  • slot-idx — the flat index into the broker registry. Today each broker binds exactly one market observer + one exit observer. slot-idx = market-idx × M + exit-idx — one broker per (market, exit) pair, N×M total. When the broker generalizes to more than two observer kinds, the indexing scheme changes. The slot-idx remains — a usize into a flat vec. The formula adapts.

  • Noise subspace — the background model. An OnlineSubspace that learns what ALL thoughts look like — the average texture of thought-space. Subtract it from a thought and what remains is what’s UNUSUAL. The reckoner learns from the unusual part, not the boring part.

  • Experience — how much a reckoner has learned. 0.0 = empty. Grows with each observation. The reckoner’s self-knowledge of its own depth.

  • Ignorance — the starting state. Every reckoner begins with zero experience. No edge. The reckoner does not participate when it knows it doesn’t know. No special bootstrap logic. The architecture IS the bootstrap — papers fill the reckoner, experience grows, the treasury starts listening. Start ignorant. Learn. Graduate.

  • Weight — an f64 that scales how much an observation contributes to learning. 1.0 = normal contribution. Larger = stronger signal. Used in reckoner.observe, broker.propagate, and scalar accumulator.observe. Typically derived from the magnitude of the outcome — a large Grace teaches harder than a marginal one.

  • Recalibration — the reckoner periodically recomputes its discriminant from accumulated observations. The interval (recalib-interval) is how often this happens — every N observations.

  • Engram gating — after a recalibration with good accuracy, snapshot the discriminant as a “good state.” An OnlineSubspace learns what good discriminants look like. Future recalibrations are checked against this memory — does the new discriminant match a known good state? Used by any entity that has a reckoner — market observers gate their direction predictions, brokers gate their Grace/Violence predictions. Same mechanism, same four fields, different reckoner, different purpose.

  • ctx — the immutable world. Lowercase intentionally — ctx is a parameter that flows through function calls, not a type you instantiate like Post or Treasury. Born at startup. Contains the ThoughtEncoder (which contains the VectorManager), dims, recalib-interval. ctx flows in as a parameter — the enterprise receives it, posts receive it, observers receive it. Nobody owns it. Everybody borrows it. Immutable config is separate from mutable state. That’s not duplication — that’s honesty. The one seam: the ThoughtEncoder’s composition cache is mutable. During encoding (parallel), the cache is read-only — misses are returned as values. Between candles (sequential), the enterprise inserts collected misses into the cache. ctx is immutable DURING a candle. The cache updates BETWEEN candles. The seam is bounded by the fold boundary.

  • encode-count — the candle counter. How many candles the post has processed. The window sampler uses it to determine window size each candle.


The construction order. Each line can only reference what’s above it — those are the things that exist when this thing is constructed. The constructor calls ARE the dependency graph.

The market produces price data at regular intervals. For one time period (5 minutes for BTC), five measurements:

  • Open — price at the start of the period
  • High — highest price during the period
  • Low — lowest price during the period
  • Close — price at the end of the period
  • Volume — how much was traded during the period

This is a RawCandle. Tagged with its asset pair — which market produced it. The enterprise consumes a stream of these. One per period.

The IndicatorBank consumes raw candles and computes technical indicators — moving averages, oscillators, volatility measures, momentum, structure. The output is an enriched Candle — the raw data plus 100+ derived measurements. This is what the observers think about.

This section shows the dependency graph as constructor calls — a sketch. The “Structs and interfaces” section below is the authority — full field definitions, full interface signatures. This section shows what depends on what. That section shows what each thing IS.

;; ── Primitives — depend on nothing ──────────────────────────────────
;; Asset: a named token
(struct asset
[name : String])
(let ((source (make-asset "USDC"))
(target (make-asset "WBTC"))
(ts "2025-01-01T00:00:00")
(open 96000.0)
(high 96500.0)
(low 95800.0)
(close 96200.0)
(volume 1500.0))
(make-raw-candle source target ts
open high low close volume)) → RawCandle
(make-indicator-bank) → IndicatorBank
(let ((seed 7919)
(min-window 12)
(max-window 2016))
(make-window-sampler seed min-window max-window)) → WindowSampler
(let ((name "trail-distance")
(encoding :log))
(make-scalar-accumulator name encoding)) → ScalarAccumulator
;; ── Candle — produced by indicator bank from raw candle ─────────────
(tick indicator-bank raw-candle) → Candle
;; ── Vocabulary — pure functions, context in, ASTs out ───────────────
;; Three domains: shared (time), market (direction), exit (conditions)
;; The vocabulary speaks a DSL of ThoughtASTs — data, not execution
(oscillator-facts candle) → Vec<ThoughtAST>
;; ThoughtAST: data describing a composition — not vectors, not execution
;; ── ThoughtEncoder — evaluates the vocabulary's ASTs ────────────────
(let ((vector-manager (make-vector-manager dims)))
(make-thought-encoder vector-manager)) → ThoughtEncoder
(encode thought-encoder ast) → (Vector, Vec<(ThoughtAST, Vector)>)
;; ── Label enums ─────────────────────────────────────────────────────
;; Side is action (what the trader does). Direction is observation (what
;; the price did). They are related (Up → Buy, Down → Sell) but distinct
;; types — one is a decision, the other is a measurement.
(enum Side :buy :sell) ; trading action — on Proposal and Trade
(enum Direction :up :down) ; price movement — used in propagation
(enum Outcome :grace :violence) ; accountability — used everywhere
;; ── Newtypes ────────────────────────────────────────────────────────
(newtype TradeId usize) ; treasury's key for active trades
;; ── Lenses — which vocabulary subset an observer thinks through ─────
;; A lens selects which vocab modules fire. The observer's identity.
;; Each variant selects a subset of the vocabulary. See vocab/ for the modules.
;; :generalist selects ALL modules in the domain.
(enum MarketLens :momentum :structure :volume :narrative :regime :generalist)
(enum ExitLens :volatility :structure :timing :generalist)
;; See Vocabulary section below for lens → module mappings.
;; ── Reckoner — the learning primitive ────────────────────────────────
;; One constructor. Config is data.
(enum reckoner-config
(Discrete
dims ; usize — vector dimensionality
recalib-interval ; usize — observations between recalibrations
labels) ; Vec<String> — ("Up" "Down")
(Continuous
dims ; usize
recalib-interval ; usize
default-value)) ; f64 — the crutch, returned when ignorant
;; This enum is authoritative — no further expansion in Structs and interfaces.
(let ((dims 10000)
(recalib-interval 500)
(labels '("Up" "Down")))
(make-reckoner (Discrete dims recalib-interval labels)))
→ Reckoner
(let ((dims 10000)
(recalib-interval 500)
(default-value 0.015)) ; 0.015 = 1.5% of price — the crutch distance
(make-reckoner (Continuous dims recalib-interval default-value)))
→ Reckoner
;; ── Prediction — what a reckoner returns. Data. ─────────────────────
;; The consumer decides what "best" means.
(enum prediction
(Discrete
scores ; Vec<(String, f64)> — (label name, cosine) for each label
conviction) ; f64 — how strongly the reckoner leans
(Continuous
value ; f64 — the reckoned scalar
experience)) ; f64 — how much the reckoner knows (0.0 = ignorant)
;; ── MarketObserver — depends on: Reckoner :discrete, WindowSampler ──
(let ((lens :momentum)
(dims 10000)
(recalib-interval 500)
(seed 7919)
(min-window 12)
(max-window 2016)
(sampler (make-window-sampler seed min-window max-window)))
(make-market-observer lens
(Discrete dims recalib-interval '("Up" "Down"))
sampler)) → MarketObserver
;; ── Distances and Levels — two representations of exit thresholds ────
;; Distances are percentages (from the exit observer — scale-free).
;; Levels are absolute prices (from the post — computed from distance × price).
;; Observers think in Distances. Trades execute at Levels. Different types
;; because they are different concepts with the same four fields.
(struct distances
[trail : f64] ; trailing stop distance (percentage of price)
[stop : f64] ; safety stop distance
[tp : f64] ; take-profit distance
[runner-trail : f64]) ; runner trailing stop distance (wider than trail,
; because the cost of stopping out a runner is zero)
(struct levels
[trail-stop : f64] ; absolute price level for trailing stop
[safety-stop : f64] ; absolute price level for safety stop
[take-profit : f64] ; absolute price level for take-profit
[runner-trail-stop : f64]) ; absolute price level for runner trailing stop
;; Distances are percentages (from exit observer). Levels are prices
;; (computed by the post: distance × current price → level). Trade
;; stores Levels. Proposal carries Distances. Different concepts.
;; ── ExitObserver — depends on: Reckoner :continuous (×4), Distances ──
(let ((lens :volatility)
(dims 10000)
(recalib-interval 500)
(default-trail 0.015)
(default-stop 0.030)
(default-tp 0.045)
(default-runner-trail 0.030)) ; wider than trail — zero cost basis
(make-exit-observer lens dims recalib-interval
default-trail default-stop default-tp default-runner-trail))
→ ExitObserver
;; ── PaperEntry — hypothetical trade inside a broker ──────────
;; A paper trade is a "what if." Every candle, every pair gets one.
;; It tracks what WOULD have happened if a trade was opened here.
;; Both sides (buy and sell) are tracked simultaneously.
;; When both sides resolve (their trailing stops fire), the paper
;; teaches the system: what distance would have been optimal?
;;
;; distances.trail drives the paper's trailing stops (buy-trail-stop,
;; sell-trail-stop). The other three (stop, tp, runner-trail) are stored
;; for the learning signal — when the paper resolves, the Resolution
;; carries optimal-distances (what hindsight says was best). The
;; predicted distances at entry vs the optimal distances at resolution
;; IS the teaching: "you predicted trail=0.015 but optimal was 0.022."
(struct paper-entry
[composed-thought : Vector] ; the thought at entry
[entry-price : f64] ; price when the paper was created
[entry-atr : f64] ; volatility at entry
[distances : Distances] ; from the exit observer at entry
[buy-extreme : f64] ; best price in buy direction so far
[buy-trail-stop : f64] ; trailing stop level (from distances.trail)
[sell-extreme : f64] ; best price in sell direction so far
[sell-trail-stop : f64] ; trailing stop level (from distances.trail)
[buy-resolved : bool] ; buy side's stop fired
[sell-resolved : bool]) ; sell side's stop fired
;; ── Broker — depends on: Reckoner :discrete, ScalarAccumulator ──────
;; :log below is a ScalarEncoding variant (defined in ScalarAccumulator section).
;; It means: encode values with encode-log (ratios compress naturally).
(let ((observers '("momentum" "volatility"))
(slot-idx 0) ; position in the N×M grid, assigned by the post
(exit-count 4) ; M — number of exit observers
(dims 10000)
(recalib-interval 500))
(make-broker observers slot-idx exit-count dims recalib-interval
(list (make-scalar-accumulator "trail-distance" :log)
(make-scalar-accumulator "stop-distance" :log)
(make-scalar-accumulator "tp-distance" :log)
(make-scalar-accumulator "runner-trail-distance" :log))))
→ Broker
;; ── Proposal — what a post produces, what the treasury evaluates ────
;; Assembled by the post during step-compute-dispatch. The post calls:
;; market observer → thought vector
;; exit observer → evaluate-and-compose(thought, fact-asts, ctx) → composed + distances
;; broker → propose(composed) → prediction
;; post bundles these into a Proposal and submits to treasury.
(struct proposal
[composed-thought : Vector] ; market thought + exit facts
[prediction : Prediction] ; :discrete (Grace/Violence) — from the broker's
; reckoner, NOT the market observer's Up/Down prediction.
[distances : Distances] ; from the exit observer
[edge : f64] ; the broker's edge. [0.0, 1.0]. Raw accuracy
; from the broker's curve at its current conviction.
; This IS the edge from the message protocol.
; The treasury sorts proposals by this value and
; funds proportionally — more edge, more capital.
[side : Side] ; :buy or :sell — trading action, from the market observer's
; Up/Down prediction. Up → :buy, Down → :sell.
; Distinct from "direction" (:up/:down) which describes
; price movement used in propagation.
[post-idx : usize] ; which post this came from
[broker-slot-idx : usize]) ; which broker proposed this
;; ── TradePhase — the state machine of a position's lifecycle ─────────
(enum trade-phase
:active ; capital reserved, all stops live
:principal-recovered ; principal returned to available, residue continues
:runner ; residue riding with wider trailing stop, zero cost basis
:settled-violence ; stop-loss fired — bounded loss
:settled-grace) ; runner trail fired or take-profit — residue is permanent gain
;; ── Trade — an active position the treasury holds ───────────────────
(struct trade
[id : TradeId] ; assigned by treasury at funding time
[post-idx : usize] ; which post
[broker-slot-idx : usize] ; which broker (for trigger routing)
[phase : TradePhase] ; :active → :principal-recovered → :runner → :settled-*
[source-asset : Asset] ; what was deployed
[target-asset : Asset] ; what was acquired
[side : Side] ; copied from the funding Proposal at treasury funding time
[entry-rate : f64]
[entry-atr : f64] ; from candle.atr at funding time
[source-amount : f64] ; how much was deployed
[stop-levels : Levels] ; current trailing stop, safety stop, take-profit
; absolute price levels, updated by step 3c
[candles-held : usize] ; how long open
[price-history : Vec<f64>]) ; close prices from entry to now. Appended each
; candle. The trade closes over its own history. Pure.
;; ── TreasurySettlement — what the treasury produces when a trade closes ──
(struct treasury-settlement
[trade : Trade] ; which trade closed (carries post-idx, broker-slot-idx, side)
[exit-price : f64] ; price at settlement
[outcome : Outcome] ; :grace or :violence
[amount : f64] ; how much value gained or lost
[composed-thought : Vector]) ; from trade-origins, stashed at funding time
;; The treasury produces this. It does NOT have optimal-distances.
;; ── Settlement — the complete record, after enterprise enrichment ─────
(struct settlement
[treasury-settlement : TreasurySettlement] ; the treasury's accounting
[direction : Direction] ; :up or :down, derived from exit-price vs entry-rate
[optimal-distances : Distances]) ; replay trade's price-history, maximize residue
;; The enterprise builds this by enriching a TreasurySettlement.
;; The trade's price-history (on the Trade) provides the replay data.
;; ── Resolution — what a broker produces when a paper resolves ────────
;; Facts, not mutations. Collected from parallel tick, applied sequentially.
;; A paper has two sides (buy and sell). Each side resolves independently.
;; Each resolved side produces one Resolution with its own direction.
(struct resolution
[broker-slot-idx : usize] ; which broker produced this
[composed-thought : Vector] ; the thought that was tested
[direction : Direction] ; :up or :down. Each paper side resolves
; independently: buy-side stop fires → :up (price rose
; then retraced). sell-side stop fires → :down.
; The direction matches the side that was TESTED, not
; the outcome — a buy-side paper only triggers because
; price moved up, so direction is :up regardless of
; whether outcome was Grace or Violence.
[outcome : Outcome] ; :grace or :violence
[amount : f64] ; how much value
[optimal-distances : Distances]) ; hindsight optimal
;; ── LogEntry — the glass box. What happened. ────────────────────────
;; Generic. Each function returns its log entries as values.
(enum log-entry
(ProposalSubmitted
broker-slot-idx ; usize
composed-thought ; Vector
distances) ; Distances
(ProposalFunded
trade-id ; TradeId
broker-slot-idx ; usize
amount-reserved) ; f64
(ProposalRejected
broker-slot-idx ; usize
reason) ; String
(TradeSettled
trade-id ; TradeId
outcome ; :grace or :violence
amount ; f64
duration) ; usize — candles held
(PaperResolved
broker-slot-idx ; usize
outcome ; :grace or :violence
optimal-distances) ; Distances
(Propagated
broker-slot-idx ; usize
observers-updated)); usize — how many observers received the outcome
;; ── TradeOrigin — where a trade came from, for propagation routing ───
(struct trade-origin
[post-idx : usize] ; which post
[broker-slot-idx : usize] ; which broker
[composed-thought : Vector]) ; the thought at entry
;; ── Post — depends on: IndicatorBank, MarketObserver, ExitObserver, Broker ──
(let ((source (make-asset "USDC"))
(target (make-asset "WBTC"))
(dims 10000)
(recalib-interval 500)
(max-window-size 2016))
(make-post post-idx source target dims recalib-interval max-window-size
(make-indicator-bank)
market-observers exit-observers registry)) → Post
;; ── Treasury — pure accounting ──────────────────────────────────────
(let ((denomination (make-asset "USD"))
(initial-balances (map-of (make-asset "USDC") 10000.0)))
(make-treasury denomination initial-balances)) → Treasury
;; ── Ctx — the immutable world. Born at startup. ────────────────────
;; Immutable DURING each candle. The ThoughtEncoder's composition cache
;; is the one seam — updated BETWEEN candles from collected misses.
(struct ctx ; this is the complete set — three fields, nothing else
[thought-encoder : ThoughtEncoder] ; contains VectorManager + composition cache (the seam)
[dims : usize] ; vector dimensionality
[recalib-interval : usize]) ; observations between recalibrations
;; ── Enterprise — the coordination plane ─────────────────────────────
(let ((posts (list btc-post sol-post))
(treasury (make-treasury denomination balances)))
(make-enterprise posts treasury)) → Enterprise
;; ctx is separate — created by the binary, passed to on-candle

RawCandle (the input — depends on: nothing)

Section titled “RawCandle (the input — depends on: nothing)”

The enterprise consumes a stream of raw candles. This is the only input. Everything else is derived. Each raw candle identifies its asset pair — the pair IS the routing key. Only the post for that pair receives it.

(struct raw-candle
[source-asset : Asset] ; e.g. USDC
[target-asset : Asset] ; e.g. WBTC
[ts : String]
[open : f64]
[high : f64]
[low : f64]
[close : f64]
[volume : f64])

Eight fields. From the parquet. From the websocket. The enterprise doesn’t care which. The asset pair IS the identity of the stream.


The enriched candle. Raw OHLCV in, 100+ computed indicators out. Produced by IndicatorBank.tick(raw-candle). The post’s first act every candle.

(struct candle
;; Raw
[ts : String] [open : f64] [high : f64] [low : f64] [close : f64] [volume : f64]
;; Moving averages
[sma20 : f64] [sma50 : f64] [sma200 : f64]
;; Bollinger
[bb-upper : f64] [bb-lower : f64] [bb-width : f64] [bb-pos : f64]
;; RSI, MACD, DMI, ATR
[rsi : f64] [macd : f64] [macd-signal : f64] [macd-hist : f64]
[plus-di : f64] [minus-di : f64] [adx : f64] [atr : f64] [atr-r : f64]
;; Stochastic, CCI, MFI, OBV, Williams %R
[stoch-k : f64] [stoch-d : f64] [williams-r : f64] [cci : f64] [mfi : f64]
[obv-slope-12 : f64] ; 12-period linear regression slope of OBV
[vol-accel : f64] ; volume / volume_sma20 — volume acceleration
;; Keltner (computed from ema20 + atr on the bank), squeeze
[kelt-upper : f64] [kelt-lower : f64] [kelt-pos : f64]
[squeeze : bool] ; Bollinger inside Keltner
;; Rate of Change
[roc-1 : f64] [roc-3 : f64] [roc-6 : f64] [roc-12 : f64]
;; ATR rate of change
[atr-roc-6 : f64] [atr-roc-12 : f64] ; how is volatility changing?
;; Trend consistency
[trend-consistency-6 : f64] [trend-consistency-12 : f64] [trend-consistency-24 : f64]
;; Range position
[range-pos-12 : f64] [range-pos-24 : f64] [range-pos-48 : f64]
;; Multi-timeframe
[tf-1h-close : f64] [tf-1h-high : f64] [tf-1h-low : f64] [tf-1h-ret : f64] [tf-1h-body : f64]
[tf-4h-close : f64] [tf-4h-high : f64] [tf-4h-low : f64] [tf-4h-ret : f64] [tf-4h-body : f64]
;; Ichimoku
[tenkan-sen : f64] [kijun-sen : f64] [senkou-span-a : f64] [senkou-span-b : f64] [cloud-top : f64] [cloud-bottom : f64]
;; Persistence (pre-computed by IndicatorBank from ring buffers)
[hurst : f64] ; Hurst exponent — trending vs mean-reverting
[autocorrelation : f64] ; lag-1 autocorrelation — signed
[vwap-distance : f64] ; (close - VWAP) / close — signed distance
;; Regime (pre-computed by IndicatorBank — regime.wat needs these)
[kama-er : f64] ; Kaufman Adaptive Moving Average Efficiency Ratio [0, 1]
[choppiness : f64] ; Choppiness Index [0, 100] — high = choppy, low = trending
[dfa-alpha : f64] ; Detrended Fluctuation Analysis exponent
[variance-ratio : f64] ; variance at scale N / (N × variance at scale 1)
[entropy-rate : f64] ; conditional entropy of discretized returns
[aroon-up : f64] ; Aroon up [0, 100] — how recent was the highest high?
[aroon-down : f64] ; Aroon down [0, 100] — how recent was the lowest low?
[fractal-dim : f64] ; fractal dimension — 1.0 trending, 2.0 noisy
;; Divergence (pre-computed by IndicatorBank from PELT peaks — divergence.wat)
[rsi-divergence-bull : f64] ; bullish divergence magnitude (price lower, RSI higher)
[rsi-divergence-bear : f64] ; bearish divergence magnitude (price higher, RSI lower)
;; Ichimoku cross delta (ichimoku.wat)
[tk-cross-delta : f64] ; (tenkan - kijun) change from prev candle — signed
;; Stochastic cross delta (stochastic.wat)
[stoch-cross-delta : f64] ; (%K - %D) change from prev candle — signed
;; Price action (pre-computed by IndicatorBank — price-action.wat)
[range-ratio : f64] ; current range / prev range. < 1 = compression, > 1 = expansion
[gap : f64] ; signed — (open - prev close) / prev close
[consecutive-up : f64] ; run count of consecutive bullish closes
[consecutive-down : f64] ; run count of consecutive bearish closes
;; Timeframe agreement (timeframe.wat)
[tf-agreement : f64] ; inter-timeframe agreement score — 5m/1h/4h direction alignment
;; Time — circular scalars (encode-circular)
[minute : f64] ; mod 60
[hour : f64] ; mod 24
[day-of-week : f64] ; mod 7
[day-of-month : f64] ; mod 31
[month-of-year : f64]) ; mod 12
;; ... additional fields computed by IndicatorBank as the vocabulary grows.
;; This struct lists the current set. "100+" in the definitions is the
;; target — the actual count grows with the vocabulary.

Streaming state machine. Advances all indicators by one raw candle. Stateful — ring buffers, EMA accumulators, Wilder smoothers. One per post (one per asset pair).

The streaming primitives — the building blocks of indicator state:

;; Leaves — depend on nothing
(struct ring-buffer
[data : Vec<f64>]
[capacity : usize]
[head : usize]
[len : usize])
(struct ema-state
[value : f64]
[smoothing : f64]
[period : usize]
[count : usize]
[accum : f64])
(struct wilder-state
[value : f64]
[period : usize]
[count : usize]
[accum : f64])
(struct rsi-state
[gain-smoother : WilderState]
[loss-smoother : WilderState]
[prev-close : f64]
[started : bool])
(struct atr-state
[wilder : WilderState]
[prev-close : f64]
[started : bool])
(struct obv-state
[obv : f64]
[prev-close : f64]
[history : RingBuffer]
[started : bool]) ; for computing obv-slope-12 via linear regression
;; Depend on RingBuffer
(struct sma-state
[buffer : RingBuffer]
[sum : f64]
[period : usize])
(struct rolling-stddev
[buffer : RingBuffer]
[sum : f64]
[sum-sq : f64]
[period : usize])
(struct stoch-state
[high-buf : RingBuffer]
[low-buf : RingBuffer]
[k-buf : RingBuffer]) ; %K history for computing %D (3-period SMA of %K)
(struct cci-state
[tp-buf : RingBuffer]
[tp-sma : SmaState])
(struct mfi-state
[pos-flow-buf : RingBuffer]
[neg-flow-buf : RingBuffer]
[prev-tp : f64]
[started : bool])
(struct ichimoku-state
[high-9 : RingBuffer] [low-9 : RingBuffer]
[high-26 : RingBuffer] [low-26 : RingBuffer]
[high-52 : RingBuffer] [low-52 : RingBuffer])
;; Depend on EmaState
(struct macd-state
[fast-ema : EmaState]
[slow-ema : EmaState]
[signal-ema : EmaState])
(struct dmi-state
[plus-smoother : WilderState]
[minus-smoother : WilderState]
[tr-smoother : WilderState]
[adx-smoother : WilderState]
[prev-high : f64]
[prev-low : f64]
[prev-close : f64]
[started : bool]
[count : usize]
[period : usize])

The indicator bank — composed from the streaming primitives:

(struct indicator-bank
;; Moving averages
[sma20 : SmaState]
[sma50 : SmaState]
[sma200 : SmaState]
[ema20 : EmaState] ; internal — for Keltner channel computation
;; Bollinger
[bb-stddev : RollingStddev]
;; Oscillators
[rsi : RsiState]
[macd : MacdState]
[dmi : DmiState]
[atr : AtrState]
[stoch : StochState]
[cci : CciState]
[mfi : MfiState]
[obv : ObvState]
[volume-sma20 : SmaState] ; internal — for volume ratio computation in flow vocab
;; ROC
[roc-buf : RingBuffer] ; 12-period close buffer — ROC 1/3/6/12 index into this
;; Range position
[range-high-12 : RingBuffer] [range-low-12 : RingBuffer]
[range-high-24 : RingBuffer] [range-low-24 : RingBuffer]
[range-high-48 : RingBuffer] [range-low-48 : RingBuffer]
;; Trend consistency
[trend-buf-24 : RingBuffer]
;; ATR history
[atr-history : RingBuffer] ; for computing atr-r (ATR ratio) on Candle
;; Multi-timeframe
[tf-1h-buf : RingBuffer] [tf-1h-high : RingBuffer] [tf-1h-low : RingBuffer]
[tf-4h-buf : RingBuffer] [tf-4h-high : RingBuffer] [tf-4h-low : RingBuffer]
;; Ichimoku
[ichimoku : IchimokuState]
;; Persistence — pre-computed from ring buffers
[close-buf-48 : RingBuffer] ; 48 closes for Hurst + autocorrelation
;; VWAP — running accumulation
[vwap-cum-vol : f64] ; cumulative volume
[vwap-cum-pv : f64] ; cumulative price × volume
;; Regime — state for regime.wat fields
[kama-er-buf : RingBuffer] ; 10-period close buffer for KAMA efficiency ratio
[chop-atr-sum : f64] ; running sum of ATR over choppiness period
[chop-buf : RingBuffer] ; 14-period ATR buffer for Choppiness Index
[dfa-buf : RingBuffer] ; close buffer for Detrended Fluctuation Analysis
[var-ratio-buf : RingBuffer] ; close buffer for variance ratio (two scales)
[entropy-buf : RingBuffer] ; discretized return buffer for conditional entropy
[aroon-high-buf : RingBuffer] ; 25-period high buffer for Aroon up
[aroon-low-buf : RingBuffer] ; 25-period low buffer for Aroon down
[fractal-buf : RingBuffer] ; close buffer for fractal dimension (Higuchi or box-counting)
;; Divergence — state for divergence.wat fields
[rsi-peak-buf : RingBuffer] ; recent RSI values for PELT peak detection
[price-peak-buf : RingBuffer] ; recent close values aligned with RSI for divergence
;; Ichimoku cross delta — prev TK spread
[prev-tk-spread : f64] ; (tenkan - kijun) from previous candle
;; Stochastic cross delta — prev K-D spread
[prev-stoch-kd : f64] ; (stoch-k - stoch-d) from previous candle
;; Price action — state for price-action.wat fields
[prev-range : f64] ; previous candle range (high - low) for range-ratio
[consecutive-up-count : usize] ; running count of consecutive bullish closes
[consecutive-down-count : usize] ; running count of consecutive bearish closes
;; Timeframe agreement — prev returns for direction comparison
[prev-tf-1h-ret : f64] ; previous 1h return for direction tracking
[prev-tf-4h-ret : f64] ; previous 4h return for direction tracking
;; Previous values
[prev-close : f64]
;; Counter
[count : usize])

Interface:

  • (make-indicator-bank) → IndicatorBank
  • (tick indicator-bank raw-candle) → Candle

Deterministic log-uniform window selection. Each market observer has its own — its own seed, its own time scale. The observer uses it every candle to decide how much history to look at.

Owned by the market observer. Not by the enterprise. Not shared. The enterprise doesn’t sample windows — the observers do.

When window sampling becomes learned, the feedback routes through the same resolution mechanism that teaches everything else. “This window size produced Grace.” The system knows. It routes back to the market observer. The market observer adjusts its sampler.

(struct window-sampler
[seed : usize]
[min-window : usize]
[max-window : usize])

Interface:

  • (make-window-sampler seed min max) → WindowSampler
  • (sample window-sampler encode-count) → usize

Note: min-window and max-window are crutches. The observer needs them to bootstrap — it cannot learn its own time scale from nothing. But the optimal window is learnable. The market tells us which windows produce Grace. This is a coordinate for future work, not a problem to solve now.


Vocabulary (depends on: what it thinks about)

Section titled “Vocabulary (depends on: what it thinks about)”

Pure functions. Something in, facts out. No state. Each domain thinks about different things. Market vocab thinks about candles. Exit vocab thinks about candles and conditions. Risk vocab (future) thinks about portfolio state. The input is whatever the domain needs to form its judgment.

Three domains. Each domain has scoped subfiles.

Domains:

  • shared/ — universal context. Any observer can use these.

    • time.wat — minute (mod 60), hour (mod 24), day-of-week (mod 7), day-of-month (mod 31). Circular scalars.
  • market/ — what the market IS DOING. Direction signal. Market observers use these. MarketLens → modules:

    • :momentum → oscillators, momentum, stochastic
    • :structure → keltner, fibonacci, ichimoku, price-action
    • :volume → flow
    • :narrative → timeframe, divergence
    • :regime → regime, persistence
    • :generalist → all of the above Files:
    • oscillators.wat — Williams %R, StochRSI, UltOsc, multi-ROC
    • flow.wat — OBV, VWAP, MFI, buying/selling pressure
    • persistence.wat — Hurst, autocorrelation, ADX zones
    • regime.wat — KAMA-ER, choppiness, DFA, variance ratio, entropy, Aroon, fractal dim
    • divergence.wat — RSI divergence via PELT structural peaks
    • ichimoku.wat — cloud zone, TK cross
    • stochastic.wat — %K/%D zones and crosses
    • fibonacci.wat — retracement level detection
    • keltner.wat — channel position, BB position, squeeze
    • momentum.wat — CCI zones
    • price-action.wat — range-ratio, gaps, consecutive runs
    • timeframe.wat — 1h/4h structure + narrative + inter-timeframe agreement
  • exit/ — whether CONDITIONS favor trading. Distance signal. Exit observers use these. ExitLens → modules:

    • :volatility → volatility.wat — ATR regime, ATR ratio, squeeze state
    • :structure → structure.wat — trend consistency, ADX strength
    • :timing → timing.wat — momentum state, reversal signals
    • :generalist → all three (volatility + structure + timing)

Interface (per module):

  • (encode-<domain>-facts candle) → Vec<ThoughtAST> e.g. (encode-oscillator-facts candle), (encode-flow-facts candle). Each module is a pure function: candle in, ASTs out. The observer calls the modules matching its lens, collects the ASTs, and passes them to evaluate-and-compose.

A fact is a composition of atoms. The composition IS a vector. The vector IS the fact. It doesn’t need a separate name. It simply is.

"RSI is at 0.73" → (bind (atom "rsi") (encode-linear 0.73 1.0)) → Vector
"close is 2.3% above SMA20" → (bind (atom "close-sma20") (encode-linear 0.023 0.1)) → Vector
"ATR is 1.8x its average" → (bind (atom "atr-ratio") (encode-log 1.8)) → Vector
"hour is 14:00" → (bind (atom "hour") (encode-circular 14.0 24.0)) → Vector

Every relationship is a signed scalar. Not “close is above SMA20” — the relative distance, with sign.

(bind (atom "close-sma20") (encode-linear 0.023 0.1)) — 2.3% above. (bind (atom "close-sma20") (encode-linear -0.041 0.1)) — 4.1% below.

Same atom. Same encoding. The sign IS the direction. The magnitude IS the distance. No “above” atom. No “below” atom. No boolean. Just the number. The discriminant learns what positive means and what negative means. The word “above” doesn’t exist in the vector space. The number 0.023 does. The number -0.041 does.

The boolean threw away the signal. The scalar preserves it. The discriminant learns that 0.1% above is noise and 5% above is signal. The sign carries direction. The magnitude carries conviction.

The vocabulary observes. It composes atoms. The result is a vector. Many fact-vectors get bundled into one thought-vector. That’s the superposition. The thought is the bundle of facts.

vocabulary observes → composes atoms → fact (a vector)
many facts → bundle → thought (a vector)
thought → cosine against discriminant → prediction

The vocabulary is conditional. It emits what IS true. Close is within the bands or beyond them — not both. Each truth has a scalar property. The vocabulary observes reality and speaks only truth.

The encoding scheme IS the bounding strategy. The vocabulary chooses the right scheme for each fact — not magic, logic:

  • encode-linear — naturally bounded scalars. The bounds are in the math.

    • Bollinger position: [-1, 1] — where on the band
    • RSI: [0, 1] — Wilder’s formula defines the range
    • Stochastic %K: [0, 1] — where in the recent range
  • encode-log — unbounded positive scalars. Log compresses naturally. The difference between 1x and 2x matters more than 4x and 5x. No cap needed.

    • Band-widths beyond Bollinger: how far past the boundary
    • ATR ratio: volatility relative to price
    • Volume ratio: volume relative to its moving average
  • encode-circular — periodic scalars. The value wraps.

    • Minute: mod 60. Hour: mod 24. Day-of-week: mod 7. Day-of-month: mod 31.

Some facts are bounded. Some aren’t. That’s honest. The log doesn’t bound — it compresses. The circular doesn’t bound — it wraps. Only linear needs bounds, and linear’s bounds come from the math.

The vocabulary owns the encode AND the decode — it put the value on the scalar, it can take it back. That’s why scalar accumulators work.

No zones. No categories. Only scalars. “Overbought” is a human label on a continuous value — a magic number wearing a name. WHO decided 70 was the boundary? The vocabulary emits “RSI is at 0.73.” The discriminant learns where the boundaries are. Maybe 65 for BTC, maybe 80 for SPY. The data decides. Every zone is a premature measurement — the boolean lie one level up. Kill them all. Emit the scalar. Let the discriminant learn.

The encoding receives normalized values. The scale is uniform. The domain knowledge lives in the vocabulary, not in the encoder.

The ThoughtEncoder in the Rust is a cache and a renderer — an optimization that pre-computes common compositions. But the concept has no intermediate form. Atoms compose. Vectors result. Thoughts bundle.


ThoughtEncoder (depends on: VectorManager)

Section titled “ThoughtEncoder (depends on: VectorManager)”

The vocabulary produces ASTs — the specification of WHAT to think. The ThoughtEncoder evaluates them — HOW to think efficiently. It walks the AST bottom-up, checking its memory at every node. The minimum computation happens. Parts of the thought are already ready for reuse.

Two kinds of memory:

Atoms: a dictionary. Finite. Known at startup. Pre-computed. Never evicted because never growing. The set is closed. Always there.

Compositions: a cache. Infinite. Optimistic. Use it if we have it. Compute if we don’t. Evict when memory says so. The set is open.

The ThoughtEncoder reclaims its name. It IS an encoder — it takes a thought AST and produces a vector, doing the minimum work.

Lives on ctx — the immutable world created at startup. Passed to posts via ctx on every on-candle call. The enterprise does not own it directly.

(struct thought-encoder
[atoms : Map<String, Vector>] ; finite, pre-computed, permanent
[compositions : LruCache<ThoughtAST, Vector>]) ; optimistic, self-evicting
;; The cache is eventually-consistent: encode returns misses as values
;; during parallel encoding, the enterprise collects all misses and
;; inserts them after all steps complete (miss on candle N, hit on N+1).
;; WHY: the cache mutates on miss, but ctx is immutable. This is the
;; one seam. The parallel phase returns misses as values. The sequential
;; phase inserts. No locks during encoding. No queues. Values up.

The AST — what the vocabulary speaks:

(enum thought-ast
(Atom name) ; dictionary lookup
(Linear name value scale) ; bind(atom, encode-linear)
(Log name value) ; bind(atom, encode-log)
(Circular name value period) ; bind(atom, encode-circular)
(Bind left right) ; composition of two sub-trees
(Bundle children)) ; superposition of sub-trees

The vocabulary produces trees of this. Cheap. No vectors. No 10,000-dim computation. Just “here is what I want to say.” The calls to bind and encode are deferred — the vocabulary knows what it wants, the encoder decides how to compute it efficiently.

Interface:

  • (encode thought-encoder ast) → (Vector, Vec<(ThoughtAST, Vector)>) On cache hit: return the vector and an empty misses list. On cache miss: compute the vector, return it AND the (ast, vector) pair in the misses list. The caller collects all misses. The enterprise collects them from each step’s return values and inserts into the cache after all steps complete. The encode function NEVER writes to the cache. Values up, not queues down.

One function. Recursive. Cache at every node. The cache key IS the AST node — its structure is its identity. Same structure, same vector.

(define (encode encoder ast)
(if (lookup (:cache encoder) ast) ;; cache hit → (vector, empty)
(values (lookup (:cache encoder) ast) '())
(let-values (((result misses)
(match ast
(Atom name)
(values (lookup-atom (:atoms encoder) name) '())
(Linear name value scale)
(let-values (((atom-vec atom-misses) (encode encoder (Atom name))))
(values (bind atom-vec (encode-linear value scale))
atom-misses))
(Log name value)
(let-values (((atom-vec atom-misses) (encode encoder (Atom name))))
(values (bind atom-vec (encode-log value))
atom-misses))
(Circular name value period)
(let-values (((atom-vec atom-misses) (encode encoder (Atom name))))
(values (bind atom-vec (encode-circular value period))
atom-misses))
(Bind left right)
(let-values (((l-vec l-misses) (encode encoder left))
((r-vec r-misses) (encode encoder right)))
(values (bind l-vec r-vec)
(append l-misses r-misses)))
(Bundle children)
(let ((pairs (map (lambda (c) (encode encoder c)) children)))
(values (apply bundle (map first pairs))
(apply append (map second pairs)))))))
(values result (cons (list ast result) misses)))))

The vocabulary produces QUOTED expressions — data, not execution. The encoder evaluates them. The vocabulary doesn’t know about caching. The encoder doesn’t know about RSI. The quoted list is the interface.

The observer composes the thought:

observer calls vocab(context) → Vec<ThoughtAST> ; AST nodes
observer wraps in (Bundle facts) → ThoughtAST ; still data
observer calls (encode encoder bundle-ast) → (Vector, misses) ; the thought + cache misses

The lens is not a parameter. The lens is on the observer. The observer knows which vocab modules are its domain.

Thought composition is AST evaluation with caching. The vocabulary produces the AST — the structure of the thought. The ThoughtEncoder walks it:

evaluate(node)
→ atom? → dictionary (always succeeds)
→ any other? → cache check → hit: reuse / miss: compute, store
→ bundle? → always fresh (per-observer, per-candle)

Scalars, binds, encodes — all go through the cache. Same structure, same vector. Scalars may evict quickly (values change each candle), but within a candle the same scalar is reused across observers.

The AST IS a function. bind(atom("rsi"), encode-linear(x, 1.0)) — the structure is fixed. Only x varies. The encoder recognizes the structure and reuses everything except the fresh scalar.

The AST can be as complex as the thought requires. These are data — quoted expressions the vocabulary returns. The ThoughtEncoder evaluates them.

;; A scalar fact — one atom, one signed value
(Linear "rsi" 0.73 1.0)
;; A signed relationship — 2.3% above. Negative would be below.
(Linear "close-sma20" 0.023 0.1)
;; A structural observation — RSI diverging from price, both magnitudes
(Bind (Atom "divergence")
(Bind (Linear "close-delta" 0.03 0.1)
(Linear "rsi-delta" -0.05 1.0)))
;; A moving average stack — the entire structure as signed distances
(Bundle
(Linear "close-sma20" 0.023 0.1)
(Linear "sma20-sma50" 0.011 0.1)
(Linear "sma50-sma200" -0.035 0.1))
;; A conditional fact — the vocabulary chose this path, not both
(Log "bb-breakout-lower" 1.3) ;; beyond: how far (log)
(Linear "bb-position" -0.7 1.0) ;; inside: where (linear)
;; A temporal change — MACD histogram 3 candles ago vs now
(Bind (Atom "macd-hist-change")
(Bind (Linear "now" -0.001 0.01)
(Linear "3-ago" 0.002 0.01)))
;; Time — circular scalars that wrap
(Circular "hour" 14.0 24.0)
(Circular "minute" 35.0 60.0)
(Circular "day-of-week" 3.0 7.0)
;; A deep confluence — multi-timeframe + oscillator + momentum
(Bundle
(Linear "tf-1h-trend" 0.7 1.0)
(Linear "tf-4h-structure" 0.6 1.0)
(Linear "rsi" 0.82 1.0)
(Linear "macd-hist" -0.0005 0.01)
(Log "macd-hist-from-peak" 0.167))

Simple thoughts are shallow trees. Complex thoughts are deep trees. The encoder walks them all the same way. The mechanism doesn’t change.


The four exit values. A named tuple. Percentage of price, not absolute levels. Appears on PaperEntry, Proposal, Resolution, Settlement. The post converts Distances to Levels (trail-stop, safety-stop, take-profit, runner-trail-stop on Trade) using the current price.

Defined in the forward declarations section (search for struct distances). No interface — Distances is pure data. Four f64 fields: trail, stop, tp, runner-trail.


ScalarAccumulator (depends on: Outcome enum)

Section titled “ScalarAccumulator (depends on: Outcome enum)”

Per-magic-number f64 learning. Lives on the broker. Global per-pair. Each distance (trail, stop, tp, runner-trail) gets its own.

Separates grace/violence observations into separate f64 prototypes. Grace outcomes accumulate one way. Violence outcomes accumulate the other. Extract recovers the value Grace prefers — sweep candidate values against the Grace accumulator, find the one with highest cosine. “What value does Grace prefer for this pair overall?” One answer regardless of thought.

Fed by resolution events: when a paper or trade resolves, the broker routes the optimal distance + Grace/Violence outcome to its scalar accumulators.

(enum scalar-encoding
:log ; no params — log compresses naturally
(Linear [scale : f64]) ; encode-linear scale
(Circular [period : f64])) ; encode-circular period
(struct scalar-accumulator
[name : String] ; which magic number ("trail-distance", etc.)
[encoding : ScalarEncoding] ; configured at construction — the data and
; its interpretation travel together
[grace-acc : Vector] ; accumulated encoded values from Grace outcomes
[violence-acc : Vector] ; accumulated encoded values from Violence outcomes
[count : usize]) ; number of observations. 0 = no data.

Interface:

  • (make-scalar-accumulator name encoding) → ScalarAccumulator encoding: ScalarEncoding — determines how values are encoded.
  • (observe-scalar acc value outcome weight) value: f64 — the scalar to accumulate (e.g. a distance). Encoded via the accumulator’s ScalarEncoding — pattern-match on the enum to dispatch. Distances use :log (ratios compress naturally). observe and extract use the SAME encoding — it’s on the struct. outcome: Outcome — :grace or :violence. Determines which accumulator receives the encoded value. weight: f64 — scales the contribution. Larger weight = stronger signal.
  • (extract-scalar acc steps range) → f64 steps: usize — how many candidates to try. range: (f64, f64) — (min, max) bounds to sweep across. Sweep steps candidate values across range, encode each, cosine against the Grace prototype. Return the candidate closest to Grace.

MarketObserver (depends on: Reckoner, OnlineSubspace, WindowSampler)

Section titled “MarketObserver (depends on: Reckoner, OnlineSubspace, WindowSampler)”

Predicts direction. Learned. Labels come from broker propagation — Predicts Up/Down. The broker routes the actual direction back from resolved paper and real trades. The market observer does NOT label itself. Reality labels it.

The generalist is just another lens. No special treatment.

(struct market-observer
[lens : MarketLens]
[reckoner : Reckoner] ; :discrete — Up/Down
[noise-subspace : OnlineSubspace] ; background model
[window-sampler : WindowSampler] ; own time scale
;; Proof tracking
[resolved : usize] ; how many predictions have been resolved
[curve : Curve] ; measures this observer's edge (conviction → accuracy)
[curve-valid : f64] ; cached edge from the curve. 0.0 = unproven.
; updated after each recalibration by querying the curve.
;; Engram gating
[good-state-subspace : OnlineSubspace] ; learns what good discriminants look like
[recalib-wins : usize] ; wins since last recalibration
[recalib-total : usize] ; total since last recalibration
[last-recalib-count : usize]) ; recalib-count at last engram check

Interface:

  • (make-market-observer lens reckoner-config window-sampler) → MarketObserver noise-subspace: (online-subspace dims 8) — 8 principal components for the background model. good-state-subspace: (online-subspace dims 4) — 4 components for engram gating (fewer — the good-state manifold is simpler). lens: MarketLens. config: Discrete with “Up”/“Down” labels. All proof-tracking and engram-gating fields initialize to zero/empty.
  • (observe-candle observer candle-window ctx) → (Vector, Prediction, f64, Vec<(ThoughtAST, Vector)>) returns: thought Vector, Prediction (Up/Down), curve-valid (f64 — the observer’s current edge, from its curve), and cache misses. Every learned output carries its track record. The consumer decides what to do with it. Cache misses are returned as values — the caller collects. candle-window: a slice of recent candles (NOT the full deque — the post calls (sample (:window-sampler observer) encode-count) to get the window size, slices, and passes the slice). The observer encodes → noise update → strip noise → predict. The Prediction does NOT appear on the Proposal. The broker produces its OWN prediction (Grace/Violence) from the composed thought.
  • (resolve observer thought direction weight) direction: Direction (:up or :down) — the actual price movement. weight: f64 — how much value was at stake. Called by broker propagation — reckoner learns from reality. Not “outcome” (which is Outcome :grace/:violence). Different type.
  • (strip-noise observer thought) → Vector
  • (experience observer) → f64 — how much has this observer learned?

ExitObserver (depends on: Reckoner :continuous)

Section titled “ExitObserver (depends on: Reckoner :continuous)”

Estimates exit distance. Learned. Each exit observer has FOUR continuous reckoners — one per distance (trail, stop, tp, runner-trail). No noise-subspace, no curve, no engram gating — intentionally simpler than MarketObserver. The exit observer’s quality is measured through the BROKER’s curve, not its own. The broker’s Grace/Violence ratio reflects the combined quality of its market + exit observers. The exit observer doesn’t need its own proof gate — it is proven through the team it belongs to.

Each reckoner accumulates (thought, distance, weight) observations and returns the cosine-weighted answer for a given thought.

Has a judgment vocabulary matching its ExitLens: :volatilityexit/volatility.wat, :structureexit/structure.wat, :timingexit/timing.wat, :generalist → all three. The generalist is just another lens. No special treatment. Composes market thoughts with its own judgment facts. One per exit lens — M instances, not N×M. The composed thought carries the market observer’s signal in superposition.

(struct exit-observer
[lens : ExitLens] ; which judgment vocabulary
[trail-reckoner : Reckoner] ; :continuous — trailing stop distance
[stop-reckoner : Reckoner] ; :continuous — safety stop distance
[tp-reckoner : Reckoner] ; :continuous — take-profit distance
[runner-reckoner : Reckoner] ; :continuous — runner trailing stop distance (wider)
[default-distances : Distances]) ; the crutches (all four), returned when empty

Each reckoner: (thought, distance, weight) observations. Query by cosine → distance for THIS thought. Contextual — different thoughts get different distances.

Interface:

  • (make-exit-observer lens dims recalib-interval default-trail default-stop default-tp default-runner-trail) → ExitObserver
  • (encode-exit-facts exit-obs candle) → Vec<ThoughtAST> pure: candle → judgment fact ASTs for this lens
  • (evaluate-and-compose exit-obs market-thought exit-fact-asts ctx) → (Vector, Vec<(ThoughtAST, Vector)>) two operations, honestly named:
    1. EVALUATE: encode exit-fact-asts into Vectors via ctx’s ThoughtEncoder
    2. COMPOSE: bundle the evaluated exit vectors with the market thought ASTs in, one composed Vector out. Returns the composed vector AND any cache misses from encoding. The name says what it does. The observer returns ASTs rather than vectors because it does not own the ThoughtEncoder — ctx does, so evaluation is deferred to the call site which has ctx in scope.
  • (recommended-distances exit-obs composed broker-accums) → (Distances, f64) returns: Distances + experience (f64 — how much the exit observer knows). Every learned output carries its track record. The consumer filters. broker-accums: Vec — the broker’s global per-pair learners. the cascade, per magic number:
    ;; experienced? = (> (experience reckoner) 0.0) — convenience predicate
    ;; has-data? = (> (:count accum) 0) — at least one observation
    (if (experienced? reckoner)
    (predict reckoner composed) ; contextual — for THIS thought
    (if (has-data? broker-accum)
    (extract-scalar broker-accum ...) ; global per-pair — any thought
    default-distance)) ; crutch — the starting value
    One call, four answers. Each distance cascades independently.
  • (observe-distances exit-obs composed optimal weight) composed: Vector — the COMPOSED thought (market + exit facts), not the raw market thought. The exit observer learns from the same vector it produced via evaluate-and-compose(). This is what makes the learning contextual. optimal: Distances — the hindsight-optimal distances from resolution. The market spoke — all four reckoners learn from one resolution.
  • (experienced? exit-obs) → bool true if ALL FOUR reckoners have accumulated enough observations to produce meaningful predictions (experience > 0.0 on each). If any reckoner is ignorant, the exit observer is inexperienced — the cascade falls through to the ScalarAccumulator or crutch.

Broker (depends on: Reckoner, OnlineSubspace, ScalarAccumulator)

Section titled “Broker (depends on: Reckoner, OnlineSubspace, ScalarAccumulator)”

The accountability primitive. Today: binds one market observer + one exit observer. N×M brokers total. Tomorrow: more observer kinds may join. Holds papers. Propagates resolved outcomes to every observer in the set. Measures Grace or Violence.

The broker’s identity IS the set of observer names it closes over. {"momentum", "volatility"} is one broker. {"regime", "timing"} is another. {"momentum", "volatility", "drawdown"} is a third — N observers, not locked to two.

The broker does NOT own the observers — they live on the post. The broker knows their coordinates: indices into the post’s observer vecs, resolved from names at construction, frozen forever. At runtime the broker grabs its observers by index. O(1). The coordinates are known.

The broker does NOT own proposals or active trades — those are the treasury’s. The broker proposes TO the treasury.

Lock-free parallel access. At construction, the enterprise enumerates all broker sets. Each set gets a slot in a flat vec. The mapping Set<String> → slot-idx is built once, then frozen. Never written to again. At runtime, all access is by slot-idx into the flat vec. Disjoint slots. No mutex. The borrow checker proves the writes are disjoint.

construction: enumerate all sets → allocate flat vec → build frozen map
runtime: frozen map (read-only) → slot-idx → &mut broker (disjoint)
(struct broker
[observer-names : Vec<String>] ; the identity. e.g. ("momentum" "volatility").
[slot-idx : usize] ; the broker's position in the N×M grid. THE identity.
[exit-count : usize] ; M — needed to derive market-idx and exit-idx:
; market-idx = slot-idx / exit-count
; exit-idx = slot-idx mod exit-count
; One fact (slot-idx), not two. The indices are derived.
;; Accountability
[reckoner : Reckoner] ; :discrete — Grace/Violence
[noise-subspace : OnlineSubspace]
[curve : Curve] ; measures how much edge this broker has earned.
; fed by the reckoner's resolved predictions.
;; Track record
[cumulative-grace : f64]
[cumulative-violence : f64]
[trade-count : usize]
;; Papers — the fast learning stream
[papers : VecDeque<PaperEntry>] ; capped
;; Scalar learning
[scalar-accums : Vec<ScalarAccumulator>]
;; Engram gating
[good-state-subspace : OnlineSubspace]
[recalib-wins : usize]
[recalib-total : usize]
[last-recalib-count : usize])

Interface:

  • (make-broker observers slot-idx exit-count dims recalib-interval scalar-accums) → Broker observers: list of lens names (e.g. ’(“momentum” “volatility”)). slot-idx: usize — the broker’s position in the N×M grid. Assigned by the post at construction. THE identity. market-idx and exit-idx are derived: market-idx = slot-idx / exit-count, exit-idx = slot-idx mod exit-count. exit-count: usize — M, the number of exit observers. scalar-accums: Vec. noise-subspace: (online-subspace dims 8). good-state-subspace: (online-subspace dims 4). Same k values as MarketObserver — same mechanism, same dimensionality needs.
  • (propose broker composed) → Prediction noise update → strip noise → predict Grace/Violence
  • (edge broker) → f64 — how much edge? The curve reads the broker’s accuracy at its typical conviction level. 0.0 = no edge. The treasury funds proportionally. More edge, more capital.
  • (register-paper broker composed entry-price entry-atr distances) create a paper entry — every candle, every broker. distances: Distances (all four: trail, stop, tp, runner-trail) from the exit observer.
  • (tick-papers broker current-price) → (Vec<Resolution>, Vec<LogEntry>) tick all papers, resolve completed. Returns resolution facts and PaperResolved log entries. Paper optimal-distances: papers don’t carry price-history. They derive optimal distances from their tracked extremes (MFE/MAE): buy-extreme and sell-extreme relative to entry-price. This is a simpler approximation than the full replay used for real trades. The objective is the same (maximize residue) but the data is limited to what the paper tracked. The wat implements the approximation.
  • (propagate broker thought outcome weight direction optimal market-observers exit-observers) thought: Vector. outcome: Outcome. weight: f64 — how much value was at stake. A $500 Grace teaches harder than a $5 Grace. direction: Direction — derived from the trade’s price movement. If exit-price > entry-price, :up. If exit-price < entry-price, :down. optimal: Distances from hindsight. The post passes its observer vecs — the broker uses its frozen indices to reach the right observers. Routes:
    • Grace/Violence + thought + weight → broker’s own reckoner
    • direction + thought + weight → market observer via resolve
    • optimal distances + composed thought + weight → exit observer via observe-distances
  • (paper-count broker) → usize

Two mechanisms for the same magic numbers — both now introduced:

The exit observer’s continuous reckoners are CONTEXTUAL: “for THIS thought, what distance?” Different thoughts → different answers.

The broker’s ScalarAccumulators are GLOBAL per-pair: “what value does Grace prefer for this pair overall?” One answer regardless of thought.

Both learn from the same resolution events. Different questions. The cascade when queried: contextual (reckoner) → global per-pair (ScalarAccumulator) → default (crutch).


Post (depends on: IndicatorBank, MarketObserver, ExitObserver, Broker)

Section titled “Post (depends on: IndicatorBank, MarketObserver, ExitObserver, Broker)”

A self-contained unit for one asset pair. The post is where the thinking happens. It owns the observers, the brokers, the indicator bank. It does NOT own proposals or trades — those belong to the treasury.

Each post watches one market. (USDC, WBTC) is one post. (USDC, SOL) is another. No cross-talk. Observers within a post learn together. Observers across posts are independent.

The post proposes to the treasury. The treasury decides. When a trade closes, the treasury routes the outcome back to the post for accountability — to the broker that proposed it.

(struct post
;; Identity
[post-idx : usize] ; this post's index in the enterprise's posts vec
[source-asset : Asset] ; e.g. USDC
[target-asset : Asset] ; e.g. WBTC
;; Data pipeline
[indicator-bank : IndicatorBank] ; streaming indicators for this pair
[candle-window : VecDeque<Candle>] ; bounded history
[max-window-size : usize] ; capacity
;; Observers — both are learned, both are per-pair
[market-observers : Vec<MarketObserver>] ; [N]
[exit-observers : Vec<ExitObserver>] ; [M]
;; Accountability — brokers in a flat vec, parallel access
[registry : Vec<Broker>] ; one per observer set, pre-allocated
;; Counter
[encode-count : usize])

Interface:

  • (make-post post-idx source target dims recalib-interval max-window-size indicator-bank market-observers exit-observers registry) → Post
  • (post-on-candle post raw-candle ctx) → (Vec<Proposal>, Vec<Vector>, Vec<(ThoughtAST, Vector)>) Returns proposals for the treasury, market-thoughts for step 3c, AND all collected cache misses from encoding. No queues — misses are values. tick indicators → push window → market observers observe-candle (→ thoughts + predictions + edge + misses) → exit observers encode-exit-facts then evaluate-and-compose(market-thought, exit-fact-asts, ctx) → (composed + misses) → exit observers recommended-distances(composed, broker.scalar-accums) → Distances (the POST passes the broker’s scalar accumulators to the exit observer — the post has access to both because it owns both) → brokers propose(composed) → returns Prediction (Grace/Violence) → the POST assembles each Proposal from: composed-thought, broker’s Prediction, distances, broker.edge(), post-idx, broker-slot-idx. Side derivation: the market observer’s Prediction has scores for “Up” and “Down”. The winning label maps to Side: “Up” → :buy, “Down” → :sell. The market observer’s edge (curve-valid, the third return value) is available to the broker as a fact per the message protocol — the broker MAY encode it as (bind (atom "market-edge") (encode-linear edge 1.0)) in its composed thought. This is a coordinate for later — the current architecture does not yet consume it. The value is produced and returned so the path exists when the broker is ready to use it. → register papers → return proposals, market-thoughts, and collected misses
  • (post-update-triggers post trades market-thoughts ctx) → Vec<(ThoughtAST, Vector)> trades: Vec<(TradeId, Trade)> — treasury’s active trades for this post. market-thoughts: Vec — this candle’s encoded thoughts (one per market observer). Returns cache misses from exit observer composition. The post composes with exit observers for distances. Each trade’s trailing stop adjusts to the current market context.
  • (current-price post) → f64 the close of the last candle in the post’s candle-window. The enterprise calls this per post to build current-prices for the treasury.
  • (compute-optimal-distances price-history direction) → Distances direction: Direction — :up or :down. Which way the price moved. This is observation (what the price did), not action (what the trader did). FREE FUNCTION — not a Post method. Takes no self. Pure. The objective function: for each distance (trail, stop, tp, runner-trail), sweep candidate values against the price-history. For each candidate, simulate the trailing stop mechanics. The candidate that produces the maximum residue IS the optimal distance. This is a well-posed optimization over a finite series — not a heuristic. The exit observer learns to predict this value BEFORE the path completes. The wat may approximate this optimization (e.g. MFE/MAE ratios) but the objective is: maximize residue. price-history in, Distances out. Called by the enterprise when enriching TreasurySettlement into Settlement.
  • (post-propagate post slot-idx thought outcome weight direction optimal) → Vec<LogEntry> direction: Direction. No observer vecs in the signature — the post owns them (self) and passes them to broker.propagate internally. The enterprise routes a settlement back to the post. Returns Propagated log entries.

Treasury (depends on: nothing — pure accounting, but receives proposals from Posts)

Section titled “Treasury (depends on: nothing — pure accounting, but receives proposals from Posts)”

Holds capital. Capital is either available or reserved. When a trade is funded, the capital moves from available to reserved — off limits. No other trade can touch it. When the trade ends, the principal returns to available. The residue is permanent gain.

Receives proposals from posts — the barrage. Accepts or rejects based on available capital and the broker’s Grace/Violence ratio. If 10 brokers propose and there’s capital for 3 — fund the top 3, reject the rest.

Settles trades. Routes outcomes back to posts for accountability. The maximum loss on any trade is bounded by its reservation.

The treasury is where the money happens. It does not think. It counts. It decides based on capital availability and proof curves.

The treasury maps each active trade back to its post and broker so that on settlement, propagate reaches the right observers.

(struct treasury
;; Capital — the ledger
[denomination : Asset] ; what "value" means (e.g. USD)
[available : Map<Asset, f64>] ; capital free to deploy
[reserved : Map<Asset, f64>] ; capital locked by active trades
;; The barrage — proposals received each candle, drained after funding
[proposals : Vec<Proposal>] ; cleared every candle
;; Active trades — funded proposals become trades
[trades : Map<TradeId, Trade>]
[trade-origins : Map<TradeId, TradeOrigin>]
;; Counter
[next-trade-id : usize]) ; monotonic

Interface:

  • (make-treasury denomination initial-balances) → Treasury denomination: Asset — what “value” means (e.g. USD). initial-balances: map of Asset → f64. All other fields start empty/zero.
  • (submit-proposal treasury proposal) a post submits a proposal for the treasury to evaluate. The proposal carries post-idx and broker-slot-idx inside it.
  • (fund-proposals treasury) → Vec<LogEntry> evaluate all proposals, sorted by proposal edge (the curve’s accuracy measure). Fund the top N that fit in available capital. Reject the rest. Returns ProposalFunded and ProposalRejected log entries. For each funded proposal: move capital from available to reserved, create a Trade, stash a TradeOrigin (post-idx, broker-slot-idx, composed-thought) for propagation at settlement time. Drain proposals.
  • (settle-triggered treasury current-prices) → (Vec<TreasurySettlement>, Vec<LogEntry>) current-prices: map of (Asset, Asset) → f64 — one price per asset pair. Each post provides its latest candle close as its current price. Check all active trades against their stop-levels, settle what triggered. Returns treasury-settlements and TradeSettled log entries. Three trigger paths per trade phase:
    • :active + safety-stop-hit → phase becomes :settled-violence. Principal minus loss returns to available. Trade is done.
    • :active + take-profit-hit → phase becomes :principal-recovered. Principal returns to available. Trade continues as a runner — residue rides with the runner-trail-stop. Zero cost basis.
    • :runner + runner-trail-hit (or :principal-recovered + runner-trail-hit) → phase becomes :settled-grace. Residue is permanent gain. Returns to available. Trade is done. Each settled trade produces a TreasurySettlement. The enterprise enriches it into a Settlement (derives direction, replays trade’s price-history for optimal-distances) before routing to post-propagate.
  • (available-capital treasury asset) → f64 how much is free to deploy?
  • (deposit treasury asset amount) add to available
  • (total-equity treasury) → f64 available + reserved, all converted to denomination
  • (update-trade-stops treasury trade-id new-levels) new-levels: Levels — absolute price levels, not Distances (percentages). The post converts Distances → Levels using the current price. step 3c: the post computes, the enterprise writes back.
  • (trades-for-post treasury post-idx) → Vec<(TradeId, Trade)> step 3c: the enterprise queries active trades for a given post.

The coordination plane. The CSP sync point.

The enterprise is the only entity that sees the whole picture. Every other entity is an independent process — it takes input and produces output. It does not know about parallelism, ordering, or other entities.

The enterprise holds posts and a treasury. It routes raw candles to the right post. It coordinates the four-step loop across all posts and the treasury.

The enterprise knows:

  • What runs parallel — market observers encode simultaneously (par_iter)
  • What runs sequential — exit dispatch into registry (disjoint slots)
  • What order — Step 1: RESOLVE+PROPAGATE → Step 2: COMPUTE+DISPATCH → Step 3a: TICK (parallel) → Step 3b: PROPAGATE (papers) → Step 3c: UPDATE TRIGGERS → Step 4: COLLECT+FUND
  • What flows where — proposals from posts to treasury, settlements from treasury to posts
  • What gets cleared — proposals empty after funding, every candle
(struct enterprise
;; The posts — one per asset pair
[posts : Vec<Post>] ; each watches one market
;; The treasury — shared across all posts
[treasury : Treasury] ; holds capital, funds trades, settles
;; The enterprise does NOT own immutable config. It receives ctx
;; as a parameter on every on-candle call. ctx is born at startup
;; and never changes. The enterprise is mutable state. ctx is not.
;; Per-candle cache — produced in step 2, consumed in step 3c
[market-thoughts-cache : Vec<Vec<Vector>>]) ; one Vec<Vector> per post, cleared each candle
;; Log entries and cache misses are returned as values from each step,
;; not accumulated in queues. The enterprise collects them from return
;; values and processes them sequentially at the candle boundary.
;; Cache misses: collected from all steps, inserted into ThoughtEncoder
;; after all steps complete. Eventually-consistent — miss on candle N,
;; hit on N+1. Same pattern, no queues.
;; Log entries: collected from fund-proposals, settle-triggered,
;; tick-papers, and post-propagate. The binary decides what to do
;; with them (write to DB, print, discard).

Interface:

  • (on-candle enterprise raw-candle ctx) → (Vec<LogEntry>, Vec<(ThoughtAST, Vector)>) route to the right post, then four steps. ctx flows in from the binary. Each step returns its log entries and cache misses as values. The enterprise collects all cache misses from all steps and returns them alongside the concatenated log entries. The BINARY inserts the misses into ctx’s ThoughtEncoder cache between candles — the enterprise cannot, because it does not own ctx. Returns: log entries (for the ledger) and cache misses (for the seam).
  • (step-resolve-and-propagate enterprise) → Vec<LogEntry> Returns TradeSettled and Propagated log entries. No ctx needed — settlement and propagation use pre-existing vectors, no encoding happens. The enterprise collects current prices internally (calls current-price on each post). Treasury settles triggered trades using those prices. For each settlement: enterprise computes optimal-distances via compute-optimal-distances (free function — price-history in, Distances out), then routes to the post for propagation.
  • (step-compute-dispatch enterprise post-idx raw-candle ctx) → (Vec<Proposal>, Vec<Vector>, Vec<(ThoughtAST, Vector)>) post-idx: usize — which post. raw-candle: RawCandle — the raw candle received by on-candle, threaded through to the post. The post returns proposals, market-thoughts, and cache misses as values. post encodes, composes, proposes — returns proposals for the treasury, market-thoughts (Vec) for step 3c, and cache misses. The enterprise caches market-thoughts between steps and collects misses.
  • (step-tick enterprise post-idx) → (Vec<Resolution>, Vec<LogEntry>) parallel tick of all brokers’ papers. Returns resolution facts and PaperResolved log entries.
  • (step-propagate enterprise post-idx resolutions) → Vec<LogEntry> sequential: apply resolutions to observers. Returns Propagated log entries. Brokers learn Grace/Violence. Market observers learn Up/Down. Exit observers learn optimal distances.
  • (step-update-triggers enterprise post-idx market-thoughts ctx) → Vec<(ThoughtAST, Vector)> the enterprise queries the treasury for active trades belonging to this post, then calls post-update-triggers(post, trades, market-thoughts, ctx). The post composes each trade’s market thought with exit observers, queries fresh distances, computes new trailing stop levels. Returns cache misses from exit observer composition. The enterprise writes the new values back to the treasury’s trade records and collects the misses. This is step 3c — after tick and propagate.
  • (step-collect-fund enterprise) → Vec<LogEntry> treasury funds or rejects all proposals, returns log entries, drains

The Binary (depends on: Enterprise, ctx, Ledger)

Section titled “The Binary (depends on: Enterprise, ctx, Ledger)”

The outer shell. The driver of the fold. The binary creates the world, feeds candles, writes the ledger, and displays progress. It does not think. It does not predict. It does not learn. It orchestrates.

The binary has a wat file like everything else — bin/enterprise.wat. The wat specifies the shape. The Rust implements it. The binary is the root of the call tree.

Responsibilities:

  1. CLI — parse arguments. The configuration that the enterprise receives as constants:

    • dims — vector dimensionality (default 10000)
    • recalib-interval — observations between recalibrations (default 500)
    • denomination — what “value” means (e.g. “USD”)
    • assets — the pool of assets to manage, as a list of (name, initial-balance) pairs. e.g. [("USDC", 10000.0), ("WBTC", 0.0)] or [("USDC", 2000.0), ("WBTC", 0.1)] — whatever amount of value you want, in whatever form. The initial balances are valued at the first candle’s prices to compute initial equity in the denomination. The binary does not know or care what the assets ARE. Each unique pair of assets becomes a post. One asset pair today. Many tomorrow. The architecture is the same.
    • data-sources — one data source per asset pair. Parquet path or websocket URL. The binary maps each source to its pair.
    • max-candles — stop after N candles (0 = run all)
    • swap-fee — per-swap venue cost as fraction (e.g. 0.0010 = 10bps)
    • slippage — per-swap slippage estimate as fraction (e.g. 0.0025)
    • max-window-size — maximum candle history (default 2016)
    • ledger — path to output SQLite database (auto-generated if omitted)
  2. Construction — build the world, then the machine:

    vm = make-vector-manager(dims)
    thought-encoder = make-thought-encoder(vm)
    ctx = { thought-encoder, dims, recalib-interval }
    ;; One post per asset pair — the binary enumerates pairs from the asset pool
    posts = for each (source, target) pair in assets:
    indicator-bank = make-indicator-bank()
    market-observers = [make-market-observer for each MarketLens variant]
    exit-observers = [make-exit-observer for each ExitLens variant]
    registry = [make-broker for each (market, exit) combination]
    make-post(idx, source, target, dims, recalib-interval,
    max-window-size, indicator-bank,
    market-observers, exit-observers, registry)
    treasury = make-treasury(denomination, initial-balances)
    enterprise = make-enterprise(posts, treasury)

    ctx is immutable after construction. Enterprise is mutable state. One pair today (USDC, WBTC). The architecture holds any number.

  3. Ledger — initialize SQLite database for this run:

    • meta table — run parameters (dims, recalib-interval, fees, etc.)
    • log table — receives LogEntry values from on-candle The ledger is the glass box. The DB is the debugger.
  4. The loop — the fold driver:

    for raw-candle in stream:
    if kill-file exists → abort
    (log-entries, cache-misses) = on-candle(enterprise, raw-candle, ctx)
    insert cache-misses into ctx.thought-encoder ; the one seam
    flush log-entries to ledger (in batches)
    if progress-interval → display diagnostics

    The stream comes from parquet (backtest) or websocket (live). The binary doesn’t know which. It consumes RawCandles. Same code path.

  5. Progress — every N candles, display:

    • encode-count, throughput (candles/second)
    • treasury equity, return vs buy-and-hold
    • per-observer stats (recalib count, discriminant strength)
    • broker stats (paper count, Grace/Violence ratio, curves proven)
    • accumulation (residue earned per side)
  6. Kill switch — the file trader-stop. Touch it to abort the run. Checked periodically (every 1000 candles), not every candle.

  7. Summary — after the loop completes:

    • final equity, return percentage, buy-and-hold comparison
    • trade count, win rate, accumulation totals
    • venue costs paid
    • observer panel summary
    • ledger path and row count

Query functions — the binary reads enterprise state for diagnostics. These are public API on the enterprise’s components, not the binary’s logic. The binary just calls them and formats the output.

Interface functions (declared on their structs):

  • (total-equity treasury) → f64
  • (paper-count broker) → usize
  • (experience observer) → f64
  • (recalib-count reckoner) → usize
  • (edge broker) → f64

Field reads (keyword-as-function, from the struct definition):

  • (:encode-count post) → usize
  • (:cumulative-grace broker) → f64
  • (:cumulative-violence broker) → f64
  • (:trade-count broker) → usize

The binary is the last thing built. It depends on everything. It touches nothing. It drives the fold and writes what happened.


The construction order section above IS the build order. The sections below it detail each entity in the same order. Each file is agreed upon before the next is written.


The CSP (Communicating Sequential Processes) per candle

Section titled “The CSP (Communicating Sequential Processes) per candle”

Every boundary is a channel. Every process reads from its channels and writes to its channels. Nobody reaches across. The coupling is data flow, not shared mutation. Nothing learns in the moment. Everything learns from the past. Produce now, consume later, learn from what actually happened.

;; What flows between processes. Each channel has a type.
raw-candle ; RawCandle — enterprise → post (routed by asset pair)
market-thoughts ; Vec<Vector> — the thought vectors from market observers
; (predictions are internal to the observer)
composed ; Vec<Vector> — exit observers → brokers
proposals ; Vec<Proposal> — posts → treasury (the barrage)
treasury-settlements ; Vec<TreasurySettlement> — treasury → enterprise
settlements ; Vec<Settlement> — enterprise enriches → posts (reality feedback)
trade-triggers ; Vec<(TradeId, Trade)> — treasury → posts (active trades for update)
distances ; Distances — exit observers → proposals + papers
propagation ; (thought, outcome, weight)
; broker → market observer.resolve (Up/Down)
; broker → exit observer.observe-distances (optimal)
; broker → self reckoner (Grace/Violence)

The four steps — who produces, who consumes

Section titled “The four steps — who produces, who consumes”
Step 1: RESOLVE + PROPAGATE (propagation path 1 — real trades)
treasury reads: active trades, current price
treasury produces: treasury-settlements
enterprise enriches: treasury-settlements → settlements (adds direction, optimal-distances)
enterprise routes: settlements → posts → brokers → propagation → observers learn
NOTE: this IS propagation — real trade outcomes teach the observers.
Step 3b is propagation path 2 (paper resolutions). Both paths call
broker.propagate. Both teach. Different sources, same mechanism.
Step 2: COMPUTE + DISPATCH
posts read: raw-candle
market observers produce: market-thoughts (parallel, par_iter)
exit observers consume: market-thoughts → compose → composed
brokers consume: composed → propose → register paper
posts produce: proposals (the barrage)
treasury receives: proposals
Step 3a: TICK (parallel — all cores)
brokers par_iter: tick papers, check conditions, compute outcomes
each broker touches ONLY its own papers. Disjoint. Lock-free.
brokers produce: Vec<Resolution> — facts, not mutations
collect() is the synchronization primitive.
Step 3b: PROPAGATE (propagation path 2 — paper resolutions, sequential)
fold over resolutions: apply to shared observers
market observers learn Up/Down. Exit observers learn distance.
brokers learn Grace/Violence. Sequential because observers are shared.
Same broker.propagate as step 1. Different source (papers, not trades).
Step 3c: UPDATE TRIGGERS (sequential)
treasury passes active trades to posts.
posts compose fresh thoughts, query exit observers for distances.
posts compute new stop levels. Treasury applies new values to trades.
Step 4: COLLECT + FUND
treasury reads: proposals, available capital, broker edge levels
treasury produces: funded trades (move capital: available → reserved)
treasury drains: proposals → empty

See wat/CIRCUIT.md — the machine as signal flow diagrams. No new definitions; it visualizes the components and interfaces declared above. The full enterprise circuit, plus sub-circuits for encoding, learning, papers, funding, cascade, and propagation. Mermaid source + component and edge legends.

f(state, candle) → state — one tick of the clock.