Library/Matching Engines and Venue Logic/Order Lifecycle and Queue State

D12-F04-A05 / Complete engineering topic

Iceberg/Reserve-Order Replenishment

A production-minded guide to Iceberg/Reserve-Order Replenishment.

D12 · MATCHING ENGINES AND VENUE LO…
D12-F04-A05Canonical / Tested / Open
D12 / D12-F04

Simulate deterministic reserve replenishment while keeping displayed, reserve, total, fill, and priority ledgers exact.

The decision this tutorial makes visible

Reserve orders split total interest into visible and non-displayed components. Replenishment affects both observable depth and queue priority.

The precise question is: How do displayed and reserve quantities change when a visible tranche is depleted and a new tranche receives fresh displayed priority?

This matters to two readers at once. 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 ordered inputs in Python, TypeScript, a visual, and a browser lab. This tutorial supplies both views and keeps the selected single-venue convention visible throughout.

Intuition before notation

Every new displayed tranche moves quantity from reserve to display without changing total remaining, while fills reduce total remaining.

The diagnostic is not a free-floating signal. Its meaning depends on the declared book or lifecycle scope, the event or snapshot clock, equality handling, the included price window, and the treatment of unavailable state. If any of those change, the result is a different estimand or engine rule even when the final field name looks similar.

Scope and nearby methods

The canonical engine uses a pedagogical zero-displayed trigger: when the visible tranche reaches zero, it exposes min(display_size,reserve) with a new displayed sequence. Nasdaq's current below-round-lot replenishment is a sourced nearby variant, not silently copied.

VariantDefinitionBest useMain limitation
Zero-display tranche modelReplenish only after visible depletionPortable teaching state machineNot exact for every venue
Nasdaq reserve behaviorCan enter a new displayed order when displayed size falls below a normal unitNasdaq production interpretationNew and original displayed pieces can coexist
Random reserveRandomize visible tranche in a declared rangeVenue-supported display randomizationStochastic and rule-specific

What is sourced, selected, synthetic, and derived

RoleMaterial claimEvidenceBoundary
Sourced factAuthoritative sources establish only the protocol vocabulary, venue message, or venue-rule behavior recorded for this topic.S1, S2, S3They do not turn the selected teaching engine into a universal or production-conformant venue model.
Implementation choiceThe canonical engine uses a pedagogical zero-displayed trigger: when the visible tranche reaches zero, it exposes min(display_size,reserve) with a new displayed sequence. Nasdaq's current below-round-lot replenishment is a sourced nearby variant, not silently copied.Frozen package definitionAuctions, routing, pegging, hidden priority, self-match controls, and other venue rules remain excluded unless named.
Synthetic teaching inputThe canonical order and event states are repository-authored teaching data.datasets/canonical-input.json and datasets/scenario-results.jsonThey are not private acknowledgements, participant records, or licensed venue observations.
Author-derived calculationThe 1,000-share synthetic order begins with 200 displayed and 800 reserve. Each full visible depletion exposes at most 200 with a new sequence; fills never create or lose quantity.Selected state rule, canonical fixture, Python/TypeScript parity, and independent arithmeticDefinition fidelity does not certify latency, operational safety, venue conformance, or trading value.

The authoritative sources define feed capabilities, protocol vocabulary, venue-specific rules, or published empirical context. 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 from that fixture under the selected implementation choice.

Formula, symbols, and numerical policy

Plain text
replenish_qty=min(display_size,reserve) when displayed=0; reserve-=replenish_qty; displayed+=replenish_qty; priority_seq+=1
SymbolMeaningUnitPolicy
Qtotal order quantitysharesdisplayed plus reserve at entry
D_tdisplayed remaining after event tsharesnonnegative
R_treserve remaining after event tsharesnonnegative
dselected display tranche sizesharespositive and no larger than total
kreplenishment countintegerincrements when the selected trigger transfers reserve to display
  • The canonical pedagogical trigger replenishes only when displayed remaining reaches zero.
  • Each tranche transfer is min(display size, reserve remaining) and receives a new priority sequence.
  • Current Nasdaq below-normal-unit behavior is documented as a nearby venue variant, not treated as equivalent.

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. Initialize displayed and reserve components
  2. Consume the current displayed tranche
  3. When it reaches zero, expose the next bounded tranche
  4. Increment displayed priority for every replenishment
  5. Return fill/replenish audit and conserved residuals

