Skip to main content

mts1b-oms-algos

Execution algorithms used by mts1b-oms: VWAP, TWAP, POV, Iceberg, Almgren-Chriss, Implementation Shortfall planners.

Repo: github.com/MTS1B/mts1b-oms-algos Layer: 3 Depends on: foundation, platform, quantkit Audience: mts1b-oms (one consumer)

What it is

Each algorithm is an async function that takes a parent Order + params and produces a stream of child orders. They share a uniform signature so OMS can dispatch by name.

Naming convention

This repo uses the parent-child naming pattern: mts1b-oms-algos is "the library used by mts1b-oms". It has no audience independent of OMS.

The contract

from typing import AsyncIterator
from mts1b_foundation.orders import Order


async def execute(parent: Order, *, params: dict) -> AsyncIterator[Order]:
"""Execute parent order via this algorithm.

Yields child orders to be submitted via mts1b-oms.
The algorithm owns: slice sizing, timing, child order types.
OMS owns: state, idempotency, broker routing for each child.
"""

API source: e.g. services/trading/src/mts/trading/modules/execution/algos/vwap.py:execute.

Algorithms

VWAP — Volume-Weighted Average Price

from mts1b_oms_algos.vwap import execute

async for child in execute(parent_order, params={
"horizon_minutes": 30,
"volume_profile": "intraday_estimated", # historical avg by minute
"max_participation": 0.10, # cap at 10% of bar volume
}):
await oms.submit(child)

Slices the parent across the horizon weighted by the venue's historical volume profile. Designed to minimize timing risk vs the average price during the window.

Child order types: limit at NBBO mid or marketable_limit at touch.

TWAP — Time-Weighted Average Price

from mts1b_oms_algos.twap import execute

async for child in execute(parent_order, params={
"horizon_minutes": 30,
"n_slices": 30, # 1 slice per minute
"jitter_pct": 0.2, # ±20% jitter on each slice timing
}):
...

Equal-sized slices at fixed intervals (with jitter to avoid signaling). Used when volume profile is unreliable or unknown.

API source: services/trading/src/mts/trading/modules/execution/algos/twap.py:execute.

POV — Participation of Volume

from mts1b_oms_algos.pov import execute

async for child in execute(parent_order, params={
"target_participation": 0.15, # 15% of incremental volume
"max_horizon_minutes": 120, # bail out after 2h
}):
...

Adaptive: as venue volume builds, child orders scale up. As volume thins, they slow. No fixed horizon — finishes when parent quantity is filled or max_horizon_minutes elapses.

Iceberg

from mts1b_oms_algos.iceberg import execute

async for child in execute(parent_order, params={
"display_qty": 100, # show 100 of e.g. 5000
"refill_threshold": 50, # refill when remaining drops below this
"limit_price": Decimal("180.50"),
}):
...

Hides parent quantity by only displaying a portion. When the displayed lot fills, the algorithm refreshes with the next slice.

Almgren-Chriss

from mts1b_oms_algos.almgren_chriss import execute

async for child in execute(parent_order, params={
"horizon_minutes": 30,
"risk_aversion": 1.0,
"volatility": 0.18, # annualized
"permanent_impact": 0.1, # bps per unit of parent size
"temporary_impact": 5.0, # bps per unit of slice size
}):
...

Optimal-execution from Almgren & Chriss (2000) — trades off market impact (faster execution costs more) vs timing risk (slower execution exposes you to price drift). Closed-form solution for the optimal trade trajectory.

API source: services/trading/src/mts/trading/modules/execution/algos/almgren_chriss.py:execute.

Implementation Shortfall (IS)

from mts1b_oms_algos.is_ import execute

async for child in execute(parent_order, params={
"arrival_price": Decimal("180.50"),
"risk_aversion": 1.0,
"max_horizon_minutes": 60,
}):
...

Targets minimum implementation shortfall = realized_avg_price - arrival_price. More aggressive than VWAP for urgent fills; less than market order.

When to use which

ScenarioAlgorithm
Standard rebalance, moderate size, want benchmarkVWAP
Schedule-driven, want predictable timingTWAP
Liquidity-driven, want to be a fraction of flowPOV
Hide size (large block)Iceberg
Optimal trade-off impact/timingAlmgren-Chriss
Urgent — minimize shortfall vs arrivalIS
Tiny order (under threshold)None — direct to broker

The OMS routing/splitter (mts1b-oms/routing/splitter.py) picks an algorithm based on size + urgency from the parent order's params.

Configuration

Each algorithm has sensible defaults. Override per-strategy:

mts1b.config
strategies:
large_cap_momentum:
execution:
algorithm: vwap
params:
horizon_minutes: 30
max_participation: 0.05

Risk

Child orders go through the full OMS pipeline — same risk gates as any order. The algorithm only decides the slice schedule; OMS still:

  • Dedupes via idempotency_key (each child gets a unique one)
  • Calls riskengine.CheckOrder
  • Routes to the broker
  • Tracks state independently

This means a faulty algorithm cannot bypass risk. The worst it can do is generate too many child orders → riskengine rejects the excess.

Build + test

pip install -e ".[dev]"
pytest -v # unit tests
pytest -m simulate # simulate against historical bar data

Tests verify:

  • Total child quantity = parent quantity
  • Last child completes within horizon (for time-bounded algos)
  • VWAP child schedule matches expected volume profile
  • Almgren-Chriss matches the closed-form solution

Roadmap

VersionItems
0.1 (Wave 1)VWAP, TWAP, POV, Iceberg, Almgren-Chriss, IS
0.2 (Wave 2)Adaptive POV with sniper override
0.3 (Wave 2)TCA hooks (post-trade)
0.4 (Wave 3)Reinforcement-learning sliced execution (research)
1.0 (LTS)Stable execution interface

See also