D12-F01-A04 / Complete engineering topic

Hybrid Pro-Rata/Time Matching

A production-minded guide to Hybrid Pro-Rata/Time Matching.

D12 · MATCHING ENGINES AND VENUE LO…
D12-F01-A04Canonical / Tested / Open
D12 / D12-F01

Implement a two-stage hybrid matcher whose FIFO and pro-rata allocations reconcile exactly.

The decision this tutorial makes visible

Hybrid allocation is easy to describe and easy to implement incorrectly. The pro-rata stage must use quantity remaining after FIFO, and both percentage rounding and lot apportionment need explicit policies.

The precise question is: How does a configurable matcher split executable quantity between a FIFO slice and a proportional slice without double-counting resting capacity?

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

One executable pool is divided into two nonoverlapping stages: time rewards queue age, then pro-rata shares what remains by residual displayed size.

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 model selects one price upstream, floors the FIFO target to whole lots, allocates that target by time, then allocates the remaining executable lots pro rata over post-FIFO open quantity.

VariantDefinitionBest useMain limitation
Canonical FIFO then pro-rataDeclared split with residual-capacity recomputationReproducible hybrid educationNo top-order or participant overlay
CME configurable/splitProduct-configured FIFO and pro-rata componentsConfigured futures productsCan include additional stages
Time-pro-rataTime-weighted proportional formulaSelected T7 productsNot a simple two-bucket split

What is sourced, selected, synthetic, and derived

RoleMaterial claimEvidenceBoundary
Sourced factCME describes venue-specific hybrid allocation that combines a FIFO share with pro-rata allocation; CME and Eurex also distinguish the component families.S1 (CME agricultural hybrid example), S2 (CME taxonomy), and S3 (Eurex component definitions)The actual product algorithm and overlays remain venue- and configuration-specific.
Implementation choiceThe canonical model selects one price upstream, floors the FIFO target to whole lots, allocates that target by time, then allocates the remaining executable lots pro rata over post-FIFO open quantity.Frozen package definitionThis selection is not presented as a universal exchange rule.
Synthetic teaching inputAll order IDs, quantities, prices, and sequence numbers in the fixture are repository-authored.datasets/canonical-input.jsonThey are not observed participant orders or licensed venue data.
Author-derived calculationAt a 40 percent FIFO share, 240 of 600 shares go first to H-1. The remaining 360 are allocated pro rata over residual sizes 260, 300, and 200, producing final totals 363, 142, and 95.Formula, fixture, independent arithmetic, and Python/TypeScript parityCorrect allocation does not prove execution quality or future fill likelihood.

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
E=min(Q,sum q); E_fifo=floor(E*f/lot)*lot; E_pro=E-E_fifo; a_i=a_i_fifo+a_i_pro(residual q)
SymbolMeaningUnitPolicy
Etotal executable quantityshares/contractscapacity bounded
fFIFO allocation fractionratiozero through one
E_Fwhole-lot FIFO targetshares/contractsfloor(E*f/L)*L
E_Ppro-rata targetshares/contractsE-E_F
  • Use exact tick-normalized price comparisons with a 1e-12 implementation tolerance.
  • Allocate only integer multiples of lot_size.
  • Do not round allocation shares until the declared lot-apportionment step.
  • Preserve null average price when no quantity executes.

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 the one-price book and fraction
  2. Compute executable whole lots
  3. Floor the FIFO target and allocate oldest orders first
  4. Recompute residual resting capacity
  5. Allocate the remaining target pro rata and reconcile

Production-minded operational checklist

  1. Resolve venue, product, instrument, session, and matching-algorithm configuration.
  2. Validate tick, lot, side, price, open quantity, identity, and engine sequence.
  3. Apply price priority before the declared within-price allocation rule.
  4. Reconcile aggressor leaves, resting residuals, and every emitted fill.
  5. Persist the rule version and diagnostics needed for deterministic replay.

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

At a 40 percent FIFO share, 240 of 600 shares go first to H-1. The remaining 360 are allocated pro rata over residual sizes 260, 300, and 200, producing final totals 363, 142, and 95.

Counterfactual checkpoint

