D12-F02-A03 / Complete engineering topic

Opening-Cross Price

A production-minded guide to Opening-Cross Price.

D12 · MATCHING ENGINES AND VENUE LO…
D12-F02-A03Canonical / Tested / Open
D12 / D12-F02

Build an opening cross that exposes eligibility, price protection, objective order, and no-cross behavior.

The decision this tutorial makes visible

The opening consolidates overnight information and establishes a session price. Treating it as an unrestricted generic auction ignores session order types, validation collars, and the role of the previous close.

The precise question is: What opening price follows from eligible on-open interest, a previous-close reference, a collar, and the declared objective hierarchy?

A practitioner needs to know what the diagnostic does and does not justify. A builder needs a contract that can be reproduced from the same point-in-time inputs in Python, TypeScript, a visual, and a browser lab.

Intuition before notation

The opening price is not simply the midpoint or last order price. It is the final survivor of an eligibility filter, price collar, and ordered auction objectives.

The result depends on the declared algorithm scope, input clocks, units, equality and rounding policies, and unsupported-state treatment. Change one of those and the output represents a different decision even when its field name is unchanged.

Scope and nearby methods

The canonical cash-opening model admits MOO, LOO, and DAY orders, restricts candidates to a declared collar containing the previous close, then applies maximum volume, minimum imbalance, directional surplus, and reference proximity.

VariantDefinitionBest useMain limitation
Canonical declared opening crossMOO/LOO/DAY, collar, ordered objectivesReproducible teaching systemNot venue-conformant
Nasdaq Opening CrossCurrent Nasdaq eligibility, NOII, validation, and rule hierarchyNasdaq-listed securitiesMore branches and protections
Xetra Opening AuctionCall with market-model auction price determinationXetra cash marketDifferent order and reference conventions

What is sourced, selected, synthetic, and derived

RoleMaterial claimEvidenceBoundary
Sourced factNasdaq rules and operations pages define opening-cross eligibility, imbalance dissemination, ordered price selection, and validation branches; Xetra supplies a distinct venue comparison.S1 (Nasdaq Equity 4), S2 (Nasdaq cross workflow), S3 (system settings), and S4 (T7 comparison); S5 records current T7 release applicabilityVenue rulebooks do not validate the repository-authored fixture or selected simplifications.
Implementation choiceThe canonical cash-opening model admits MOO, LOO, and DAY orders, restricts candidates to a declared collar containing the previous close, then applies maximum volume, minimum imbalance, directional surplus, and reference proximity.Frozen package definitionThis is a declared teaching convention rather than universal or venue-conformant logic.
Synthetic teaching inputAll orders, references, collars, bands, and quantities are repository-authored.datasets/canonical-input.jsonThey are not observed imbalance messages or participant orders.
Author-derived calculationThe synthetic opening executes 650 shares at 100. Prices 99 and 101 execute 350 and 600, so the objective hierarchy selects 100 inside the 95–105 collar.Formula, fixture, independent arithmetic, and Python/TypeScript parityCorrect calculation does not predict a future venue cross.

The authoritative sources support only the exact facts named in the claim ledger. They do not certify the synthetic numbers in this tutorial. The repository fixture is deliberately invented for auditability, and the displayed output is author-derived under the selected implementation choice.

Formula, symbols, and numerical policy

Plain text
select lexicographically: max V -> min |I| -> buy surplus highest / sell surplus lowest -> nearest previous close
SymbolMeaningUnitPolicy
P_0previous-close referencepriceinside collar
[L,U]opening price collarprice intervalinclusive and tick aligned
V(p)opening executable volumeshares/contractsmaximize first
I(p)signed opening imbalanceshares/contractsminimum absolute value second
  • Candidate prices are unique tick-aligned limit prices plus the declared reference.
  • Buy demand includes market orders and buy limits greater than or equal to candidate price.
  • Sell supply includes market orders and sell limits less than or equal to candidate price.
  • Equality at a limit or collar is included.

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 MOO, LOO, and DAY eligibility
  2. Build tick candidates inside the opening collar
  3. Maximize volume and minimize imbalance
  4. Apply directional surplus or previous-close proximity
  5. Publish price, volume, imbalance, and trace

