D12-F03-A06 / Complete engineering topic

Circuit-Breaker Trigger

A production-minded guide to Circuit-Breaker Trigger.

D12 · MATCHING ENGINES AND VENUE LO…
D12-F03-A06Canonical / Tested / Open
D12 / D12-F03

Classify exact decline thresholds without confusing a reached level with a new halt event.

The decision this tutorial makes visible

Circuit breakers are stateful: a threshold may be reached while its corresponding action has already been triggered.

The precise question is: Which configured market-wide decline level has been reached, and is it newly triggered relative to prior state?

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

A falling index crosses numbered floors. The state machine must remember which floors already produced an action.

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 classifier compares an integer current index with a prior-close reference and three increasing thresholds. It labels new versus already-triggered state but does not implement session-time halt durations.

VariantDefinitionBest useMain limitation
Configurable threshold classifierThree input thresholds plus prior stateDeterministic teaching coreNo session-time rules
Current U.S. MWCB operatorFull 7/13/20 policy with time rulesProduction market operatorBroader operational state
Single-security LULDDynamic per-security bandsIndividual-stock volatilityDifferent control

What is sourced, selected, synthetic, and derived

RoleMaterial claimEvidenceBoundary
Sourced factNYSE MWCB FAQ v4.0 confirms current S&P 500 prior-close thresholds of 7%, 13%, and 20%, with Level 1 and 2 limited to their time window and once per trading day, and Level 3 closing the market.S1, S2The package classifies configurable thresholds and remembered state only; it does not implement time-of-day eligibility, halt duration, reopening auctions, or coordinated order handling.
Implementation choiceThe canonical classifier compares an integer current index with a prior-close reference and three increasing thresholds. It labels new versus already-triggered state but does not implement session-time halt durations.Frozen package definitionProduction control hierarchies, exemptions, overrides, and account/venue rules remain outside scope unless explicitly named.
Synthetic teaching inputCanonical orders, prices, thresholds, sessions, and identifiers are repository-authored.datasets/canonical-input.json and scenario-results.jsonThey are not observed participant or venue records.
Author-derived calculationThe synthetic index falls from 4,000,000 to 3,480,000 atoms, exactly 1,300 bps. Level 2 is reached and is new because level 1 was previously triggered.Formula, canonical fixture, Python/TypeScript parity, and independent arithmeticCorrect 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

Plain text
decline_bps = max(0,(close-current)/close×10,000); new = reached_level > previous_level
SymbolMeaningUnitPolicy
I_0reference prior closeindex atomspositive
I_tcurrent index levelindex atomspositive
d_tdeclinebpsfloored at zero
L_prevhighest previously triggered levellevel0–3
  • Decline is reported to six decimal basis points.
  • Equality at a threshold counts as reached.
  • A rising index produces zero decline rather than a negative circuit-breaker value.

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

  1. Validate reference, observation, thresholds, and prior state
  2. Compute nonnegative decline
  3. Find the highest reached threshold
  4. Compare reached level with prior triggered level
  5. Return action and next threshold distance

Production-minded operational checklist

  1. Verify reference index and prior close
  2. Load effective thresholds and time rules
  3. Update state monotonically
  4. Emit new actions exactly once
  5. Delegate halt duration/reopen logic to the operator state machine

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, newly_triggered_level, is a 1,300 bps decline reaches Level 2 and emits a new Level 2 action because only Level 1 was remembered. The complete input and output are in datasets/canonical-input.json and datasets/expected-output.json.

The synthetic index falls from 4,000,000 to 3,480,000 atoms, exactly 1,300 bps. Level 2 is reached and is new because level 1 was previously triggered.

Counterfactual checkpoint

Level 2 was already processed. Keep the exact 13% observation but change the remembered level from 1 to 2. The output changes because Threshold reach is an observation; action emission is a state transition and must occur only once.

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.

