Replay external fills and the first internal-match boundary under three explicit prevention modes.
The decision this tutorial makes visible
A self-match control must distinguish prevented quantity from genuine external execution and preserve a reasoned audit trail.
The precise question is: When an incoming order reaches contra interest from the same prevention group, what is canceled, decremented, or allowed to continue?
An operator needs a deterministic accept, reject, prevent, purge, or trigger decision with its exact reason. A builder needs the same point-in-time policy and order state to reproduce that decision in code, audit data, visuals, and the browser lab.
Intuition before notation
The matcher may trade with external interest before it encounters internal interest. Prevention begins at the exact self-match encounter, not at order entry.
The result depends on instrument and venue scope, effective policy identity, session ownership, decision-time reference state, equality rules, and exact units. Changing any one of them creates a different control decision even when the output field name is unchanged.
Scope and nearby methods
The canonical price-time sweep identifies self interest by participant plus STP group and supports cancel-newest, cancel-oldest, and decrement-both. It is not a universal venue rule.
| Variant | Definition | Best use | Main limitation |
|---|---|---|---|
| Cancel newest | Cancel incoming residual at self encounter | Protect resting liquidity | May stop access to later external orders |
| Cancel oldest | Cancel resting self order and continue | Favor incoming intent | Changes resting book |
| Decrement both | Reduce both sides without a trade | Quantity-aware prevention | Venue semantics vary |
What is sourced, selected, synthetic, and derived
| Role | Material claim | Evidence | Boundary |
|---|---|---|---|
| Sourced fact | Nasdaq OUCH and Cboe MTP documentation distinguish executed quantity from self-match-prevention decrements or cancellations and expose configurable prevention behavior. | S1, S2 | Venue fields, identity scope, defaults, overrides, and exceptions differ; the package's participant-plus-group identity and three-mode engine are explicit teaching choices. |
| Implementation choice | The canonical price-time sweep identifies self interest by participant plus STP group and supports cancel-newest, cancel-oldest, and decrement-both. It is not a universal venue rule. | Frozen package definition | Production control hierarchies, exemptions, overrides, and account/venue rules remain outside scope unless explicitly named. |
| Synthetic teaching input | Canonical orders, prices, thresholds, sessions, and identifiers are repository-authored. | datasets/canonical-input.json and scenario-results.json | They are not observed participant or venue records. |
| Author-derived calculation | The synthetic incoming buy first executes 150 external shares, then meets 220 shares from its own group. Cancel-newest prevents that match and cancels the 450-share incoming residual. | Formula, canonical fixture, Python/TypeScript parity, and independent arithmetic | Correct arithmetic does not establish compliance, latency, or trading value. |
The primary sources support only the named regulatory or venue behavior. They do not certify the synthetic policy IDs, orders, thresholds, sessions, or outputs. Those values are repository-authored, and every displayed result is an author-derived calculation under the selected control contract.
Formula, symbols, and numerical policy
self_match = same_participant ∧ same_group; action = policy(self_match)
| Symbol | Meaning | Unit | Policy |
|---|---|---|---|
| Q_in | incoming quantity | shares | positive |
| Q_ext | external executed quantity | shares | actual execution |
| Q_stp | prevented internal quantity | shares | no trade |
| M | prevention mode | enum | explicit |
- Process price-time order deterministically.
- Use min(incoming leaves, resting quantity) as the prevented encounter quantity.
- Never create a trade record for decrement-both.
Read the formula in the same order as the algorithm. Validate identity, ordering, units, and supported state first. Apply the selected equality and window rules second. Calculate with unrounded numeric values. Round only at the declared presentation boundary, and preserve null as a diagnostic rather than coercing it to zero.
Build the algorithm
- Sort marketable contra orders by price-time
- Execute external interest normally
- At a matching STP group apply the selected mode
- Emit every cancellation, decrement, and execution
- Return the residual book
Production-minded operational checklist
- Resolve prevention identity scope
- Load configured mode
- Preserve price-time order
- Separate execution and prevention ledgers
- Reconcile both residual quantities
The checklist is intentionally strict: an explicit rejection is safer than a plausible output built from stale, malformed, or unsupported state.
Worked synthetic example
The canonical fixture is synthetic teaching data, not an observed control
event, customer order, or broker execution. Its primary author-derived output,
prevented_self_quantity, is 150 shares execute externally, 220 shares are prevented from self-matching, and the incoming residual is canceled. The complete input and output
are in datasets/canonical-input.json and datasets/expected-output.json.
The synthetic incoming buy first executes 150 external shares, then meets 220 shares from its own group. Cancel-newest prevents that match and cancels the 450-share incoming residual.
Counterfactual checkpoint
External liquidity consumes the parent first. Reduce the incoming order to 150 shares while leaving the first external contra order unchanged. The output changes because Price-time processing encounters and exhausts external quantity before an STP boundary exists.
The structured result retains state and diagnostics in addition to the primary number. That makes the calculation independently reviewable and prevents a partial, null, rejected, or venue-bounded outcome from being mistaken for an unqualified value.
Boundary and counterexample workbook
The playground computes every scenario at 61 deterministic parameter states.
The table uses the declared focus step and states whether that focus reproduces
the canonical fixture. The full state ledger and compressed transition
segments are in datasets/scenario-results.json.
| Scenario | Review focus | Purpose | State | Primary output | Diagnostic | Decision segments |
|---|---|---|---|---|---|---|
| Canonical cancel-newest sweep | Step 50 · canonical fixture | Execute external interest first, then prevent same-group quantity by canceling the incoming residual. Synthetic data; the effective policy remains explicit. | self-trade-prevented | prevented 220 shares | external 150; incoming leaves 0 | 2 |
| External-fill exhaustion | Step 30 · comparison focus | Reduce incoming quantity so external interest fills it before any self-match boundary. Synthetic data; the effective policy remains explicit. | externally-filled | prevented 0 shares | external 150; incoming leaves 0 | 1 |
| Cancel-oldest mode | Step 30 · comparison focus | Keep the incoming order and cancel eligible same-group resting interest. Synthetic data; the effective policy remains explicit. | self-trade-prevented | prevented 220 shares | external 400; incoming leaves 0 | 2 |
| Decrement-both accounting | Step 30 · comparison focus | Remove matched quantity from both orders without recording an external execution. Synthetic data; the effective policy remains explicit. | self-trade-prevented | prevented 220 shares | external 180; incoming leaves 0 | 2 |
| Different-group comparison | Step 30 · comparison focus | Change the incoming STP group so otherwise marketable resting interest may execute. Synthetic data; the effective policy remains explicit. | externally-filled | prevented 0 shares | external 400; incoming leaves 0 | 2 |
| Partial self-resting quantity | Step 30 · comparison focus | Vary the quantity of same-group interest and inspect residual matching. Synthetic data; the effective policy remains explicit. | self-trade-prevented | prevented 200 shares | external 200; incoming leaves 0 | 2 |
| Self-interest first in queue | Step 30 · comparison focus | Place same-group interest at the first eligible price-time position and prevent before any external fill. Synthetic data; the effective policy remains explicit. | self-trade-prevented | prevented 150 shares | external 0; incoming leaves 0 | 1 |
These rows are not backtest observations. They are controlled counterexamples that expose how one driver changes the state, output, or reason code while the rest of the contract stays fixed.
Visualize the boundary
Open this SVG at full size, or use the guided playground to compare the seven topic-specific canonical, boundary, policy, and failure scenarios.
The Mermaid flow answers where the selected calculation sits in the processing sequence. The SVG keeps the formula, output, decision boundary, and invariant visible together. The lab lets the reader step through the same structured states without changing the underlying definition.
Implementation walkthrough
The Python and TypeScript references validate policy identity, price/quantity units, session or remembered state, and the supported mode before applying the control. Both return the decision together with distances, violations, cancellation/prevention events, or trigger state so an operator can reconstruct why the gate acted.
The main implementation branches are:
- contra is external — Execute, because Not a self match.
- contra is same group — Apply selected prevention mode, because Internal match would occur.
- price no longer marketable — Stop sweep, because Limit protection.
Neither reference silently fetches data, mutates caller-owned inputs outside the declared engine behavior, guesses hidden state, or substitutes a provider default. Shared JSON fixtures make value, null, state, and reason-code drift visible across languages.
Testing and validation
Definition tests compare every canonical field, reject malformed state, and exercise the material boundary. Family validation recomputes every playground state from the reference function. Independent arithmetic is recorded beside the fixture rather than inferred only from implementation output.
The audit must preserve these invariants:
- Prevented self quantity is never reported as execution.
- Every resting and incoming decrement is conserved.
- Order identifiers are unique.
- The structured output retains
prevented_self_quantity, state, policy context, and reason fields.
Passing these checks proves that the deterministic reference matches the selected control contract. It does not certify exchange conformance, regulatory compliance, latency, operational resilience, or the safety of an override.
Failure modes and misuse
- Passing one control does not imply an order passes every venue, broker, regulatory, credit, position, or market-access check.
- Reference prices, bands, thresholds, group identifiers, and disconnect ownership must be point-in-time inputs; later values cannot repair an earlier decision.
- A definition-correct control does not certify production latency, legal compliance, operational resilience, or trading profitability.
Debugging order
When a result looks surprising, inspect the state in this order:
- Confirm instrument, venue/product, session, order type, side, and policy ID.
- Confirm integer price scale, quantity units, reference or band as-of time, and trigger clock.
- Confirm identity/ownership scope, mode, equality rule, threshold ordering, and remembered state.
- Recalculate the invariant and the declared scenario focus before changing code.
Evidence and historical boundary
Historical decision: deferred. A named production-control event would require complete point-in-time order, policy-version, reference-data, session, override, acknowledgement, and venue evidence plus redistribution permission. A labeled synthetic fixture is more reproducible and avoids implying a public record proves private control behavior.
The primary sources are Nasdaq OUCH 5.0, Cboe Match Trade Prevention. They support the source roles listed in the research ledger, not a redistributable historical observation, a private participant decision, current configuration at an unnamed venue, compliance certification, trading outcome, profitability claim, or prediction claim.
Summary and next topic
You can now implement auditable self-match controls without inventing executions. The learning flow is: Price-Band Validation → Self-Trade Prevention → Cancel-on-Disconnect. Carry the result forward only with its scope, clock, state, and evidence label.
Rendered from the canonical Mermaid sources linked by this article.
Self-Trade Prevention calculation flow
This flow identifies the selected calculation stages and the structured output.
Takeaway: External execution can precede prevention; the prevention mode controls only the internal encounter.
ReferencesPrimary sources and evidence notesExpand the source trail, evidence role, and limitations behind the engineering choices.
Expand the source trail, evidence role, and limitations behind the engineering choices.
S1 — Nasdaq OUCH 5.0 Specification
- Organization or authors: Nasdaq
- Source type: Official exchange order-entry specification
- Publication or effective date: Current document accessed 2026-07-30
- Version: 5.0
- URL or DOI: https://www.nasdaqtrader.com/content/technicalsupport/specifications/TradingProducts/OUCH5.0.pdf
- Accessed: 2026-07-30
- Jurisdiction: Nasdaq U.S. equities
- Supports: Order entry fields, cancellation messages, and self-match-prevention/decrement accounting vocabulary.
- Limitations: Availability, configuration, and behavior remain venue-, port-, MPID-, and rule-version-specific.
S2 — Cboe U.S. Equities Match Trade Prevention
- Organization or authors: Cboe Global Markets
- Source type: Official exchange feature guide
- Publication or effective date: 2019; current link accessed 2026-07-30
- Version: USE_2.4
- URL or DOI: https://cdn.cboe.com/resources/membership/Cboe_US_Equities_MTP.pdf
- Accessed: 2026-07-30
- Jurisdiction: Cboe U.S. equities venues
- Supports: Incoming-order control of match prevention and cancel/decrement mode distinctions for eligible same-group orders.
- Limitations: The repository uses simplified group identity and accounting; production field values, exceptions, and port defaults remain venue-specific.
Evidence boundary
The sources establish only the current or historical rule and protocol facts named in each source record. They do not verify the repository-authored orders, policy identifiers, reference values, thresholds, session state, override authority, or output.
Full dependency-light reference implementations in both supported languages.
/* Deterministic reference algorithms for D12-F03 Order Controls. */
type Inputs = Record<string, any>;
function integer(name: string, value: unknown, positive = false, nonnegative = false): number {
if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${name} must be an integer`);
if (positive && value <= 0) throw new Error(`${name} must be positive`);
if (nonnegative && value < 0) throw new Error(`${name} must be nonnegative`);
return value;
}
function text(name: string, value: unknown): string {
if (typeof value !== "string" || !value.trim()) throw new Error(`${name} must be a nonempty string`);
return value.trim();
}
function booleanValue(name: string, value: unknown): boolean {
if (typeof value !== "boolean") throw new Error(`${name} must be boolean`);
return value;
}
export function tickSizeValidation(
price_atoms: unknown,
tick_size_atoms: unknown,
price_scale: unknown,
effective_policy_id: unknown,
): Record<string, unknown> {
const price = integer("price_atoms", price_atoms, true);
const tick = integer("tick_size_atoms", tick_size_atoms, true);
const scale = integer("price_scale", price_scale, true);
const policyId = text("effective_policy_id", effective_policy_id);
if (tick > price) throw new Error("tick_size_atoms cannot exceed price_atoms");
const quotient = Math.floor(price / tick);
const remainder = price % tick;
const lower = quotient * tick;
const upper = remainder === 0 ? lower : lower + tick;
const valid = remainder === 0;
return {
effective_policy_id: policyId,
price_atoms: price,
tick_size_atoms: tick,
price_scale: scale,
valid,
remainder_atoms: remainder,
lower_valid_price_atoms: lower,
upper_valid_price_atoms: upper,
distance_to_lower_atoms: price - lower,
distance_to_upper_atoms: upper - price,
reason: valid ? "on-grid" : "off-grid",
state: valid ? "accepted" : "rejected",
};
}
export function priceBandValidation(
side: unknown,
limit_price_atoms: unknown,
lower_band_atoms: unknown,
upper_band_atoms: unknown,
band_as_of_ns: unknown,
inclusive: unknown = true,
): Record<string, unknown> {
const orderSide = text("side", side).toLowerCase();
if (!["buy", "sell"].includes(orderSide)) throw new Error("side must be buy or sell");
const price = integer("limit_price_atoms", limit_price_atoms, true);
const lower = integer("lower_band_atoms", lower_band_atoms, true);
const upper = integer("upper_band_atoms", upper_band_atoms, true);
const asOf = integer("band_as_of_ns", band_as_of_ns, false, true);
const includeEdges = booleanValue("inclusive", inclusive);
if (lower >= upper) throw new Error("lower_band_atoms must be less than upper_band_atoms");
const valid = includeEdges ? lower <= price && price <= upper : lower < price && price < upper;
let reason = "inside-band";
let violation = 0;
if (price < lower || (price === lower && !includeEdges)) {
reason = "below-lower-band"; violation = lower - price;
} else if (price > upper || (price === upper && !includeEdges)) {
reason = "above-upper-band"; violation = price - upper;
}
return {
side: orderSide, limit_price_atoms: price, lower_band_atoms: lower, upper_band_atoms: upper,
band_as_of_ns: asOf, inclusive: includeEdges, valid,
distance_from_lower_atoms: price - lower, distance_to_upper_atoms: upper - price,
violation_atoms: violation, reason, state: valid ? "accepted" : "rejected",
};
}
export function selfTradePrevention(incoming_order: unknown, resting_orders: unknown, mode: unknown): Record<string, unknown> {
if (!incoming_order || typeof incoming_order !== "object" || Array.isArray(incoming_order)) throw new Error("incoming_order must be an object");
if (!Array.isArray(resting_orders)) throw new Error("resting_orders must be an array");
const incoming = incoming_order as Inputs;
const selectedMode = text("mode", mode).toLowerCase();
if (!["cancel_newest", "cancel_oldest", "decrement_both"].includes(selectedMode)) throw new Error("unsupported self-trade-prevention mode");
const incomingId = text("incoming_order.order_id", incoming.order_id);
const side = text("incoming_order.side", incoming.side).toLowerCase();
if (!["buy", "sell"].includes(side)) throw new Error("incoming_order.side must be buy or sell");
const price = integer("incoming_order.price_atoms", incoming.price_atoms, true);
const quantity = integer("incoming_order.quantity", incoming.quantity, true);
const participant = text("incoming_order.participant_id", incoming.participant_id);
const group = text("incoming_order.stp_group", incoming.stp_group);
const seen = new Set<string>([incomingId]);
const parsed = resting_orders.map((raw: any, index: number) => {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`resting_orders[${index}] must be an object`);
const orderId = text(`resting_orders[${index}].order_id`, raw.order_id);
if (seen.has(orderId)) throw new Error("order identifiers must be unique");
seen.add(orderId);
const restingSide = text(`resting_orders[${index}].side`, raw.side).toLowerCase();
if (!["buy", "sell"].includes(restingSide) || restingSide === side) throw new Error("resting orders must be on the contra side");
return {
order_id: orderId, side: restingSide,
price_atoms: integer(`resting_orders[${index}].price_atoms`, raw.price_atoms, true),
quantity: integer(`resting_orders[${index}].quantity`, raw.quantity, true),
participant_id: text(`resting_orders[${index}].participant_id`, raw.participant_id),
stp_group: text(`resting_orders[${index}].stp_group`, raw.stp_group),
sequence: integer(`resting_orders[${index}].sequence`, raw.sequence, false, true),
};
});
parsed.sort((a, b) => side === "buy" ? a.price_atoms - b.price_atoms || a.sequence - b.sequence : b.price_atoms - a.price_atoms || a.sequence - b.sequence);
let remaining = quantity;
let externalExecuted = 0, prevented = 0, canceledIncoming = 0, canceledResting = 0;
let stopped = false;
const events: any[] = [], finalBook: any[] = [];
for (const original of parsed) {
const resting = { ...original };
const marketable = side === "buy" ? resting.price_atoms <= price : resting.price_atoms >= price;
if (stopped || remaining === 0 || !marketable) { finalBook.push(resting); continue; }
const sameGroup = resting.participant_id === participant && resting.stp_group === group;
const matchQty = Math.min(remaining, resting.quantity);
if (sameGroup) {
if (selectedMode === "cancel_newest") {
canceledIncoming = remaining; prevented += matchQty;
events.push({ action: "cancel-incoming", incoming_order_id: incomingId, resting_order_id: resting.order_id, prevented_quantity: matchQty });
remaining = 0; stopped = true; finalBook.push(resting);
} else if (selectedMode === "cancel_oldest") {
canceledResting += resting.quantity; prevented += matchQty;
events.push({ action: "cancel-resting", incoming_order_id: incomingId, resting_order_id: resting.order_id, prevented_quantity: matchQty });
} else {
resting.quantity -= matchQty; remaining -= matchQty; prevented += matchQty;
events.push({ action: "decrement-both", incoming_order_id: incomingId, resting_order_id: resting.order_id, prevented_quantity: matchQty });
if (resting.quantity > 0) finalBook.push(resting);
}
} else {
resting.quantity -= matchQty; remaining -= matchQty; externalExecuted += matchQty;
events.push({ action: "execute-external", incoming_order_id: incomingId, resting_order_id: resting.order_id, quantity: matchQty, price_atoms: resting.price_atoms });
if (resting.quantity > 0) finalBook.push(resting);
}
}
const state = prevented ? "self-trade-prevented" : remaining === 0 ? "externally-filled" : "resting-or-residual";
return {
mode: selectedMode, incoming_order_id: incomingId, original_incoming_quantity: quantity,
external_executed_quantity: externalExecuted, prevented_self_quantity: prevented,
canceled_incoming_quantity: canceledIncoming, canceled_resting_quantity: canceledResting,
remaining_incoming_quantity: remaining, events, resting_orders: finalBook, state,
};
}
export function cancelOnDisconnect(
disconnected_session_id: unknown,
disconnect_type: unknown,
trigger_disconnect_types: unknown,
policy: unknown,
orders: unknown,
): Record<string, unknown> {
const session = text("disconnected_session_id", disconnected_session_id);
const eventType = text("disconnect_type", disconnect_type).toLowerCase();
if (!Array.isArray(trigger_disconnect_types) || !trigger_disconnect_types.length) throw new Error("trigger_disconnect_types must be a nonempty array");
const triggers = trigger_disconnect_types.map((item) => text("trigger_disconnect_type", item).toLowerCase());
const selectedPolicy = text("policy", policy).toLowerCase();
if (!["cancel_all", "cancel_continuous", "keep_gtc"].includes(selectedPolicy)) throw new Error("unsupported cancel-on-disconnect policy");
if (!Array.isArray(orders)) throw new Error("orders must be an array");
const seen = new Set<string>();
const parsed = orders.map((raw: any, index: number) => {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`orders[${index}] must be an object`);
const orderId = text(`orders[${index}].order_id`, raw.order_id);
if (seen.has(orderId)) throw new Error("order identifiers must be unique");
seen.add(orderId);
const book = text(`orders[${index}].book`, raw.book).toLowerCase();
if (!["continuous", "auction"].includes(book)) throw new Error("book must be continuous or auction");
return {
order_id: orderId, session_id: text(`orders[${index}].session_id`, raw.session_id), book,
time_in_force: text(`orders[${index}].time_in_force`, raw.time_in_force).toUpperCase(),
quantity: integer(`orders[${index}].quantity`, raw.quantity, true),
};
});
const triggered = triggers.includes(eventType);
const canceled: string[] = [], retained: Array<{ order_id: string; reason: string }> = [];
for (const order of parsed) {
if (order.session_id !== session) { retained.push({ order_id: order.order_id, reason: "different-session" }); continue; }
if (!triggered) { retained.push({ order_id: order.order_id, reason: "disconnect-type-not-configured" }); continue; }
const shouldCancel = selectedPolicy === "cancel_all"
|| (selectedPolicy === "cancel_continuous" && order.book === "continuous")
|| (selectedPolicy === "keep_gtc" && order.time_in_force !== "GTC");
if (shouldCancel) canceled.push(order.order_id);
else retained.push({ order_id: order.order_id, reason: order.book === "auction" ? "auction-order-retained" : "gtc-retained" });
}
return {
disconnected_session_id: session, disconnect_type: eventType, triggered, policy: selectedPolicy,
canceled_order_ids: canceled, retained_orders: retained, canceled_count: canceled.length,
retained_count: retained.length, state: triggered ? "purge-applied" : "no-purge",
};
}
export function fatFingerLimit(
side: unknown,
limit_price_atoms: unknown,
reference_price_atoms: unknown,
quantity: unknown,
max_quantity: unknown,
max_notional_atoms: unknown,
max_aggressive_deviation_bps: unknown,
): Record<string, unknown> {
const orderSide = text("side", side).toLowerCase();
if (!["buy", "sell"].includes(orderSide)) throw new Error("side must be buy or sell");
const price = integer("limit_price_atoms", limit_price_atoms, true);
const reference = integer("reference_price_atoms", reference_price_atoms, true);
const qty = integer("quantity", quantity, true);
const maxQty = integer("max_quantity", max_quantity, true);
const maxNotional = integer("max_notional_atoms", max_notional_atoms, true);
const maxDeviation = integer("max_aggressive_deviation_bps", max_aggressive_deviation_bps, false, true);
const notional = price * qty;
const signed = orderSide === "buy" ? price - reference : reference - price;
const aggressiveDeviationBps = Math.max(0, Math.round((signed * 10000 / reference) * 1e6) / 1e6);
const checks = {
quantity: { value: qty, limit: maxQty, passed: qty <= maxQty },
notional: { value: notional, limit: maxNotional, passed: notional <= maxNotional },
aggressive_deviation_bps: { value: aggressiveDeviationBps, limit: maxDeviation, passed: aggressiveDeviationBps <= maxDeviation },
};
const violations = Object.entries(checks).filter(([, check]) => !check.passed).map(([name]) => name);
return {
side: orderSide, limit_price_atoms: price, reference_price_atoms: reference, quantity: qty,
order_notional_atoms: notional, aggressive_deviation_bps: aggressiveDeviationBps,
checks, violations, valid: violations.length === 0,
reason: violations.length ? `limit-breached:${violations.join(",")}` : "within-configured-limits",
state: violations.length ? "rejected" : "accepted",
};
}
export function circuitBreakerTrigger(
reference_close_atoms: unknown,
current_index_atoms: unknown,
level_thresholds_bps: unknown,
previously_triggered_level: unknown = 0,
): Record<string, unknown> {
const reference = integer("reference_close_atoms", reference_close_atoms, true);
const current = integer("current_index_atoms", current_index_atoms, true);
const previous = integer("previously_triggered_level", previously_triggered_level, false, true);
if (previous > 3) throw new Error("previously_triggered_level cannot exceed 3");
if (!Array.isArray(level_thresholds_bps) || level_thresholds_bps.length !== 3) throw new Error("level_thresholds_bps must contain exactly three thresholds");
const thresholds = level_thresholds_bps.map((value) => integer("threshold_bps", value, true));
if (new Set(thresholds).size !== 3 || thresholds.some((value, index) => index > 0 && value <= thresholds[index - 1])) throw new Error("thresholds must be strictly increasing");
const declineBps = Math.max(0, Math.round(((reference - current) * 10000 / reference) * 1e6) / 1e6);
let reached = 0;
thresholds.forEach((threshold, index) => { if (declineBps >= threshold) reached = index + 1; });
const newlyTriggered = reached > previous;
const triggerLevel = newlyTriggered ? reached : 0;
const actions = ["continue", "level-1-halt", "level-2-halt", "level-3-close"];
const nextThreshold = reached < thresholds.length ? thresholds[reached] : null;
const distance = nextThreshold === null ? null : Math.max(0, Math.round((nextThreshold - declineBps) * 1e6) / 1e6);
return {
reference_close_atoms: reference, current_index_atoms: current, decline_bps: declineBps,
level_thresholds_bps: thresholds, previously_triggered_level: previous,
reached_level: reached, newly_triggered_level: triggerLevel, newly_triggered: newlyTriggered,
action: actions[triggerLevel], next_threshold_bps: nextThreshold,
distance_to_next_threshold_bps: distance,
state: newlyTriggered ? "triggered" : reached ? "already-triggered" : "normal",
};
}
export function calculate(topicId: string, inputs: Inputs): Record<string, unknown> {
if (!inputs || typeof inputs !== "object" || Array.isArray(inputs)) throw new Error("inputs must be an object");
if (topicId === "D12-F03-A01") return tickSizeValidation(inputs.price_atoms, inputs.tick_size_atoms, inputs.price_scale, inputs.effective_policy_id);
if (topicId === "D12-F03-A02") return priceBandValidation(inputs.side, inputs.limit_price_atoms, inputs.lower_band_atoms, inputs.upper_band_atoms, inputs.band_as_of_ns, inputs.inclusive);
if (topicId === "D12-F03-A03") return selfTradePrevention(inputs.incoming_order, inputs.resting_orders, inputs.mode);
if (topicId === "D12-F03-A04") return cancelOnDisconnect(inputs.disconnected_session_id, inputs.disconnect_type, inputs.trigger_disconnect_types, inputs.policy, inputs.orders);
if (topicId === "D12-F03-A05") return fatFingerLimit(inputs.side, inputs.limit_price_atoms, inputs.reference_price_atoms, inputs.quantity, inputs.max_quantity, inputs.max_notional_atoms, inputs.max_aggressive_deviation_bps);
if (topicId === "D12-F03-A06") return circuitBreakerTrigger(inputs.reference_close_atoms, inputs.current_index_atoms, inputs.level_thresholds_bps, inputs.previously_triggered_level);
throw new Error(`unsupported topic_id: ${topicId}`);
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.