Production-minded operational checklist

  1. Freeze venue rule, trigger, display size, and reserve terms.
  2. Apply fills to displayed interest before the selected replenishment trigger.
  3. Audit each reserve-to-display transfer and new sequence.
  4. Reconcile displayed plus reserve to total remaining after every event.
  5. Stop replenishing when reserve reaches zero and avoid universal venue claims.

The checklist is intentionally stricter than a charting demo. A plausible number from stale, incomplete, off-grid, misordered, or unsupported state is more dangerous than an explicit rejection.

Worked synthetic example

The canonical fixture is synthetic teaching data, not an observed venue snapshot or private order record. Its primary author-derived output, replenishment_count, is 4. The complete input and output are in datasets/canonical-input.json and datasets/expected-output.json.

The 1,000-share synthetic order begins with 200 displayed and 800 reserve. Each full visible depletion exposes at most 200 with a new sequence; fills never create or lose quantity.

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 shows the midpoint used for prose review; the full state ledger is in datasets/scenario-results.json.

ScenarioPurposeStatePrimary outputDiagnostic
Canonical pathMove one declared driver through the canonical lifecycle.active-reserve2 replenishmentsdisplayed 150.0 · reserve 400.0
Live or preserved boundaryInspect a nonterminal or priority-preserving state.active-reserve0 replenishmentsdisplayed 100.0 · reserve 800.0
Terminal or reset boundaryInspect a legal terminal or new-priority transition.filled4 replenishmentsdisplayed 0.0 · reserve 0.0
Partial and residual caseLeave quantity live after part of the action completes.active-reserve3 replenishmentsdisplayed 50.0 · reserve 200.0
Comparison pathCompare another valid order type, side, or queue shape.active-reserve3 replenishmentsdisplayed 75.0 · reserve 200.0
Equality and idempotency boundaryLand exactly on a quantity, price, terminal, or priority equality branch.active-reserve0 replenishmentsdisplayed 200.0 · reserve 800.0
Adversarial audit pathUse a valid but easily misread state to test conservation and scope guardrails.active-reserve6 replenishmentsdisplayed 125.0 · reserve 125.0

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

Iceberg/Reserve-Order Replenishment annotated teaching map

Open this SVG at full size, or use the guided playground to compare canonical, equality, null, residual, adversarial, and cross-method 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. They reject malformed and unsupported state before calculation, preserve best-to-worse or event-sequence ordering, apply the equality policy in the symbol table, and return a structured diagnostic object rather than one context-free number.

The main implementation branches are:

  • Displayed reaches zero and reserve remains — Expose next tranche with new sequence, because Canonical replenishment trigger.
  • Reserve is smaller than display size — Expose the residual reserve, because Never create quantity.
  • Fill request exceeds total remaining — Record unfilled requested quantity, because No over-execution.

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:

  • Executed plus total remaining equals original total.
  • Displayed plus reserve equals total remaining.
  • Each replenishment reduces reserve by exactly the new displayed quantity.
  • The structured output retains replenishment_count, lifecycle state, and reason fields needed to replay the branch.

Passing definition and parity checks proves that the implementation matches the selected contract. It does not prove that displayed state remains available, that another venue uses the same rule, or that the diagnostic predicts a later price or fill.

Failure modes and misuse

  • Price-time FIFO is the selected teaching model; other venues and products can use pro-rata, size, customer, parity, or hybrid priority.
  • Public displayed data cannot reveal all hidden, reserve, routed, or private participant state.
  • Definition-correct state processing does not establish latency performance, production safety, or trading value.
  • Nasdaq's current reserve rule can add a new displayed order while a sub-round-lot original displayed residual remains; the canonical zero-trigger model is deliberately simpler.

Debugging order

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

  1. Confirm instrument, venue, session, order type, and side.
  2. Confirm price, quantity, tick, clock, and sequence units.
  3. Confirm the included depth window or lifecycle-event boundary.
  4. Confirm equality, null, residual, and reset policies.
  5. Recalculate the invariant and midpoint counterexample before changing code.

Evidence and historical boundary