Hybrid endpoints. Move the FIFO fraction from zero to one without changing the one-price resting book. The output changes because both stages draw from one conserved executable pool and pro-rata sees only post-FIFO residual capacity.

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 allocation curveStep 30 · canonical fixtureIncoming quantity moves through the selected queue and whole-lot rule.fully-allocated363.0 to first orderFIFO 240.0 · pro-rata 360.01
Equal-size queueStep 30 · comparison focusAll resting sizes tie so time becomes the visible fallback or residual tie-break.fully-allocated300.0 to first orderFIFO 300.0 · pro-rata 300.02
Concentrated first orderStep 30 · comparison focusOne older order grows from modest to dominant displayed size.fully-allocated400.0 to first orderFIFO 300.0 · pro-rata 300.02
Capacity shortfallStep 30 · comparison focusIncoming quantity exceeds the single-price displayed book.partially-allocated500.0 to first orderFIFO 500.0 · pro-rata 500.02
Coarse lot boundaryStep 30 · comparison focusA larger lot size exposes deterministic floor and residual assignment.fully-allocated380.0 to first orderFIFO 300.0 · pro-rata 300.01
Reversed arrival orderStep 30 · comparison focusSequence order is reversed without changing resting quantities.fully-allocated200.0 to first orderFIFO 300.0 · pro-rata 300.01
Small-order edgeStep 30 · comparison focusOne small resting order tests capping and residual-lot fairness.fully-allocated398.0 to first orderFIFO 300.0 · pro-rata 300.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

Hybrid Pro-Rata/Time Matching 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:

  • fifo_fraction equals zero — Use pure pro-rata stage, because No executable lots are reserved for time.
  • fifo_fraction equals one — Use pure FIFO stage, because No proportional quantity remains.
  • FIFO partially consumes an order — Use only its residual in pro-rata, because Prevents capacity double-counting.

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:

  • Filled plus residual incoming quantity equals requested quantity.
  • No resting allocation exceeds that order's open quantity.
  • The sum of per-order allocations equals allocated quantity.
  • Every fill obeys price priority before the selected within-price rule.

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

  • A venue or product may use participant, customer, market-maker, top-order, threshold, hidden, reserve, or implied-liquidity overlays.
  • Public depth does not reveal every private priority attribute or future cancellation.
  • Definition fidelity does not establish latency performance, fill probability, 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 continuous-book reconstruction would require sequence-complete licensed order events, exact product algorithm parameters, participant and order attributes, gap and correction policy, clock provenance, and redistribution rights. Synthetic orders expose every allocation without inventing inaccessible queue state.

The primary sources are CME agricultural matching, CME matching overview, Eurex matching principles. 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 compose FIFO and pro-rata allocation without violating conservation. The learning flow is: Size-Time Priority → Hybrid Pro-Rata/Time Matching → Maximum-Executable-Volume Auction. Carry the result forward only with its scope, clock, state, and evidence label.

Hybrid Pro-Rata/Time Matching calculation flow

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

Rendering system map…

Takeaway: The second stage sees post-FIFO residual capacity, never the original book.

ReferencesPrimary sources and evidence notes

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

S1 — How CME Group Agricultural Markets Operate

  • Organization or authors: CME Group
  • Source type: Official exchange market-structure explanation
  • Publication or effective date: Current page accessed 2026-07-30
  • Version: Current online version
  • URL or DOI: https://www.cmegroup.com/education/articles-and-reports/overview-what-makes-ags-markets-work
  • Accessed: 2026-07-30
  • Jurisdiction: CME Group agricultural derivatives
  • Supports: FIFO as time priority and configurable split FIFO/pro-rata allocation as a venue-specific hybrid.
  • Limitations: Named product settings can change and do not make the package split universal.

S2 — Matching Algorithm Overview

  • Organization or authors: CME Group
  • Source type: Official exchange educational and systems overview
  • Publication or effective date: Current page accessed 2026-07-30
  • Version: Current online version
  • URL or DOI: https://www.cmegroup.com/education/matching-algorithm-overview
  • Accessed: 2026-07-30
  • Jurisdiction: CME Group derivatives markets
  • Supports: FIFO, pro-rata, configurable, threshold, top-order, and market-maker allocation families.
  • Limitations: Product-specific parameters and current assignments must be read from the applicable reference data and rules.