ScenarioReview focusPurposeStatePrimary outputDiagnosticDecision segments
Canonical decline sweepStep 30 · canonical fixtureMove the synthetic index through the 7%, 13%, and 20% configured thresholds. Synthetic data; the effective policy remains explicit.triggered1300.0 bps declinereached L2; level-2-halt4
Exact Level-1 equalityStep 30 · comparison focusLand exactly at a 700-bps decline; equality counts as reached. Synthetic data; the effective policy remains explicit.triggered700.0 bps declinereached L1; level-1-halt1
Below-Level-1 controlStep 30 · comparison focusRemain below the first threshold and report distance to Level 1. Synthetic data; the effective policy remains explicit.normal300.0 bps declinereached L0; continue1
New Level-2 crossingStep 30 · comparison focusMove above Level 2 while Level 1 is the remembered prior action. Synthetic data; the effective policy remains explicit.triggered1600.0 bps declinereached L2; level-2-halt1
Level-1 already processedStep 30 · comparison focusReach Level 1 with Level 1 already remembered; do not emit twice. Synthetic data; the effective policy remains explicit.already-triggered1200.0 bps declinereached L1; continue4
Exact Level-2 already processedStep 30 · comparison focusHold a 1,300-bps decline with Level 2 already remembered. Synthetic data; the effective policy remains explicit.already-triggered1300.0 bps declinereached L2; continue1
Level-3 closeStep 30 · comparison focusCross the 20% threshold and emit the terminal Level-3 action. Synthetic data; the effective policy remains explicit.triggered2150.0 bps declinereached L3; level-3-close1

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

Circuit-Breaker Trigger annotated teaching map

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:

  • decline below level 1 — Continue, because No threshold reached.
  • reached level > previous — Emit mapped action, because New threshold crossing.
  • reached level ≤ previous — No new action, because Already processed.

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:

  • Thresholds are strictly increasing.
  • Reached level never decreases for a fixed observation.
  • An already-triggered level does not emit a new action.
  • The structured output retains newly_triggered_level, 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:

  1. Confirm instrument, venue/product, session, order type, side, and policy ID.
  2. Confirm integer price scale, quantity units, reference or band as-of time, and trigger clock.
  3. Confirm identity/ownership scope, mode, equality rule, threshold ordering, and remembered state.
  4. 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 SEC market-wide circuit breakers, NYSE MWCB FAQ v4.0. 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 classify stateful market-wide threshold crossings. The learning flow is: Fat-Finger Limit → Circuit-Breaker Trigger → Limit-Order Lifecycle State Machine. Carry the result forward only with its scope, clock, state, and evidence label.

Circuit-Breaker Trigger calculation flow

This flow identifies the selected calculation stages and the structured output.

Rendering system map…

Takeaway: Crossing a threshold and emitting a new action are different state questions.

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

S1 — SEC Approves Proposals to Address Extraordinary Volatility in Individual Stocks and Broader Stock Market

  • Organization or authors: U.S. Securities and Exchange Commission
  • Source type: Official regulator release
  • Publication or effective date: 2012-05-31
  • Version: Release 2012-107
  • URL or DOI: https://www.sec.gov/newsroom/press-releases/2012-2012-107htm
  • Accessed: 2026-07-30
  • Jurisdiction: United States listed equities
  • Supports: The 7%, 13%, and 20% decline thresholds measured from the prior closing reference.
  • Limitations: Current operating times, durations, reference-index governance, and exchange procedures must be verified separately.

S2 — Market-Wide Circuit Breakers FAQ

  • Organization or authors: New York Stock Exchange
  • Source type: Official exchange operating FAQ
  • Publication or effective date: 2026-02
  • Version: 4.0
  • URL or DOI: https://www.nyse.com/publicdocs/nyse/NYSE_MWCB_FAQ.pdf
  • Accessed: 2026-07-30
  • Jurisdiction: NYSE U.S. equities and options exchanges
  • Supports: The S&P 500 prior-close basis, 7%, 13%, and 20% levels, Level 1/2 time window and once-per-day behavior, and Level 3 close.
  • Limitations: The canonical classifier intentionally excludes session-time halt duration, reopening auctions, order handling, and exchange coordination.

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.

order-controls.ts
/* 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}`);
}
Full-height labplaygroundOpen full screen