Production-minded operational checklist

  1. Resolve venue, security, auction session, timezone, and effective rule version.
  2. Freeze eligible interest, tick table, references, collars, and market-control state.
  3. Evaluate every protected candidate from one end-of-call snapshot.
  4. Apply objectives lexicographically and retain every survivor at each stage.
  5. Persist imbalance, selection trace, no-cross reason, and publication provenance.

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, selected_price, is 100.0. The complete input and output are in datasets/canonical-input.json and datasets/expected-output.json.

The synthetic opening executes 650 shares at 100. Prices 99 and 101 execute 350 and 600, so the objective hierarchy selects 100 inside the 95–105 collar.

Counterfactual checkpoint

Opening pressure moves the protected winner. Increase synthetic market-on-open buy quantity while keeping eligibility and the collar fixed. The output changes because eligibility and collar protection occur before the lexicographic auction objectives.

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 session crossStep 18 · canonical fixtureMarket-on-session pressure moves through the protected candidate set.crossed100.00volume 650.0 · imbalance 250.02
Buy-imbalance extremeStep 30 · comparison focusAligned buy surplus exposes the highest-price tie-break.crossed101.00volume 1150.0 · imbalance 50.03
Sell-imbalance extremeStep 30 · comparison focusAligned sell surplus exposes the lowest-price tie-break.crossed100.00volume 1000.0 · imbalance -400.03
Reference-proximity tieStep 30 · comparison focusMixed or zero surplus reaches the declared session reference.crossed101.00volume 700.0 · imbalance -450.02
Collar equalityStep 30 · comparison focusThe winning candidate lands exactly on an inclusive collar endpoint.crossed101.00volume 700.0 · imbalance -450.02
Narrow-collar protectionStep 30 · comparison focusA valid book loses out-of-collar candidates before ranking.crossed101.00volume 700.0 · imbalance -450.02
No-cross sessionStep 30 · comparison focusEligible orders exist but no candidate executes both sides.no-crossno crossvolume 0.0 · imbalance 0.01

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

Opening-Cross Price 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 begin with the same validation contract, reject malformed and unsupported state before calculation, preserve declared ordering and rounding policies, and return structured diagnostics rather than one context-free number.

The main implementation branches are:

  • Order type is not MOO, LOO, or DAY — Reject package input, because Session eligibility is part of the contract.
  • Candidate lies outside collar — Exclude it, because Opening price protection precedes objective ranking.
  • Survivors have mixed or zero imbalance — Use nearest previous close, because Reference resolves the remaining ambiguity.

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:

  • Executable quantity equals min(cumulative buy, cumulative sell) at each candidate.
  • Imbalance equals cumulative buy minus cumulative sell.
  • Every later objective is applied only to survivors of every earlier objective.
  • A no-cross state never publishes a fabricated clearing price.

Passing definition and parity checks proves that the implementation matches the selected contract. It does not prove production performance, universal applicability, or a later market outcome.

Failure modes and misuse

  • Auction objective order and eligible interest vary by venue, session, security, and rule version.
  • A candidate price from an incomplete public book is not a venue-conformant auction prediction.
  • Definition fidelity does not establish execution probability, liquidity quality, or trading value.

Debugging order

When a result looks surprising, inspect the state in this order:

  1. Confirm identifiers, scope, side, and decision clock.
  2. Confirm units, ordering, and point-in-time inputs.
  3. Confirm equality, rounding, null, and reset policies.
  4. Recalculate the invariant and declared scenario focus before changing code.

Evidence and historical boundary

Historical decision: deferred. A named auction reconstruction would require a sequence-complete licensed imbalance and order feed, exact session and order eligibility, reference and collar history, regulatory state, corrections, timestamps, and redistribution rights. Synthetic books expose the objective hierarchy without inventing unavailable auction interest.

The primary sources are Nasdaq Equity 4, Nasdaq opening and closing crosses, Nasdaq system settings, T7 Release 14 Functional Reference, Xetra Release 14.1 production notice. They support the source roles listed in the research ledger, not a redistributable historical observation, a private participant decision, production conformance certification, execution-quality result, profitability claim, or prediction claim.

Summary and next topic

You can now build a protected opening cross with an auditable selection trace. The learning flow is: Minimum-Imbalance Tie-Break → Opening-Cross Price → Closing-Cross Price. Carry the result forward only with its scope, clock, state, and evidence label.

