Skip to content

Smart Order Router

VETA simulates a US equity venue topology. Child orders generated by the execution algorithms (TWAP, POV, VWAP, IS, MOMENTUM, ICEBERG, ARRIVAL_PRICE) route through the EMS which assigns a venue via weighted-random selection. The SNIPER algorithm routes its own children with a different strategy: best-price selection across the top N venues simultaneously.

This page documents both routing paths.

Seven US venues with weighting, spread multiplier, and depth multiplier. Defined once in backend/src/ems/fill-math.ts and consumed by both the EMS and the SNIPER algorithm.

| MIC | Weight | Spread mult | Depth mult | Notes | | --- | ---: | ---: | ---: | --- | | XNAS | 30% | 1.00 | 1.00 | Baseline reference | | XNYS | 25% | 1.05 | 1.20 | Deepest book | | ARCX | 15% | 1.08 | 0.85 | Widest spread | | BATS | 12% | 0.95 | 0.90 | Tightest spread | | EDGX | 8% | 0.98 | 0.75 | Low depth | | IEX | 6% | 1.02 | 0.95 | Slightly wider spread | | MEMX | 4% | 0.97 | 0.65 | Lowest depth |

Spread and depth multipliers drive realistic fill behaviour: BATS gets the tightest fills, MEMX runs out of book first. XNAS (Nasdaq) is the reference venue for both multipliers. XNYS (NYSE) has the deepest simulated book but a slightly wider spread. ARCX (Arca) carries the widest simulated spread. BATS (Cboe BZX) and EDGX (Cboe EDGX) apply a maker-bias rebate, with EDGX combining a tight spread with low depth. IEX's famous 350-µs speed bump is not modelled. MEMX has the lowest depth of the seven venues.

When a child order arrives at the EMS without an explicit venue, pickWeightedVenue() selects one weighted by the table above. The weights approximate published US equity market-share numbers.

flowchart LR
A[Parent order accepted] --> B{algo == SNIPER?}
B -- no --> C[Algo emits orders.child<br/>without venue]
C --> D[EMS pickWeightedVenue]
D --> E[Child filled per<br/>venue spread + depth]

Every other algorithm flows through this path. The user does not pick a venue.

SNIPER does its own routing inside the algorithm. On each price trigger:

  1. Score every venue's effectivePrice from its order-book top-of-book for the side
  2. Pick the top maxVenues (1-3, default 2) by best price
  3. Split aggressionPct% (default 80) of the remaining quantity across those venues simultaneously
  4. Enforce a 2-second cooldown between routing events

This produces measurable maker-rebate capture on BATS/EDGX when prices line up and rotates venues as the order book moves.

| Param | Default | Range | Effect | | --- | ---: | ---: | --- | | aggressionPct | 80 | 1-100 | Percentage of remaining quantity routed per trigger. Higher = closer to TWAP behaviour. | | maxVenues | 2 | 1-3 | How many venues to split across. 1 = single best, 3 = always spread three ways. |

The 2-second cooldown is a hard-coded constant (COOLDOWN_MS in sniper-strategy.ts); not user-tunable.

Once a child order lands on a venue, computeFill determines what fills:

  • filledQty = min(orderQty, tickVolume * participationCap * venueDepthMult)
  • participationCap is 20% by default (EMS_PARTICIPATION_CAP env override)
  • Market impact in basis points: (filledQty / 1000) * impactPer1000 * spreadMult
  • BATS and EDGX have a 65% chance of being marked MAKER (vs 40% elsewhere), which awards the MAKER_REBATE_PER_SHARE rebate

Every fill carries a MAKER / TAKER / CROSS flag, surfaced in the AlgoMonitor's performance card. The distribution is biased per venue to reflect rebate structure:

  • BATS, EDGX: 65% MAKER, ~30% TAKER, ~5% CROSS
  • All other venues: 40% MAKER, ~55% TAKER, ~5% CROSS

A SNIPER child specifically routed to BATS will, in aggregate, capture more maker rebates than a TWAP child routed at random.