S3 — Matching principles

  • Organization or authors: Eurex Frankfurt AG
  • Source type: Official exchange methodology page
  • Publication or effective date: Current page accessed 2026-07-30
  • Version: Current online version
  • URL or DOI: https://www.eurex.com/ex-en/trade/order-book-trading/matching-principles
  • Accessed: 2026-07-30
  • Jurisdiction: Eurex derivatives markets
  • Supports: Price/time allocation, pro-rata allocation, quantity sorting, time-priority ties, and time-pro-rata as distinct allocation methods.
  • Limitations: Product configuration and overlays determine the actual allocation method; the page does not define this package fixture.

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.

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

function num(name: string, value: unknown, positive = 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`);
  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 multiple(name: string, value: number, lot: number): void {
  if (Math.abs(value / lot - Math.round(value / lot)) > 1e-9) throw new RangeError(`${name} must be an integer multiple of lot_size`);
}
function parseResting(raw: unknown, expectedSide: string | null = null): JsonObject[] {
  if (!Array.isArray(raw) || raw.length === 0) throw new RangeError("resting_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(`resting_orders[${index}] must be an object`);
    const order = item as JsonObject, orderId = text(`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 side = text(`resting_orders[${index}].side`, order.side);
    if (!["buy", "sell"].includes(side)) throw new RangeError("order side must be buy or sell");
    if (expectedSide && side !== expectedSide) throw new RangeError("all resting orders must be on the opposite side");
    const sequence = integer(`resting_orders[${index}].arrival_sequence`, order.arrival_sequence, true);
    if (sequences.has(sequence)) throw new RangeError("arrival_sequence values must be unique"); sequences.add(sequence);
    return {
      order_id: orderId,
      side,
      price: num(`resting_orders[${index}].price`, order.price, true),
      remaining_quantity: num(`resting_orders[${index}].remaining_quantity`, order.remaining_quantity, true),
      arrival_sequence: sequence,
    };
  });
}

export function priceTimePriority(incomingRaw: unknown, restingRaw: unknown): JsonObject {
  if (!incomingRaw || typeof incomingRaw !== "object") throw new TypeError("incoming_order must be an object");
  const incoming = incomingRaw as JsonObject;
  const orderId = text("incoming_order.order_id", incoming.order_id), side = text("incoming_order.side", incoming.side);
  if (!["buy", "sell"].includes(side)) throw new RangeError("incoming side must be buy or sell");
  const orderType = text("incoming_order.order_type", incoming.order_type);
  if (!["market", "limit"].includes(orderType)) throw new RangeError("order_type must be market or limit");
  const quantity = num("incoming_order.quantity", incoming.quantity, true);
  let limitPrice: number | null = null;
  if (orderType === "limit") limitPrice = num("incoming_order.limit_price", incoming.limit_price, true);
  else if (incoming.limit_price != null) throw new RangeError("market order must not carry limit_price");
  const opposite = side === "buy" ? "sell" : "buy";
  const book = parseResting(restingRaw, opposite);
  if (book.some(order => order.order_id === orderId)) throw new RangeError("incoming and resting order IDs must be unique");
  book.sort((a, b) => side === "buy" ? a.price - b.price || a.arrival_sequence - b.arrival_sequence : b.price - a.price || a.arrival_sequence - b.arrival_sequence);
  let remaining = quantity, notional = 0;
  const fills: JsonObject[] = [];
  for (const order of book) {
    const marketable = limitPrice == null || (side === "buy" ? order.price <= limitPrice : order.price >= limitPrice);
    if (!marketable) break;
    const fill = Math.min(remaining, order.remaining_quantity);
    if (fill <= 0) continue;
    order.remaining_quantity -= fill; remaining -= fill; notional += fill * order.price;
    fills.push({resting_order_id: order.order_id, price: order.price, quantity: fill, arrival_sequence: order.arrival_sequence});
    if (remaining <= 1e-12) { remaining = 0; break; }
  }
  const filled = quantity - remaining;
  return {
    model: "single-venue-price-time-priority", incoming_order_id: orderId, requested_quantity: quantity,
    filled_quantity: filled, residual_quantity: remaining, average_fill_price: filled ? notional / filled : null,
    fill_count: fills.length, fills, final_resting_orders: book.filter(order => order.remaining_quantity > 0),
    state: remaining === 0 ? "filled" : filled ? "partially-filled" : "unfilled",
  };
}