Opening-Cross Price calculation flow

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

Rendering system map…

Takeaway: Eligibility and collar validation occur before the auction objectives choose a price.

ReferencesPrimary sources and evidence notes

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

S1 — Nasdaq Equity 4 Trading Rules

  • Organization or authors: The Nasdaq Stock Market LLC
  • Source type: Official exchange rulebook
  • Publication or effective date: Current rulebook accessed 2026-07-30
  • Version: Current online rule text
  • URL or DOI: https://listingcenter.nasdaq.com/rulebook/nasdaq/rules/Nasdaq%20Equity%204
  • Accessed: 2026-07-30
  • Jurisdiction: United States equities; Nasdaq
  • Supports: Current Reference Price, maximum pairing, minimum imbalance, opening and closing eligible interest, and cross processing.
  • Limitations: The rules contain session-, security-, order-type-, LULD-, and exception-specific branches not reproduced by the package.

S2 — The Nasdaq Opening and Closing Crosses

  • Organization or authors: Nasdaq Trader
  • Source type: Official exchange market-operations page
  • Publication or effective date: Current page accessed 2026-07-30
  • Version: Current online version
  • URL or DOI: https://www.nasdaqtrader.com/Trader.aspx?id=OpenClose
  • Accessed: 2026-07-30
  • Jurisdiction: United States equities; Nasdaq
  • Supports: Cross timing, imbalance dissemination, security eligibility, and on-open/on-close order categories.
  • Limitations: The page is an operational overview and must be read with the rulebook.

S3 — The Nasdaq Stock Market System Settings

  • Organization or authors: Nasdaq Trader
  • Source type: Official exchange settings page
  • Publication or effective date: Current page accessed 2026-07-30
  • Version: Current online version
  • URL or DOI: https://www.nasdaqtrader.com/Trader.aspx?id=SYSTEMSETTINGS
  • Accessed: 2026-07-30
  • Jurisdiction: United States equities; Nasdaq
  • Supports: Published opening/closing threshold and price-validation parameters.
  • Limitations: Settings change and do not define this package's synthetic collar.

S4 — T7 Release 14.0 Functional Reference, Version 3

  • Organization or authors: Deutsche Börse Group
  • Source type: Official trading-system functional reference
  • Publication or effective date: Release 14.0 documentation accessed 2026-07-30
  • Version: Version 3
  • URL or DOI: https://www.xetra.com/resource/blob/4591804/afb8a01ba7c00951ad48f8196d9f3732/data/T7_Release_14.0_-_Functional_Reference_Version_3.pdf
  • Accessed: 2026-07-30
  • Jurisdiction: Deutsche Börse T7 cash and derivatives markets
  • Supports: Cash-auction most-executable-volume, lowest-surplus, directional surplus, reference-price, and volatility-interruption mechanics.
  • Limitations: Market and product configuration determine applicable behavior; the package is not a T7 conformance implementation.

S5 — Introduction of T7 Release 14.1

Evidence boundary

The sources establish the exact rule, interface, protocol, or research context named above. They do not verify the repository-authored synthetic fixture, thresholds, empirical usefulness, execution probability, or profitability. Package-selected choices remain labeled as implementation choices wherever they are used.

auction-matching.ts
export type JsonObject = Record<string, any>;