Historical decision: deferred. A named venue lifecycle case would require synchronized private acknowledgements or a sequence-complete licensed order feed, exact venue and order-type rules, corrections, session state, and redistribution permission. Synthetic events make every transition publicly reproducible.

The primary sources are FIX order-state matrices, Nasdaq TotalView-ITCH 5.0, Nasdaq Equity 4 rules. They support the source roles listed in the research ledger, not a redistributable historical observation, participant-intent claim, venue-conformance certification, profitability claim, or prediction claim.

Summary and next topic

You can now simulate reserve tranches with quantity conservation. The learning flow is: Queue Position and Ahead-Volume Calculation → Iceberg/Reserve-Order Replenishment → Marketable-Order Multi-Level Sweep. Carry the result forward only with its scope, clock, state, and evidence label.

Iceberg/Reserve-Order Replenishment calculation flow

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

Rendering system map…

Takeaway: Replenishment moves quantity between hidden and visible state, while only fills reduce total remaining.

ReferencesPrimary sources and evidence notes

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

S1 — Order State Changes

  • Organization or authors: FIX Trading Community
  • Source type: Official protocol specification guidance
  • Publication or effective date: Current online specification accessed 2026-07-30
  • Version: Online specification
  • URL or DOI: https://www.fixtrading.org/online-specification/order-state-changes/
  • Accessed: 2026-07-30
  • Jurisdiction: Cross-market protocol semantics
  • Supports: Order status, execution type, cumulative quantity, leaves quantity, and state-transition examples.
  • Limitations: FIX communication states do not prescribe every venue matching or priority rule.

S2 — Nasdaq TotalView-ITCH 5.0 Specification

  • Organization or authors: Nasdaq
  • Source type: Official exchange technical specification
  • Publication or effective date: Specification current at access
  • Version: 5.0
  • URL or DOI: https://nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/NQTVITCHSpecification.pdf
  • Accessed: 2026-07-30
  • Jurisdiction: United States equities; Nasdaq
  • Supports: Add, execution, partial cancel, delete, and replace messages, including new reference numbers on replace.
  • Limitations: A public market-data feed is not the participant's complete private order-entry acknowledgement stream.

S3 — Nasdaq Equity 4 — Equity Rules

  • Organization or authors: The Nasdaq Stock Market LLC
  • Source type: Official exchange rulebook
  • Publication or effective date: Current web rulebook accessed 2026-07-30
  • Version: Current 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: Time ranking, same-price size-decrease priority preservation, new timestamps for other modifications, decrementation on execution, reserve-size behavior, and price-time processing.
  • Limitations: Rules are venue-, order-type-, attribute-, protocol-, session-, and exception-specific and can change.

Evidence boundary

The sources establish feed fields, venue rules, protocol states, or published microstructure definitions. They do not verify the repository-authored synthetic fixture, thresholds, empirical usefulness, execution probability, or profitability. The package-selected choices are labeled as implementation choices wherever they are used.

order-lifecycle-queue-state.ts
export type JsonObject = Record<string, any>;

function numberValue(name: string, value: unknown, options: {positive?: boolean; nonnegative?: boolean} = {}): number {
  if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${name} must be a finite number`);
  if (options.positive && value <= 0) throw new RangeError(`${name} must be positive`);
  if (options.nonnegative && value < 0) throw new RangeError(`${name} must be nonnegative`);
  return value;
}
function integerValue(name: string, value: unknown, nonnegative = false): number {
  const result = numberValue(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 textValue(name: string, value: unknown): string {
  if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be a non-empty string`);
  return value;
}

