The Entry Gauntlet
A stimulus becomes an order only by surviving seven ordered tests. Most do not survive, and the row records which test killed them. That is the design working, not failing.
A stimulus is a question, not an answer
Trades happen only at defined decision points and only from eight named causes. Everything else — a hunch, a headline that did not clear the classifier, a probability that moved but did not cross its configured line — is not a stimulus and produces no order. The absence of a trade needs no journal entry; the presence of one always has exactly one Stimuli row at its root.
| Code | Stimulus | Example |
|---|---|---|
| S1 | Signal threshold crossing | Momentum rank enters the top N; a sector grade upgrades to A |
| S2 | Probability threshold crossing | p.regime-persist-30d drops below 0.40 → de-risk |
| S3 | Event trigger | A news item scores materiality ≥ 4 with volume confirmation |
| S4 | Regime transition | The GMI regime flips; allocation matrices re-key |
| S5 | Rebalance / drift | Allocation drift breaches its band |
| S6 | Risk trigger | Stress gauge escalates; a position hits its drawdown stop — overrides everything |
| S7 | Desk signal | Allan's workup-approved user signal |
| S8 | Scheduled review verdict | An exit-check or contradiction check returns broken |
Seven stages, in order
The stage names below are the seed rows in PipelineStageTypes, in their SortOrder. Every stage reads the frozen MarketState blob, not the tables; the world was computed once at 06:45 and every stimulus sees the same copy.
| Stage | Asks | Reads |
|---|---|---|
predicate | Does this strategy's config actually say act? | StrategyConfigs |
confirmation | Do the confirmation-role signals agree — RVOL, breadth, materiality corroboration? | ResolverStances via the blob |
veto | Does a veto-role signal say no — stress gauge, priced-in gauge, the earnings pre-print window, a standing contradiction verdict? | same — risk always outranks |
probability | Does the named p-signal clear its bar, with enough nEffective and a narrow enough interval? | ProbabilityEmissions via the blob |
caps | Would this breach a lane cap, an account cap, the holdings count, the minimum position size, the short-exposure cap? | TradingAccounts, Positions |
sizing | How much, in dollars, fractional — and does the commission-drag guard allow it? | account equity, the sizing mode |
construction | What order type, what collar, what execution window; for shorts, is borrow available? | Instruments, the bars |
Short entries run the same gauntlet with four additions: the strategy must declare can_short; borrow availability and modeled borrow cost enter sizing; a short-exposure cap applies; and a squeeze guard refuses new shorts in names above a short-interest or days-to-cover threshold. Until the live arming step exists, shorts are paper only.

Most of them die, and the row says where
Each pass through the gauntlet is one PipelineRuns row. It records the outcome and, when the stimulus was refused, FkKilledAtPipelineStageTypesKey — which stage, and which gate within it. Most rows in this table are refusals. A system whose main output is trades it decided not to make, journaled as carefully as the ones it did, is a system that can be trusted to enter.
The kills are not waste. A refused stimulus becomes a StandAside decision, and the learning loop scores it against what would have happened: AvoidedLoss or MissedGain. "The veto gate saved us X this quarter" is a query, not an impression. The full reason chain is written before any order is submitted, never after.
Risk always outranks
Two places enforce it. On the way in, the veto stage sits third — after the strategy has said yes and confirmation has agreed — and a stress or priced-in resolver can still stand the trade down. The stress resolver is an absolute veto over the composite stance; no strategy opinion and no desk tilt outranks it.
On the way out, E5 risk stops can fire in any hourly window regardless of what any strategy thinks, and the exit-evaluation pipeline checks them first. Risk exits are also the one class of decision with no staleness budget: a stop that should have fired at 11:00 still fires at 15:00 after downtime, because the position is still wrong.
The other two pipelines
The same PipelineRuns table journals two more pipelines, distinguished by FkDecisionPipelineTypesKey, and they run on held positions rather than candidates.
hold-review asks three things in order at each position's own review tier — daily, weekly, or quarterly. exit-rules: has any bound exit fired? entry-quality: has the thesis-relevant signal set degraded below the entry predicate — would we enter this today? Tactical lanes exit on failure; long-horizon lanes flag for review. slot-defense: does the position still earn its slot against the current candidate ranking? Passing all three is the hold decision, journaled once at the review tier, not seven times a day.
exit-evaluation runs every hourly window. risk-precedence first: E5 and S6 exits, always allowed to fire. Then exit-arbitration: the remaining armed exits in order, first to fire wins, with hysteresis so positions do not flap. Then exit-construction: the exit order runs the same construction and journal path as an entry.
Eight ways out, bound at entry
Every position binds its exits when it opens. They are typed so that each kind gets its own learning-loop bucket — "our time stops make money, our panic never fires" becomes measurable.
| Code | Exit | Trigger |
|---|---|---|
| E1 | Thesis complete | The entry thesis' stated outcome occurred — the good exit |
| E2 | Signal decay | The driving signal fell through its exit threshold, set below the entry threshold |
| E3 | Probability flip | The bound p-signal crossed its exit line |
| E4 | Time stop | Horizon expiry — mandatory for capped lanes; re-entry is a new gauntlet |
| E5 | Risk stop | Drawdown stop or stress-gauge de-risk — always outranks holds |
| E6 | Thesis broken | A contradiction verdict, or the entry's what would change this occurred |
| E7 | Valuation outrun | Price appreciation outran earnings-power growth past the stretch factor |
| E8 | Displacement | A rebalance ranked a new candidate above the weakest holding beyond the switch margin |
These were a JSON list in an early draft. They are now PositionExits rows — one armed exit per row, with its trigger (a price, a time, a threshold value, a trail), its status (Armed, Triggered, Cancelled, Expired), and once triggered, the order that executed it. The reason is a query: every hourly window asks which armed stops are below the current price?, and that question should be an index, not a document scan.

- Stimuli — the cause, with
TriggerJsonand a pointer to the frozen world - PipelineRuns — the pass, and
FkKilledAtPipelineStageTypesKeyif it died - Decisions — the verdict, with the typed expectation it will be graded on
- PositionExits — the armed E1–E8 rows on a live position
- The stress resolver vetoes everything
- E5 fires in any window, whatever the strategy thinks
- First armed exit to fire wins; hysteresis stops flapping
- A hold is journaled once, at its review tier