function num(name: string, value: unknown, positive = false, nonnegative = false): number {
  if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${name} must be a finite number`);
  if (positive && value <= 0) throw new RangeError(`${name} must be positive`);
  if (nonnegative && value < 0) throw new RangeError(`${name} must be nonnegative`);
  return value;
}
function integer(name: string, value: unknown, nonnegative = false): number {
  const result = num(name, value);
  if (!Number.isInteger(result)) throw new RangeError(`${name} must be an integer`);
  if (nonnegative && result < 0) throw new RangeError(`${name} must be nonnegative`);
  return result;
}
function text(name: string, value: unknown): string {
  if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be a non-empty string`);
  return value;
}
function onTick(value: number, tick: number): boolean {
  return Math.abs(value / tick - Math.round(value / tick)) <= 1e-8;
}
function parseOrders(raw: unknown, tick: number): JsonObject[] {
  if (!Array.isArray(raw) || raw.length === 0) throw new RangeError("orders must be a non-empty list");
  const ids = new Set<string>(), sequences = new Set<number>();
  return raw.map((item, index) => {
    if (!item || typeof item !== "object") throw new TypeError(`orders[${index}] must be an object`);
    const order = item as JsonObject, orderId = text(`orders[${index}].order_id`, order.order_id);
    if (ids.has(orderId)) throw new RangeError("order_id values must be unique"); ids.add(orderId);
    const side = text(`orders[${index}].side`, order.side);
    if (!["buy", "sell"].includes(side)) throw new RangeError("order side must be buy or sell");
    const orderType = text(`orders[${index}].order_type`, order.order_type);
    const sequence = integer(`orders[${index}].arrival_sequence`, order.arrival_sequence, true);
    if (sequences.has(sequence)) throw new RangeError("arrival_sequence values must be unique"); sequences.add(sequence);
    const quantity = num(`orders[${index}].quantity`, order.quantity, true);
    let price: number | null = null;
    if (["market", "MOO", "MOC"].includes(orderType)) {
      if (order.price != null) throw new RangeError("market-style auction orders must not carry a price");
    } else if (["limit", "LOO", "LOC", "DAY"].includes(orderType)) {
      price = num(`orders[${index}].price`, order.price, true);
      if (!onTick(price, tick)) throw new RangeError("order price must align with tick_size");
    } else throw new RangeError(`unsupported auction order_type: ${orderType}`);
    return {order_id: orderId, side, order_type: orderType, price, quantity, arrival_sequence: sequence};
  });
}
function evaluationAt(price: number, orders: JsonObject[]): JsonObject {
  const buy = orders.filter(order => order.side === "buy" && (order.price == null || order.price >= price)).reduce((sum, order) => sum + order.quantity, 0);
  const sell = orders.filter(order => order.side === "sell" && (order.price == null || order.price <= price)).reduce((sum, order) => sum + order.quantity, 0);
  const executable = Math.min(buy, sell), imbalance = buy - sell;
  return {price, buy_quantity: buy, sell_quantity: sell, executable_quantity: executable, imbalance_quantity: imbalance, absolute_imbalance: Math.abs(imbalance), imbalance_side: imbalance > 0 ? "buy" : imbalance < 0 ? "sell" : "none"};
}
function evaluateBook(ordersRaw: unknown, referenceRaw: unknown, tickRaw: unknown, low: number | null = null, high: number | null = null): [JsonObject[], number, number, JsonObject[]] {
  const reference = num("reference_price", referenceRaw, true), tick = num("tick_size", tickRaw, true);
  if (!onTick(reference, tick)) throw new RangeError("reference_price must align with tick_size");
  const orders = parseOrders(ordersRaw, tick), priceSet = new Set<number>([reference]);
  orders.forEach(order => { if (order.price != null) priceSet.add(order.price); });
  const candidates = [...priceSet].filter(price => (low == null || price >= low - 1e-12) && (high == null || price <= high + 1e-12)).sort((a, b) => a - b);
  if (!candidates.length) throw new RangeError("no candidate price remains inside the declared collar");
  return [orders, reference, tick, candidates.map(price => evaluationAt(price, orders))];
}

export function maximumExecutableVolumeAuction(ordersRaw: unknown, referenceRaw: unknown, tickRaw: unknown): JsonObject {
  const [orders, reference, tick, evaluations] = evaluateBook(ordersRaw, referenceRaw, tickRaw);
  const maximum = Math.max(...evaluations.map(row => row.executable_quantity)), winners = evaluations.filter(row => row.executable_quantity === maximum);
  return {model: "maximum-executable-volume-candidate-set", reference_price: reference, tick_size: tick, order_count: orders.length, maximum_executable_quantity: maximum, maximum_volume_prices: winners.map(row => row.price), unique_price: winners.length === 1 ? winners[0].price : null, candidate_evaluations: evaluations, state: maximum === 0 ? "no-cross" : winners.length === 1 ? "unique-maximum" : "maximum-volume-tie"};
}

