v1.0.0 to v2.0.0

57 added, 24 removed. Audit A to A.

---
name: exchange-tick-size-regime-tracking
description: >-
- Quantitative market data engine for tracking dynamic exchange tick size regimes (US SEC Rule 612, EU MiFID II RTS 11, DFM 2026 AED), aligning prices to valid tick steps, and auditing order tick compliance.
+ Use when an order price must be validated or aligned to a venue's minimum pricing increment — SEC Rule 612 penny/sub-penny bands for US NMS stocks, the two-dimensional MiFID II RTS 11 price x liquidity-band table for EU shares/DRs/ETFs, and the DFM AED tick bands — before the order is displayed, ranked, or accepted.
domain: Data Management Global
subdomain: Exchange Tick Rules & Price Increments
tags: ["tick-size", "exchange-rules", "mifid-ii-rts-11", "sec-rule-612", "dfm-tick", "price-alignment", "market-data"]
- brokers_frameworks: ["SEC Rule 612 Reg NMS", "MiFID II RTS 11", "DFM/ADX Rules", "Python Dataclasses"]
- version: "1.0.0"
+ brokers_frameworks: ["SEC Rule 612 (17 CFR 242.612)", "MiFID II RTS 11 (EU) 2017/588", "DFM Circular 02/2026", "Python Decimal"]
+ version: "2.0.0"
author: algo-trading-skills-contributors
license: Apache-2.0
---
## When to Use
- Use this skill in quantitative market data pipelines, Smart Order Routers (SOR), and algorithmic order entry engines. Different global exchanges enforce dynamic price-band dependent tick size regimes (e.g. US SEC Rule 612 sub-penny rules for $P < \$1.00$, EU MiFID II RTS 11 liquidity/price band tables, DFM 2026 AED step bands). Submitting off-tick orders triggers immediate venue rejection. This module tracks regime rules, calculates active tick sizes, and aligns order prices to valid tick steps.
+ Use this skill in market-data pipelines, Smart Order Routers, and order-entry gateways whenever a limit price is constructed programmatically — from a signal, a mid-price, a spread offset, or a repricing loop — and must be a legal increment on the destination venue before it is sent. An off-tick price is rejected by the matching engine (or, on a venue that silently rounds, filled at a price you did not choose), and on a regulated venue an impermissibly fine quote is a rule breach, not just a bad fill.
+ The three regimes shipped here are structurally different, and that difference is the point of the skill:
+
+ - **`US_EQUITIES` — SEC Rule 612 (17 CFR 242.612).** Price-driven: $\ge \$1.00 \implies \$0.01$, $< \$1.00 \implies \$0.0001$.
+ - **`EU_RTS11`** (alias `EU_XETRA`) **— MiFID II RTS 11.** *Not* price-driven. The tick is a cell in a 19 price ranges $\times$ 6 liquidity bands table; the liquidity band comes from the instrument's average daily number of transactions (ADNT) published by ESMA/the NCA.
+ - **`DFM_DUBAI` — DFM Circular 02/2026** (effective 2026-04-06). Five AED price bands, applying to listed equities, ETFs and REITs.
+
+ ## When NOT to Use
+
+ - **To validate an execution price.** Rule 612 governs *displaying, ranking, or accepting* bids, offers, orders and indications of interest. It does not govern executions: a midpoint fill at \$10.005 or a sub-penny price improvement is lawful. Auditing a fill price against this engine will flag legitimate executions as violations.
+ - **As a substitute for venue reference data.** Every table here is a regulatory *minimum*. RTS 11 requires ticks "equal to or greater than" the Annex value, and venues may publish coarser ticks per instrument or per segment. When the venue's own tick is known, pass it as `venue_assigned_tick`; the engine will use it and reject it only if it is finer than the regulatory floor.
+ - **For instruments outside each regime's scope.** RTS 11 covers shares, depositary receipts and ETFs on EU trading venues — not bonds, structured products or derivatives, which carry venue-specific step tables. Rule 612 covers NMS stocks, not options, futures or crypto.
+ - **For venues that are not registered.** The engine raises `UnknownVenueError` rather than defaulting to a penny. Register the venue with `register_venue()` from its published rulebook first.
+ - **For quantity/lot rounding** — see `minimum-fill-size-and-lot-rounding-logic`.
+
## Prerequisites
- - Exchange venue identifier (`venue_id`: `'US_EQUITIES'`, `'EU_XETRA'`, `'DFM_DUBAI'`).
- - Asset symbol & price ($P$).
- - European ADNT liquidity band (if applicable for EU RTS 11).
+ - Venue identifier (`US_EQUITIES`, `EU_RTS11` / `EU_XETRA`, `DFM_DUBAI`) or a regime registered via `register_venue()`.
+ - Proposed limit price, ideally as `str` or `Decimal`. Floats are accepted and read through their shortest repr, so `0.1 + 0.2` is treated as the `0.30000000000000004` it actually is.
+ - **For RTS 11 venues only:** the instrument's liquidity band 1–6, from the ESMA/NCA annual ADNT calculation. `liquidity_band_for_adnt()` maps a published ADNT to a band. ETFs whose underlyings are exclusively in-scope shares use band 6 (RTS 11 Article 3).
+ - Order `side` if the price will be sent as a live limit order (see step 2).
## Workflow
- 1. **Active Tick Size Lookup**:
- - Evaluate price $P$ against venue price band rules:
- - `US_EQUITIES`: $P \ge \$1.00 \implies \$0.01$, $P < \$1.00 \implies \$0.0001$.
- - `EU_XETRA`: $P < €10 \implies €0.001$, $€10 \le P < €50 \implies €0.005$, $P \ge €50 \implies €0.01$.
- - `DFM_DUBAI`: $P < 1.00 \implies 0.001$, $1.00 \le P < 10.00 \implies 0.01$, $10.00 \le P < 50.00 \implies 0.02$, $P \ge 50.00 \implies 0.05$.
- 2. **Price Alignment & Rounding**:
- - $P_{\text{aligned}} = \text{round}\left(\frac{P}{\text{tick}}\right) \times \text{tick}$.
- 3. **Tick Compliance Audit**:
- - If $|P - P_{\text{aligned}}| > 1e-6 \implies$ Flag `OFF_TICK_REJECTION`.
- - Else $\implies$ Flag `TICK_COMPLIANT`.
- 4. **Audit Report Generation**: Output structured `TickRegimeAuditReport`.
+ 1. **Resolve the active tick size** — `get_active_tick_size_decimal(venue, price, liquidity_band=..., tick_constrained=...)`.
+ - **Decision point — is the venue band-dependent?** An RTS 11 venue queried without `liquidity_band` raises `LiquidityBandRequiredError`. Do not paper over it with a default: at €25 the tick ranges from €0.005 (band 6) to €0.2 (band 1), a factor of 40. Guessing the liquid band under-ticks 5 of the 6 bands.
+ - **Decision point — is this a tick-constrained US symbol?** The amended Rule 612 \$0.005 increment is assigned per symbol by the listing exchange from a Time Weighted Average Quoted Spread $\le \$0.015$; it cannot be inferred from price, and it is **not yet operative** (SEC exemptive relief of 2026-06-11 defers compliance to the first business day of November 2027). Pass `tick_constrained=True` only when carrying a real assignment from reference data.
+ - **Decision point — do you hold the venue's own tick?** If so pass `venue_assigned_tick`; the regulatory table is a floor, not the venue's authoritative step.
+ 2. **Choose a rounding policy before aligning** — `align_price_to_tick_decimal(price, tick, side=..., policy=...)`.
+ - `PASSIVE` (BUY rounds down, SELL rounds up) is the correct default for live limit orders: it can never push a buy limit above the price the strategy asked for, and never turns a resting quote into a spread-crossing taker.
+ - `NEAREST` (round half up) is for reference/analytics prices where no order is being sent.
+ - `AGGRESSIVE` is a deliberate marketable reprice and *will* pay more or receive less than proposed.
+ - A price smaller than half a tick raises rather than aligning to zero or silently multiplying the limit.
+
+ 3. **Audit and re-check the band** — `audit_order_tick_compliance(...)` returns a `TickRegimeAuditReport`.
+ - Alignment can move a price *across* a band boundary: \$0.99999 rounds to \$1.0000, where the minimum increment becomes \$0.01 rather than \$0.0001. The engine re-resolves the tick at the aligned price, reports the tick that actually governs the price being sent, and sets `crossed_price_band`.
+ - `status` is one of `TICK_COMPLIANT`, `OFF_TICK_ALIGNED` (auto-aligned, `auto_align=True`), or `OFF_TICK_REJECTED` (`auto_align=False`; nothing is sent, and `aligned_price_decimal` shows what a legal price would have been).
+ - **Decision point — send `aligned_price_decimal`, not `aligned_price`.** The float mirror exists for logging and legacy callers; the `Decimal` is the exact value the gateway should serialise.
+
+ 4. **Record the audit trail.** The report carries `regulatory_source`, `liquidity_band`, `side`, `rounding_policy` and both exact `Decimal` values so a compliance reviewer can reconstruct why a price was changed.
+
> Full procedure: see `references/workflows.md`.
> Standards reference: see `references/standards.md`.
> Printable pre-flight checklist: see `assets/checklist.md`.
## Common Pitfalls
- - **Applying US Sub-Penny Rules to $P \ge \$1.00$ Shares**: Submitting $\$150.005$ on US equities, breaching SEC Rule 612 and causing venue order rejection.
- - **Ignoring European RTS 11 Dynamic Price Bands**: Hardcoding $€0.01$ tick steps across all European equities, missing sub-cent ticks (€0.001 / €0.005) for lower-priced stocks.
- - **Floating Point Rounding Errors**: Using raw floating-point division without explicit rounding, misclassifying valid on-tick prices as off-tick.
+ - **Treating RTS 11 as a price-only table.** The single most common error in EU order entry: a hardcoded price→tick map ignores the liquidity dimension entirely and is wrong for five of the six bands. It fails *silently* — the engine returns a plausible number and the venue rejects the order (or accepts an illegally fine quote).
+ - **Auditing fills against Rule 612.** Sub-penny *executions* from midpoint matching or price improvement are permitted; only quotes, orders and IOIs are constrained. Flagging a \$10.0050 midpoint print as a breach generates false compliance alerts.
+ - **Rounding a buy limit up.** Half-up rounding of \$150.005 gives \$150.01 — one cent above the instruction, on every share, and potentially through the offer. Use `PASSIVE` with a `side` for anything that will be routed.
+ - **Assuming the aligned price is still in the same band.** \$0.99999 aligns to \$1.0000 and leaves the sub-penny regime; a table lookup performed only on the pre-alignment price reports a \$0.0001 tick for a price that must now be a multiple of \$0.01.
+ - **Defaulting an unmapped venue to \$0.01.** A silent fallback produces off-tick orders on every venue with a finer or coarser step, and the failure surfaces as unexplained rejections in production, not at configuration time.
+ - **Float arithmetic in the price path.** `0.1 + 0.2` is not `0.3`, and a tolerance wide enough to hide that (e.g. `1e-6`) is 1% of a \$0.0001 tick — wide enough to wave through genuinely off-tick sub-dollar prices. Carry prices as `str`/`Decimal` end to end.
+ - **Hardcoding a regime and never revisiting it.** DFM widened the top of its table on 2026-04-06 (AED 100+ moved to a 0.10 step) and validated *existing open orders* against the new rules on the effective date. Tick tables change by circular; treat them as versioned reference data.
+ - **Reading the US \$0.005 tier as live.** It is adopted but under exemptive relief until the first business day of November 2027, and it is a per-symbol assignment — quoting a half-penny on an unassigned symbol is a Rule 612 breach.
## Verification
- - Instantiate `ExchangeTickSizeRegimeEngine`. Query US Equities @ \$150.00 $\implies$ verify tick = \$0.01. Query US Equities @ \$0.50 $\implies$ verify tick = \$0.0001. Query EU Xetra @ €25.00 $\implies$ verify tick = €0.005. Submit off-tick price (\$150.005 on US Equities). Verify engine flags `OFF_TICK_REJECTION` and provides aligned price (\$150.01).
- - Run `python scripts/test_exchange_tick_size_regime_tracking.py`.
+ - Instantiate `ExchangeTickSizeRegimeEngine()`. Query `US_EQUITIES` at \$150.00 $\implies$ \$0.01; at \$0.50 $\implies$ \$0.0001; at exactly \$1.00 $\implies$ \$0.01.
+ - Query `EU_RTS11` at €25.00 with `liquidity_band=1` $\implies$ €0.2 and with `liquidity_band=6` $\implies$ €0.005 (RTS 11 Annex, row $20 \le P < 50$). Query it *without* a band and confirm `LiquidityBandRequiredError`.
+ - Query `DFM_DUBAI` at AED 150.05 $\implies$ tick AED 0.10, `is_on_tick` false, aligned AED 150.10 (Circular 02/2026).
+ - Audit \$150.005 on `US_EQUITIES`: default `NEAREST` $\implies$ `OFF_TICK_ALIGNED` at \$150.01; `side='BUY', policy='PASSIVE'` $\implies$ \$150.00; `auto_align=False` $\implies$ `OFF_TICK_REJECTED`.
+ - Audit \$0.99999 on `US_EQUITIES` and confirm `aligned_price_decimal == Decimal('1.00')`, `active_tick_size_decimal == Decimal('0.01')`, `crossed_price_band` true.
+ - Negative checks: unknown venue, NaN/inf/zero/negative price, non-positive tick, `PASSIVE` without a side, a `venue_assigned_tick` finer than the regulatory floor, and a registered table with a gap must each raise.
+ - Run `python -m unittest discover -s skills/exchange-tick-size-regime-tracking/scripts` and confirm 100% pass rate.
## Related Skills
- `minimum-fill-size-and-lot-rounding-logic`
- `deutsche-borse-xetra-api-integration`
- ---
+ - `post-only-limit-repricing-under-fast-markets`
+ - `tick-size-pilot-program-impact-assessment`
+ - `reference-data-golden-source-designation`