Build a dual-corridor interruption and reopening state machine with explicit equality boundaries.
The decision this tutorial makes visible
A volatility interruption is a market-state transition, not a trade rejection alone. The trigger, call phase, expanded corridor, no-cross path, and reopening decision must agree.
The precise question is: When does a potential continuous price trigger a volatility auction, and when may an executable indicative auction price reopen trading?
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
Two reference corridors guard different horizons. A breach pauses continuous matching; an auction then needs both executable interest and a protected price before reopening.
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 Xetra-inspired teaching model triggers when a potential continuous price lies strictly outside either dynamic or static corridor. An executable indicative auction price reopens only when it lies inside both expanded corridors; otherwise the auction remains extended.
| Variant | Definition | Best use | Main limitation |
|---|---|---|---|
| Canonical dual-corridor reopening | Strict breach; executable price inside both expanded corridors | State-machine education | Simplifies venue operations |
| Xetra Single Volatility Interruption | Dynamic/static checks and potentially extended call | Applicable Xetra instruments | Manual and automatic termination details |
| Automated Corridor Expansion | Cascade of configured corridors | ACE-enabled products | Not a single multiplier |
What is sourced, selected, synthetic, and derived
| Role | Material claim | Evidence | Boundary |
|---|---|---|---|
| Sourced fact | T7 documents cash-market volatility interruptions and auction reopening context; 2026 Xetra circulars show that active release context, references, and ACE parameter classes can change. | S1-S2 (T7 mechanism), S3 (Release 14.1 production guard), and S4 (2026 ACE parameter change guard) | Venue rulebooks do not validate the repository-authored fixture or selected simplifications. |
| Implementation choice | The canonical Xetra-inspired teaching model triggers when a potential continuous price lies strictly outside either dynamic or static corridor. An executable indicative auction price reopens only when it lies inside both expanded corridors; otherwise the auction remains extended. | Frozen package definition | This is a declared teaching convention rather than universal or venue-conformant logic. |
| Synthetic teaching input | All orders, references, collars, bands, and quantities are repository-authored. | datasets/canonical-input.json | They are not observed imbalance messages or participant orders. |
| Author-derived calculation | The potential price 103 breaches the 98–102 dynamic corridor. With multiplier two, indicative price 101.5 lies inside both expanded corridors and 500 shares are executable, so the state is reopened at 101.5. | Formula, fixture, independent arithmetic, and Python/TypeScript parity | Correct 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
trigger=(p<L_d or p>U_d) or (p<L_s or p>U_s); reopen=trigger and E>0 and p_a in expanded dynamic AND static corridors
| Symbol | Meaning | Unit | Policy |
|---|---|---|---|
| [L_d,U_d] | active dynamic corridor | price interval | inclusive boundary |
| [L_s,U_s] | active static corridor | price interval | inclusive boundary |
| p_a | indicative auction price | price | tested only after trigger |
| m | extended-band multiplier | ratio | at least one |
- 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
- Validate references, percentages, multiplier, and quantity
- Test potential price against both active corridors
- Enter volatility-auction state on either strict breach
- Test indicative price against both expanded corridors
- Reopen, remain no-cross, or extend with a reason
Production-minded operational checklist
- Resolve venue, security, auction session, timezone, and effective rule version.
- Freeze eligible interest, tick table, references, collars, and market-control state.
- Evaluate every protected candidate from one end-of-call snapshot.
- Apply objectives lexicographically and retain every survivor at each stage.
- 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,
reopened, is true. The complete input and output
are in datasets/canonical-input.json and datasets/expected-output.json.
The potential price 103 breaches the 98–102 dynamic corridor. With multiplier two, indicative price 101.5 lies inside both expanded corridors and 500 shares are executable, so the state is reopened at 101.5.
Counterfactual checkpoint
Endpoint equality versus strict breach. Place the potential price exactly on an active corridor endpoint, then one tick outside it. The output changes because the package declares inclusive corridors and strict outside breach comparisons.
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 trigger and reopen | Step 30 · canonical fixture | Potential price breaches the active dynamic corridor; auction price reenters both expanded corridors. | reopened | 101.50 indicative | reopened · executable 500.0 | 3 |
| Inside-corridor control | Step 30 · comparison focus | Potential price stays inside both active corridors and continuous trading continues. | continuous | 101.50 indicative | continuous · executable 500.0 | 1 |
| Dynamic-boundary equality | Step 30 · comparison focus | Potential price lands exactly on the inclusive dynamic endpoint. | continuous | 101.50 indicative | continuous · executable 500.0 | 1 |
| Static-only breach | Step 30 · comparison focus | The dynamic test passes while the static corridor triggers interruption. | continuous | 101.50 indicative | continuous · executable 500.0 | 3 |
| Auction no-cross | Step 30 · comparison focus | A valid trigger has zero executable auction quantity. | auction-no-cross | 101.50 indicative | auction-no-cross · executable 0.0 | 3 |
| Extended-auction failure | Step 30 · comparison focus | Indicative price remains outside one expanded corridor. | extended-volatility-auction | 105.50 indicative | extended-volatility-auction · executable 500.0 | 3 |
| Multiplier sensitivity | Step 30 · comparison focus | Expanded-corridor width changes the reopening decision. | reopened | 104.00 indicative | reopened · executable 500.0 | 4 |
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 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:
- Potential price equals a corridor endpoint — Remain inside, because Equality is the declared boundary.
- Trigger occurs but executable quantity is zero — Auction-no-cross, because A protected price alone cannot reopen.
- Indicative price fails either expanded corridor — Extend auction, because Both protections must pass.
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:
- Confirm identifiers, scope, side, and decision clock.
- Confirm units, ordering, and point-in-time inputs.
- Confirm equality, rounding, null, and reset policies.
- 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 T7 Release 14 Functional Reference, Xetra Release 14 market model, Xetra Release 14.1 production notice, Xetra ACE parameter update. 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 implement a protected volatility-auction state transition. The learning flow is: Closing-Cross Price → Volatility-Auction Reopening → Tick-Size Validation. Carry the result forward only with its scope, clock, state, and evidence label.
Rendered from the canonical Mermaid sources linked by this article.
Volatility-Auction Reopening calculation flow
This flow identifies the selected calculation stages and the structured output.
Takeaway: A trigger starts the auction; executable protected price determination ends it.
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 — 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.
S2 — Market Model for the Trading Venue Xetra, T7 Release 14.0
- Organization or authors: Deutsche Börse Cash Market
- Source type: Official venue market model
- Publication or effective date: 27 October 2025
- Version: T7 Release 14.0
- URL or DOI: https://www.xetra.com/xetra-en/technology/t7/system-documentation/release14-0/production
- Accessed: 2026-07-30
- Jurisdiction: Xetra cash market
- Supports: Opening, closing, auction-call, indicative price, executable volume, imbalance, and volatility-interruption phases.
- Limitations: The documentation page links versioned artifacts; exact current parameters are instrument configuration.
S3 — Introduction of T7 Release 14.1
- Organization or authors: Deutsche Börse Cash Market
- Source type: Official venue release circular
- Publication or effective date: 17 November 2025; production start 18 May 2026
- Version: Xetra Circular 054/2025
- URL or DOI: https://www.cashmarket.deutsche-boerse.com/cash-en/Stay-Informed/circulars-newsletters/deutsche-boerse-circulars/Introduction-of-T7-Release-14.1-4803320
- Accessed: 2026-07-30
- Jurisdiction: Xetra and Börse Frankfurt cash markets
- Supports: T7 Release 14.1 production timing and auction-related functionality context.
- Limitations: The circular is a release guard, not the source of the package-selected price formula or synthetic parameters.
S4 — T7 Trading Parameters: Implied Reference Price and ACE Parameter Classes
- Organization or authors: Deutsche Börse Cash Market
- Source type: Official venue parameter circular
- Publication or effective date: 3 June 2026; effective 8 and 15 June 2026
- Version: Xetra Circular 023/2026
- URL or DOI: https://www.cashmarket.deutsche-boerse.com/cash-en/Stay-Informed/circulars-newsletters/deutsche-boerse-circulars/T7-Trading-Parameters-Determination-of-the-Implied-Reference-Price-and-Adjustment-of-Parameter-Classes-in-the-Volatility-Interruption-Model-with-Automated-Corridor-Expansion-ACE--5302636
- Accessed: 2026-07-30
- Jurisdiction: Xetra ETFs and ETPs in applicable ACE closing auctions
- Supports: Instrument-class corridor parameters and reference-price methods can change independently of the teaching state machine.
- Limitations: The circular applies to specified ETF/ETP closing-auction configurations and does not define universal corridor values.
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.
Full dependency-light reference implementations in both supported languages.
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}`);
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.