export function limitOrderLifecycle(orderIdRaw: unknown, orderQuantityRaw: unknown, eventsRaw: unknown): JsonObject {
  const orderId = textValue("order_id", orderIdRaw), orderQuantity = numberValue("order_quantity", orderQuantityRaw, {positive: true});
  if (!Array.isArray(eventsRaw) || eventsRaw.length === 0) throw new RangeError("events must be a non-empty chronological list");
  let status = "pending-new", cumulative = 0, leaves = orderQuantity, notional = 0, executionCount = 0;
  const terminal = new Set(["filled", "canceled", "rejected", "expired", "replaced"]);
  const transitions: JsonObject[] = [];
  eventsRaw.forEach((raw, index) => {
    if (!raw || typeof raw !== "object") throw new TypeError(`events[${index}] must be an object`);
    const event = raw as JsonObject, eventType = textValue(`events[${index}].type`, event.type), before = status;
    if (terminal.has(status)) throw new RangeError("terminal order state cannot accept another event");
    if (eventType === "accept") {
      if (status !== "pending-new") throw new RangeError("accept is valid only from pending-new");
      status = "new";
    } else if (eventType === "reject") {
      if (status !== "pending-new") throw new RangeError("reject is valid only from pending-new");
      status = "rejected"; leaves = 0;
    } else if (eventType === "fill") {
      if (!["new", "partially-filled"].includes(status)) throw new RangeError("fill requires a live accepted order");
      const fillQuantity = numberValue(`events[${index}].quantity`, event.quantity, {positive: true});
      const fillPrice = numberValue(`events[${index}].price`, event.price, {positive: true});
      if (fillQuantity > leaves + 1e-12) throw new RangeError("fill quantity exceeds order leaves quantity");
      cumulative += fillQuantity; leaves = Math.max(0, orderQuantity - cumulative); notional += fillQuantity * fillPrice; executionCount += 1;
      status = leaves === 0 ? "filled" : "partially-filled";
    } else if (eventType === "cancel" || eventType === "expire") {
      if (!["new", "partially-filled"].includes(status)) throw new RangeError(`${eventType} requires a live accepted order`);
      status = eventType === "cancel" ? "canceled" : "expired"; leaves = 0;
    } else if (eventType === "replace") {
      if (!["new", "partially-filled"].includes(status)) throw new RangeError("replace requires a live accepted order");
      textValue(`events[${index}].new_order_id`, event.new_order_id); status = "replaced"; leaves = 0;
    } else throw new RangeError(`unsupported lifecycle event: ${eventType}`);
    transitions.push({event_index: index, event_type: eventType, before_status: before, after_status: status, cumulative_quantity: cumulative, leaves_quantity: leaves});
  });
  return {model: "accepted-order-lifecycle-state-machine", order_id: orderId, order_quantity: orderQuantity, cumulative_quantity: cumulative, leaves_quantity: leaves, average_fill_price: cumulative ? notional / cumulative : null, execution_count: executionCount, status, terminal: terminal.has(status), transitions, state: status};
}

export function cancelReplacePriority(originalRaw: unknown, replacementRaw: unknown): JsonObject {
  if (!originalRaw || typeof originalRaw !== "object" || !replacementRaw || typeof replacementRaw !== "object") throw new TypeError("original and replacement must be objects");
  const original = originalRaw as JsonObject, replacement = replacementRaw as JsonObject;
  const orderType = textValue("original.order_type", original.order_type);
  if (orderType !== "displayed-limit") throw new RangeError("canonical priority rule supports displayed-limit orders only");
  const originalId = textValue("original.order_id", original.order_id), replacementId = textValue("replacement.order_id", replacement.order_id);
  const originalPrice = numberValue("original.price", original.price, {positive: true}), replacementPrice = numberValue("replacement.price", replacement.price, {positive: true});
  const originalQuantity = numberValue("original.remaining_quantity", original.remaining_quantity, {positive: true}), replacementQuantity = numberValue("replacement.remaining_quantity", replacement.remaining_quantity, {positive: true});
  const originalSequence = integerValue("original.priority_sequence", original.priority_sequence, true), replaceSequence = integerValue("replacement.replace_sequence", replacement.replace_sequence, true);
  if (replaceSequence <= originalSequence) throw new RangeError("replace_sequence must be after original priority_sequence");
  const samePrice = Math.abs(originalPrice - replacementPrice) <= 1e-12, decreaseOnly = replacementQuantity <= originalQuantity, preserved = samePrice && decreaseOnly;
  const reason = preserved ? "same-price quantity decrease preserves canonical displayed-order priority" : !samePrice ? "price modification creates a new priority timestamp" : "quantity increase creates a new priority timestamp";
  return {model: "nasdaq-style-displayed-limit-priority-rule", original_order_id: originalId, replacement_order_id: replacementId, original_price: originalPrice, replacement_price: replacementPrice, original_remaining_quantity: originalQuantity, replacement_remaining_quantity: replacementQuantity, same_price: samePrice, decrease_only: decreaseOnly, priority_preserved: preserved, original_priority_sequence: originalSequence, effective_priority_sequence: preserved ? originalSequence : replaceSequence, reason, state: preserved ? "priority-preserved" : "new-priority"};
}