export function minimumImbalanceTieBreak(ordersRaw: unknown, referenceRaw: unknown, tickRaw: unknown): JsonObject {
  const [orders, reference, tick, evaluations] = evaluateBook(ordersRaw, referenceRaw, tickRaw);
  const maximum = Math.max(...evaluations.map(row => row.executable_quantity));
  const volumeWinners = evaluations.filter(row => row.executable_quantity === maximum);
  const minimum = Math.min(...volumeWinners.map(row => row.absolute_imbalance));
  const winners = volumeWinners.filter(row => row.absolute_imbalance === minimum);
  return {model: "maximum-volume-then-minimum-imbalance", reference_price: reference, tick_size: tick, order_count: orders.length, maximum_executable_quantity: maximum, minimum_absolute_imbalance: minimum, volume_winner_prices: volumeWinners.map(row => row.price), minimum_imbalance_prices: winners.map(row => row.price), selected_price: winners.length === 1 ? winners[0].price : null, candidate_evaluations: evaluations, state: maximum === 0 ? "no-cross" : winners.length === 1 ? "tie-resolved" : "tie-remains"};
}

function crossPrice(session: string, ordersRaw: unknown, referenceRaw: unknown, tickRaw: unknown, lowRaw: unknown, highRaw: unknown): JsonObject {
  const reference = num("reference_price", referenceRaw, true), tick = num("tick_size", tickRaw, true), low = num("collar_low", lowRaw, true), high = num("collar_high", highRaw, true);
  if (low > reference || high < reference || low > high) throw new RangeError("collar must contain the reference price");
  if (![reference, low, high].every(value => onTick(value, tick))) throw new RangeError("reference and collar prices must align with tick_size");
  const parsed = parseOrders(ordersRaw, tick), allowed = new Set(session === "opening" ? ["MOO", "LOO", "DAY"] : ["MOC", "LOC", "DAY"]);
  if (parsed.some(order => !allowed.has(order.order_type))) throw new RangeError(`unsupported order type for ${session} cross`);
  const [, , , evaluations] = evaluateBook(parsed, reference, tick, low, high);
  const maximum = Math.max(...evaluations.map(row => row.executable_quantity));
  if (maximum === 0) return {model: `declared-${session}-cross`, session, reference_price: reference, collar_low: low, collar_high: high, selected_price: null, executed_quantity: 0, imbalance_quantity: 0, selection_trace: ["no candidate price executes both sides"], candidate_evaluations: evaluations, state: "no-cross"};
  let stage = evaluations.filter(row => row.executable_quantity === maximum);
  const trace = [`maximize executable quantity at ${maximum}`];
  const minimum = Math.min(...stage.map(row => row.absolute_imbalance));
  stage = stage.filter(row => row.absolute_imbalance === minimum); trace.push(`minimize absolute imbalance at ${minimum}`);
  let selected: JsonObject;
  if (stage.length > 1) {
    const signs = new Set(stage.map(row => row.imbalance_side));
    if (signs.size === 1 && signs.has("buy")) {
      selected = stage.reduce((a, b) => a.price > b.price ? a : b); trace.push("buy surplus selects the highest remaining price");
    } else if (signs.size === 1 && signs.has("sell")) {
      selected = stage.reduce((a, b) => a.price < b.price ? a : b); trace.push("sell surplus selects the lowest remaining price");
    } else {
      selected = [...stage].sort((a, b) => Math.abs(a.price - reference) - Math.abs(b.price - reference) || a.price - b.price)[0];
      trace.push("mixed or zero surplus selects the price nearest the reference");
    }
  } else { selected = stage[0]; trace.push("the objective hierarchy leaves one price"); }
  return {model: `declared-${session}-cross`, session, reference_price: reference, collar_low: low, collar_high: high, eligible_order_count: parsed.length, selected_price: selected.price, executed_quantity: selected.executable_quantity, buy_quantity: selected.buy_quantity, sell_quantity: selected.sell_quantity, imbalance_quantity: selected.imbalance_quantity, imbalance_side: selected.imbalance_side, selection_trace: trace, candidate_evaluations: evaluations, state: "crossed"};
}

export function openingCrossPrice(orders: unknown, previousClose: unknown, tick: unknown, low: unknown, high: unknown): JsonObject {
  return crossPrice("opening", orders, previousClose, tick, low, high);
}
export function closingCrossPrice(orders: unknown, reference: unknown, tick: unknown, low: unknown, high: unknown): JsonObject {
  return crossPrice("closing", orders, reference, tick, low, high);
}

