OTC Market Data Aggregation: How EZRate Cleans Unstructured Quotes in Real Time
Inside OTC market data aggregation: collecting quotes, parsing units and sides, time windows, median-based outlier removal, jump guards and source counts.

- Iran’s FX market is an OTC market: quotes arrive as free-text messages and venue prices, not as a central feed.
- EZRate parses each message into asset, side, unit and market, keeps a short time window, drops quotes beyond ±1% of the median and publishes a weighted median every 60 seconds.
- Low-source flags and a jump guard make the output honest when data is thin or noisy.
Most price APIs sit on top of an exchange that already produces a clean ticker. Iran’s currency market has none. Prices exist as thousands of messages, board quotes and venue prices that use different units, formats and conventions. This article explains the OTC market data aggregation pipeline that turns that noise into one publishable rate per minute.
Part of the Iranian Rial exchange rate API guide. The public summary is on the methodology page.
Stage 1: collection
Sources include trading channels and groups, exchange-office boards, regional markets and crypto venues. The collector reads them continuously and stores every raw message with its source and timestamp, so any published rate can be traced back.
Stage 2: parsing unstructured text
A typical message mixes emojis, hashtags, Persian and Latin digits and abbreviations. The parser has to answer, for every line: which asset, which side, which unit, which market?
| Problem | Example | Handling |
|---|---|---|
| Digits and separators | ۱۰۵٬۲۰۰ / 105,200 / 105.2 | Normalise to Latin digits; infer thousands |
| Unit | Rial vs Toman, sometimes per line | Explicit markers, then plausibility vs current level |
| Side | “خرید / فروش”, arrows, one-sided lines | Keyword and position rules per source |
| Asset names | Synonyms and nicknames | Dictionary of aliases per asset |
| Market | Tehran, Herat, Dubai… | Title or hashtags of the message |
| Noise | Change values, ads, old reposts | Ignore non-price numbers; drop forwarded old messages |
Each source can have its own regex patterns with an automatic fallback, because formats change without notice.

Stage 3: time window
Only quotes from the last few minutes count toward the current rate. A ten-minute-old quote describes a different market. Edited messages replace their earlier values instead of counting twice.
Stage 4: outlier removal
Within the window, the pipeline computes the median per asset, market and side, and drops quotes more than about 1% away. The median is used because it is robust: a handful of fat-finger or manipulated quotes cannot move it while most sources are honest.
from statistics import median
def clean(quotes: list[float], band: float = 0.01) -> list[float]:
if not quotes:
return []
m = median(quotes)
return [q for q in quotes if abs(q / m - 1) <= band]The full statistical discussion, including MAD and Z-scores, is in outlier detection in price feeds.
Stage 5: weighted median
The remaining quotes are combined with a weighted median: sources with a longer track record of agreeing with the consensus weigh more, but no single source can decide the price.
Stage 6: confidence and guards
- Low-source flag: fewer than five valid sources → keep the previous rate, set
low_sources: true. - Jump guard: a move far beyond normal volatility must persist before it is published.
- Source count:
sourcesis published with every rate.

What consumers should do with these signals
- Display
updated_atand the source count next to rates. - Pause automatic repricing while
low_sourcesis true. - Treat a missing update as “unchanged”, not as zero.
Implementation patterns are in financial API failover.
US Dollar270,680Toman+0.40%Related live rates: US dollar rate, 18K gold price, Emami coin price.

Comments (0)
Comments are shown after review. Sign-in is needed to send.