export function partialFillResidual(orderQuantityRaw: unknown, fillsRaw: unknown): JsonObject {
  const orderQuantity = numberValue("order_quantity", orderQuantityRaw, {positive: true});
  if (!Array.isArray(fillsRaw)) throw new TypeError("fills must be a list");
  let cumulative = 0, notional = 0;
  const seen = new Set<string>(), states: JsonObject[] = [];
  fillsRaw.forEach((raw, index) => {
    if (!raw || typeof raw !== "object") throw new TypeError(`fills[${index}] must be an object`);
    const fill = raw as JsonObject, executionId = textValue(`fills[${index}].execution_id`, fill.execution_id);
    if (seen.has(executionId)) throw new RangeError("execution_id values must be unique"); seen.add(executionId);
    const fillQuantity = numberValue(`fills[${index}].quantity`, fill.quantity, {positive: true}), fillPrice = numberValue(`fills[${index}].price`, fill.price, {positive: true});
    if (cumulative + fillQuantity > orderQuantity + 1e-12) throw new RangeError("fills exceed original order quantity");
    cumulative += fillQuantity; notional += fillQuantity * fillPrice;
    const leaves = Math.max(0, orderQuantity - cumulative);
    states.push({execution_id: executionId, last_quantity: fillQuantity, last_price: fillPrice, cumulative_quantity: cumulative, leaves_quantity: leaves, status: leaves === 0 ? "filled" : "partially-filled"});
  });
  const leaves = orderQuantity - cumulative, status = fillsRaw.length === 0 ? "new" : leaves === 0 ? "filled" : "partially-filled";
  return {model: "partial-fill-residual-accounting", order_quantity: orderQuantity, cumulative_quantity: cumulative, leaves_quantity: leaves, fill_notional: notional, average_fill_price: cumulative ? notional / cumulative : null, execution_count: fillsRaw.length, fill_states: states, status, state: status};
}