export function volatilityAuctionReopening(inputs: JsonObject): JsonObject {
  const reference = num("reference_price", inputs.reference_price, true);
  const dynamicRef = num("dynamic_reference", inputs.dynamic_reference, true), dynamicPct = num("dynamic_band_pct", inputs.dynamic_band_pct, true);
  const staticRef = num("static_reference", inputs.static_reference, true), staticPct = num("static_band_pct", inputs.static_band_pct, true);
  const potential = num("potential_continuous_price", inputs.potential_continuous_price, true), indicative = num("indicative_auction_price", inputs.indicative_auction_price, true);
  const multiplier = num("extended_band_multiplier", inputs.extended_band_multiplier, true);
  if (multiplier < 1) throw new RangeError("extended_band_multiplier must be at least one");
  const executable = num("executable_quantity", inputs.executable_quantity, false, true);
  const dynamicLow = dynamicRef * (1 - dynamicPct), dynamicHigh = dynamicRef * (1 + dynamicPct);
  const staticLow = staticRef * (1 - staticPct), staticHigh = staticRef * (1 + staticPct);
  const dynamicBreach = potential < dynamicLow || potential > dynamicHigh, staticBreach = potential < staticLow || potential > staticHigh, triggered = dynamicBreach || staticBreach;
  const expandedDynamicLow = dynamicRef * (1 - dynamicPct * multiplier), expandedDynamicHigh = dynamicRef * (1 + dynamicPct * multiplier);
  const expandedStaticLow = staticRef * (1 - staticPct * multiplier), expandedStaticHigh = staticRef * (1 + staticPct * multiplier);
  const inside = indicative >= expandedDynamicLow && indicative <= expandedDynamicHigh && indicative >= expandedStaticLow && indicative <= expandedStaticHigh;
  let state: string, reopened: boolean, reopenPrice: number | null, reason: string;
  if (!triggered) { state = "continuous"; reopened = false; reopenPrice = null; reason = "potential price remains inside both active corridors"; }
  else if (executable === 0) { state = "auction-no-cross"; reopened = false; reopenPrice = null; reason = "volatility auction has no executable quantity"; }
  else if (inside) { state = "reopened"; reopened = true; reopenPrice = indicative; reason = "indicative auction price is executable and inside both expanded corridors"; }
  else { state = "extended-volatility-auction"; reopened = false; reopenPrice = null; reason = "indicative auction price remains outside an expanded corridor"; }
  return {model: "dual-corridor-volatility-auction-reopening", reference_price: reference, potential_continuous_price: potential, dynamic_corridor: [dynamicLow, dynamicHigh], static_corridor: [staticLow, staticHigh], dynamic_breach: dynamicBreach, static_breach: staticBreach, triggered, indicative_auction_price: indicative, executable_quantity: executable, expanded_dynamic_corridor: [expandedDynamicLow, expandedDynamicHigh], expanded_static_corridor: [expandedStaticLow, expandedStaticHigh], inside_reopening_corridor: inside, reopened, reopen_price: reopenPrice, reason, state};
}

export function calculate(topicId: string, inputs: JsonObject): JsonObject {
  if (!inputs || typeof inputs !== "object" || Array.isArray(inputs)) throw new TypeError("inputs must be an object");
  if (topicId === "D12-F02-A01") return maximumExecutableVolumeAuction(inputs.orders, inputs.reference_price, inputs.tick_size);
  if (topicId === "D12-F02-A02") return minimumImbalanceTieBreak(inputs.orders, inputs.reference_price, inputs.tick_size);
  if (topicId === "D12-F02-A03") return openingCrossPrice(inputs.orders, inputs.previous_close, inputs.tick_size, inputs.collar_low, inputs.collar_high);
  if (topicId === "D12-F02-A04") return closingCrossPrice(inputs.orders, inputs.closing_reference, inputs.tick_size, inputs.collar_low, inputs.collar_high);
  if (topicId === "D12-F02-A05") return volatilityAuctionReopening(inputs);
  throw new RangeError(`unsupported topic_id: ${topicId}`);
}
Full-height labplaygroundOpen full screen