function singlePriceInputs(incomingRaw: unknown, priceRaw: unknown, lotRaw: unknown, restingRaw: unknown): [number, number, number, JsonObject[]] {
  const incoming = num("incoming_quantity", incomingRaw, true), price = num("price", priceRaw, true), lot = num("lot_size", lotRaw, true);
  multiple("incoming_quantity", incoming, lot);
  const orders = parseResting(restingRaw), side = orders[0].side;
  orders.forEach(order => {
    if (order.side !== side) throw new RangeError("single-price allocation requires one resting side");
    if (Math.abs(order.price - price) > 1e-12) throw new RangeError("every resting order must be at price");
    multiple("remaining_quantity", order.remaining_quantity, lot);
  });
  return [incoming, price, lot, orders];
}

function proRataUnits(orders: JsonObject[], allocationUnits: number, lot: number): Record<string, number> {
  const capacities: Record<string, number> = {};
  orders.forEach(order => capacities[order.order_id] = Math.round(order.remaining_quantity / lot));
  const totalCapacity = Object.values(capacities).reduce((a, b) => a + b, 0), units = Math.min(allocationUnits, totalCapacity);
  if (units <= 0) return Object.fromEntries(orders.map(order => [order.order_id, 0]));
  const raw: Record<string, number> = {}, allocated: Record<string, number> = {};
  orders.forEach(order => {
    raw[order.order_id] = units * capacities[order.order_id] / totalCapacity;
    allocated[order.order_id] = Math.min(capacities[order.order_id], Math.floor(raw[order.order_id]));
  });
  let remainder = units - Object.values(allocated).reduce((a, b) => a + b, 0);
  const rank = [...orders].sort((a, b) => {
    const fa = raw[a.order_id] - Math.floor(raw[a.order_id]), fb = raw[b.order_id] - Math.floor(raw[b.order_id]);
    return fb - fa || capacities[b.order_id] - capacities[a.order_id] || a.arrival_sequence - b.arrival_sequence;
  });
  while (remainder > 0) {
    let progressed = false;
    for (const order of rank) {
      if (allocated[order.order_id] < capacities[order.order_id]) {
        allocated[order.order_id] += 1; remainder -= 1; progressed = true;
        if (remainder === 0) break;
      }
    }
    if (!progressed) break;
  }
  return allocated;
}

export function proRataMatching(incomingRaw: unknown, priceRaw: unknown, lotRaw: unknown, restingRaw: unknown): JsonObject {
  const [incoming, price, lot, orders] = singlePriceInputs(incomingRaw, priceRaw, lotRaw, restingRaw);
  const executable = Math.min(incoming, orders.reduce((sum, order) => sum + order.remaining_quantity, 0));
  const units = proRataUnits(orders, Math.round(executable / lot), lot);
  const allocations = [...orders].sort((a, b) => a.arrival_sequence - b.arrival_sequence).map(order => {
    const quantity = units[order.order_id] * lot;
    return {order_id: order.order_id, resting_quantity: order.remaining_quantity, allocated_quantity: quantity, remaining_quantity: order.remaining_quantity - quantity, arrival_sequence: order.arrival_sequence};
  });
  const allocated = allocations.reduce((sum, row) => sum + row.allocated_quantity, 0);
  return {model: "single-price-lot-aware-pro-rata", price, lot_size: lot, incoming_quantity: incoming, executable_quantity: executable, allocated_quantity: allocated, unfilled_quantity: incoming - allocated, allocations, state: allocated === incoming ? "fully-allocated" : "partially-allocated"};
}