export function queuePositionAheadVolume(ordersRaw: unknown, targetIdRaw: unknown): JsonObject {
  if (!Array.isArray(ordersRaw) || ordersRaw.length === 0) throw new RangeError("orders must be a non-empty list");
  const targetId = textValue("target_order_id", targetIdRaw), ids = new Set<string>(), sequences = new Set<number>();
  const parsed = ordersRaw.map((raw, index) => {
    if (!raw || typeof raw !== "object") throw new TypeError(`orders[${index}] must be an object`);
    const order = raw as JsonObject, orderId = textValue(`orders[${index}].order_id`, order.order_id);
    if (ids.has(orderId)) throw new RangeError("order_id values must be unique"); ids.add(orderId);
    const sequence = integerValue(`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 side = textValue(`orders[${index}].side`, order.side);
    if (!["buy", "sell"].includes(side)) throw new RangeError("order side must be buy or sell");
    if (typeof order.displayed !== "boolean") throw new TypeError("displayed must be boolean");
    return {order_id: orderId, side, price: numberValue(`orders[${index}].price`, order.price, {positive: true}), remaining_quantity: numberValue(`orders[${index}].remaining_quantity`, order.remaining_quantity, {positive: true}), arrival_sequence: sequence, displayed: order.displayed};
  });
  const target = parsed.find(order => order.order_id === targetId);
  if (!target) throw new RangeError("target order is not present");
  if (!target.displayed) throw new RangeError("canonical queue position requires a displayed target");
  const samePrice = parsed.filter(order => order.side === target.side && Math.abs(order.price - target.price) <= 1e-12);
  const displayed = samePrice.filter(order => order.displayed).sort((a, b) => a.arrival_sequence - b.arrival_sequence);
  const ahead = displayed.filter(order => order.arrival_sequence < target.arrival_sequence), behind = displayed.filter(order => order.arrival_sequence > target.arrival_sequence);
  const aheadVolume = ahead.reduce((sum, order) => sum + order.remaining_quantity, 0);
  return {model: "displayed-price-time-fifo-ahead-volume", target_order_id: targetId, side: target.side, price: target.price, ahead_order_count: ahead.length, ahead_volume: aheadVolume, displayed_queue_rank: ahead.length + 1, behind_order_count: behind.length, behind_volume: behind.reduce((sum, order) => sum + order.remaining_quantity, 0), hidden_same_price_quantity_excluded: samePrice.filter(order => !order.displayed).reduce((sum, order) => sum + order.remaining_quantity, 0), ahead_order_ids: ahead.map(order => order.order_id), state: ahead.length ? "queued-behind-displayed-volume" : "front-of-displayed-queue"};
}

export function icebergReplenishment(totalRaw: unknown, initialRaw: unknown, displaySizeRaw: unknown, fillsRaw: unknown, priorityRaw: unknown): JsonObject {
  const total = numberValue("total_quantity", totalRaw, {positive: true});
  let displayed = numberValue("initial_display_quantity", initialRaw, {positive: true});
  const tranche = numberValue("display_size", displaySizeRaw, {positive: true});
  let priority = integerValue("starting_priority_sequence", priorityRaw, true);
  if (displayed > total) throw new RangeError("initial displayed quantity cannot exceed total quantity");
  if (!Array.isArray(fillsRaw)) throw new TypeError("fills must be a list");
  let reserve = total - displayed, executed = 0, unfilledRequested = 0, replenishments = 0;
  const events: JsonObject[] = [];
  const replenish = () => {
    if (displayed !== 0 || reserve <= 0) return;
    const amount = Math.min(tranche, reserve); reserve -= amount; displayed = amount; priority += 1; replenishments += 1;
    events.push({type: "replenish", quantity: amount, new_priority_sequence: priority, displayed_remaining: displayed, reserve_remaining: reserve});
  };
  fillsRaw.forEach((rawFill, index) => {
    let remainingRequest = numberValue(`fills[${index}]`, rawFill, {positive: true});
    while (remainingRequest > 1e-12) {
      replenish();
      if (displayed <= 0) { unfilledRequested += remainingRequest; break; }
      const amount = Math.min(displayed, remainingRequest);
      displayed -= amount; remainingRequest -= amount; executed += amount;
      events.push({type: "fill", fill_request_index: index, quantity: amount, displayed_remaining: displayed, reserve_remaining: reserve, priority_sequence: priority});
    }
    replenish();
  });
  const remainingTotal = displayed + reserve;
  return {model: "zero-displayed-tranche-replenishment", total_quantity: total, display_size: tranche, executed_quantity: executed, displayed_remaining: displayed, reserve_remaining: reserve, total_remaining: remainingTotal, replenishment_count: replenishments, current_priority_sequence: priority, unfilled_requested_quantity: unfilledRequested, events, state: remainingTotal === 0 ? "filled" : reserve > 0 ? "active-reserve" : "displayed-only"};
}

export function marketableOrderSweep(incomingRaw: unknown, restingRaw: unknown): JsonObject {
  if (!incomingRaw || typeof incomingRaw !== "object" || !Array.isArray(restingRaw)) throw new TypeError("incoming_order must be an object and resting_orders a list");
  const incoming = incomingRaw as JsonObject, incomingId = textValue("incoming_order.order_id", incoming.order_id), side = textValue("incoming_order.side", incoming.side);
  if (!["buy", "sell"].includes(side)) throw new RangeError("incoming side must be buy or sell");
  const orderType = textValue("incoming_order.order_type", incoming.order_type);
  if (!["market", "limit"].includes(orderType)) throw new RangeError("order_type must be market or limit");
  const tif = textValue("incoming_order.time_in_force", incoming.time_in_force);
  if (!["GTC", "IOC"].includes(tif)) throw new RangeError("time_in_force must be GTC or IOC");
  const quantity = numberValue("incoming_order.quantity", incoming.quantity, {positive: true});
  let limit: number | null = null;
  if (orderType === "limit") limit = numberValue("incoming_order.limit_price", incoming.limit_price, {positive: true});
  else if (incoming.limit_price != null) throw new RangeError("market order must not specify limit_price");
  const arrivalSequence = integerValue("incoming_order.arrival_sequence", incoming.arrival_sequence, true), ids = new Set([incomingId]), sequences = new Set<number>();
  const parsed = restingRaw.map((raw, index) => {
    if (!raw || typeof raw !== "object") throw new TypeError(`resting_orders[${index}] must be an object`);
    const order = raw as JsonObject, orderId = textValue(`resting_orders[${index}].order_id`, order.order_id);
    if (ids.has(orderId)) throw new RangeError("order_id values must be unique"); ids.add(orderId);
    const restingSide = textValue(`resting_orders[${index}].side`, order.side);
    if (!["buy", "sell"].includes(restingSide)) throw new RangeError("resting side must be buy or sell");
    const sequence = integerValue(`resting_orders[${index}].priority_sequence`, order.priority_sequence, true);
    if (sequences.has(sequence)) throw new RangeError("priority_sequence values must be unique"); sequences.add(sequence);
    return {order_id: orderId, side: restingSide, price: numberValue(`resting_orders[${index}].price`, order.price, {positive: true}), remaining_quantity: numberValue(`resting_orders[${index}].remaining_quantity`, order.remaining_quantity, {positive: true}), priority_sequence: sequence};
  });
  const opposite = side === "buy" ? "sell" : "buy";
  const candidates = parsed.filter(order => order.side === opposite).sort((a, b) => side === "buy" ? a.price - b.price || a.priority_sequence - b.priority_sequence : b.price - a.price || a.priority_sequence - b.priority_sequence);
  let remaining = quantity, notional = 0;
  const fills: JsonObject[] = [];
  for (const resting of candidates) {
    const marketable = limit == null || (side === "buy" ? resting.price <= limit : resting.price >= limit);
    if (!marketable) break;
    const amount = Math.min(remaining, resting.remaining_quantity);
    if (amount <= 0) continue;
    resting.remaining_quantity -= amount; remaining -= amount; notional += amount * resting.price;
    fills.push({resting_order_id: resting.order_id, price: resting.price, quantity: amount, resting_remaining_quantity: resting.remaining_quantity});
    if (remaining <= 1e-12) { remaining = 0; break; }
  }
  const filled = quantity - remaining;
  let residualAction = "none";
  if (remaining > 0) {
    if (orderType === "limit" && tif === "GTC") {
      parsed.push({order_id: incomingId, side, price: limit as number, remaining_quantity: remaining, priority_sequence: arrivalSequence}); residualAction = "posted";
    } else residualAction = "canceled";
  }
  const finalBook = parsed.filter(order => order.remaining_quantity > 0).sort((a, b) => {
    if (a.side !== b.side) return a.side === "buy" ? -1 : 1;
    return a.side === "buy" ? b.price - a.price || a.priority_sequence - b.priority_sequence : a.price - b.price || a.priority_sequence - b.priority_sequence;
  });
  const status = remaining === 0 ? "filled" : filled > 0 && residualAction === "posted" ? "partially-filled-and-posted" : filled > 0 ? "partially-filled-and-canceled" : residualAction === "posted" ? "posted" : "unfilled-and-canceled";
  return {model: "single-venue-price-time-marketable-sweep", incoming_order_id: incomingId, incoming_side: side, requested_quantity: quantity, filled_quantity: filled, residual_quantity: remaining, fill_notional: notional, average_fill_price: filled ? notional / filled : null, fills, residual_action: residualAction, final_book: finalBook, status, state: status};
}

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-F04-A01") return limitOrderLifecycle(inputs.order_id, inputs.order_quantity, inputs.events);
  if (topicId === "D12-F04-A02") return cancelReplacePriority(inputs.original, inputs.replacement);
  if (topicId === "D12-F04-A03") return partialFillResidual(inputs.order_quantity, inputs.fills);
  if (topicId === "D12-F04-A04") return queuePositionAheadVolume(inputs.orders, inputs.target_order_id);
  if (topicId === "D12-F04-A05") return icebergReplenishment(inputs.total_quantity, inputs.initial_display_quantity, inputs.display_size, inputs.fills, inputs.starting_priority_sequence);
  if (topicId === "D12-F04-A06") return marketableOrderSweep(inputs.incoming_order, inputs.resting_orders);
  throw new RangeError(`unsupported topic_id: ${topicId}`);
}
Full-height labplaygroundOpen full screen