export function sizeTimePriority(incomingRaw: unknown, priceRaw: unknown, lotRaw: unknown, restingRaw: unknown): JsonObject {
  const [incoming, price, lot, orders] = singlePriceInputs(incomingRaw, priceRaw, lotRaw, restingRaw);
  const ranked = [...orders].sort((a, b) => b.remaining_quantity - a.remaining_quantity || a.arrival_sequence - b.arrival_sequence);
  let remaining = incoming;
  const allocations: JsonObject[] = [];
  for (let index = 0; index < ranked.length; index += 1) {
    const order = ranked[index], quantity = Math.min(remaining, order.remaining_quantity);
    remaining -= quantity;
    allocations.push({rank: index + 1, order_id: order.order_id, resting_quantity: order.remaining_quantity, allocated_quantity: quantity, remaining_quantity: order.remaining_quantity - quantity, arrival_sequence: order.arrival_sequence});
    if (remaining <= 1e-12) { remaining = 0; break; }
  }
  const allocated = incoming - remaining;
  return {model: "single-price-size-then-time-priority", price, lot_size: lot, incoming_quantity: incoming, allocated_quantity: allocated, unfilled_quantity: remaining, ranked_order_ids: ranked.map(order => order.order_id), allocations, state: remaining === 0 ? "fully-allocated" : "partially-allocated"};
}

export function hybridProRataTime(incomingRaw: unknown, priceRaw: unknown, lotRaw: unknown, fractionRaw: unknown, restingRaw: unknown): JsonObject {
  const [incoming, price, lot, orders] = singlePriceInputs(incomingRaw, priceRaw, lotRaw, restingRaw);
  const fraction = num("fifo_fraction", fractionRaw);
  if (fraction < 0 || fraction > 1) throw new RangeError("fifo_fraction must be between zero and one");
  const executable = Math.min(incoming, orders.reduce((sum, order) => sum + order.remaining_quantity, 0));
  const totalUnits = Math.round(executable / lot), fifoUnits = Math.floor(totalUnits * fraction + 1e-12);
  const fifoAlloc: Record<string, number> = Object.fromEntries(orders.map(order => [order.order_id, 0]));
  let fifoRemaining = fifoUnits;
  for (const order of [...orders].sort((a, b) => a.arrival_sequence - b.arrival_sequence)) {
    const capacity = Math.round(order.remaining_quantity / lot), take = Math.min(fifoRemaining, capacity);
    fifoAlloc[order.order_id] = take; fifoRemaining -= take;
    if (fifoRemaining === 0) break;
  }
  const residual = orders.flatMap(order => {
    const residualUnits = Math.round(order.remaining_quantity / lot) - fifoAlloc[order.order_id];
    return residualUnits > 0 ? [{...order, remaining_quantity: residualUnits * lot}] : [];
  });
  const proUnits = totalUnits - fifoUnits, proAlloc = residual.length ? proRataUnits(residual, proUnits, lot) : {};
  const allocations = [...orders].sort((a, b) => a.arrival_sequence - b.arrival_sequence).map(order => {
    const fifoQuantity = fifoAlloc[order.order_id] * lot, proQuantity = (proAlloc[order.order_id] || 0) * lot, total = fifoQuantity + proQuantity;
    return {order_id: order.order_id, resting_quantity: order.remaining_quantity, fifo_quantity: fifoQuantity, pro_rata_quantity: proQuantity, allocated_quantity: total, remaining_quantity: order.remaining_quantity - total, arrival_sequence: order.arrival_sequence};
  });
  const allocated = allocations.reduce((sum, row) => sum + row.allocated_quantity, 0);
  return {model: "single-price-fifo-pro-rata-split", price, lot_size: lot, fifo_fraction: fraction, incoming_quantity: incoming, executable_quantity: executable, fifo_target_quantity: fifoUnits * lot, pro_rata_target_quantity: proUnits * lot, allocated_quantity: allocated, unfilled_quantity: incoming - allocated, allocations, state: allocated === incoming ? "fully-allocated" : "partially-allocated"};
}

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-F01-A01") return priceTimePriority(inputs.incoming_order, inputs.resting_orders);
  if (topicId === "D12-F01-A02") return proRataMatching(inputs.incoming_quantity, inputs.price, inputs.lot_size, inputs.resting_orders);
  if (topicId === "D12-F01-A03") return sizeTimePriority(inputs.incoming_quantity, inputs.price, inputs.lot_size, inputs.resting_orders);
  if (topicId === "D12-F01-A04") return hybridProRataTime(inputs.incoming_quantity, inputs.price, inputs.lot_size, inputs.fifo_fraction, inputs.resting_orders);
  throw new RangeError(`unsupported topic_id: ${topicId}`);
}
Full-height labplaygroundOpen full screen