For AI agents: the complete documentation index is at llms.txt. Every page is also available as markdown by appending .md to its URL, or by sending an Accept: text/markdown request header.

Aggregated slippage by venue and counterparty

The per-fill slippage recipe measures slippage on individual trades. This recipe aggregates those measurements to answer higher-level questions: which ECN gives you the best execution? Which counterparties are cheapest to trade against? Do passive fills outperform aggressive ones?

Problem​

You want to compare average execution quality across different dimensions — venue (ECN), counterparty, and order type (passive vs aggressive) — to identify where you get the best and worst fills.

Solution​

Group slippage calculations by the dimensions of interest and compute averages:

Aggregate slippage by ECN, counterparty, and passive/aggressiveDemo this query
SELECT
t.symbol,
t.ecn,
t.counterparty,
t.passive,
count() AS trade_count,
sum(t.quantity) AS total_qty,
avg(
CASE t.side
WHEN 'buy' THEN (t.price - (m.best_bid + m.best_ask) / 2)
/ ((m.best_bid + m.best_ask) / 2) * 10000
WHEN 'sell' THEN ((m.best_bid + m.best_ask) / 2 - t.price)
/ ((m.best_bid + m.best_ask) / 2) * 10000
END
) AS avg_slippage_vs_mid_bps,
avg(
CASE t.side
WHEN 'buy' THEN (t.price - m.best_ask) / m.best_ask * 10000
WHEN 'sell' THEN (m.best_bid - t.price) / m.best_bid * 10000
END
) AS avg_slippage_vs_tob_bps,
avg(
(m.best_ask - m.best_bid)
/ ((m.best_bid + m.best_ask) / 2) * 10000
) AS avg_spread_bps
FROM fx_trades t
ASOF JOIN market_data m ON (symbol)
WHERE t.timestamp IN '$yesterday'
GROUP BY t.symbol, t.ecn, t.counterparty, t.passive
ORDER BY avg_slippage_vs_mid_bps DESC;

How it works​

This builds on the same ASOF JOIN approach from the per-fill slippage recipe, but wraps the slippage calculations in avg() and groups by the dimensions you want to compare.

The three metrics per group:

  • avg_slippage_vs_mid_bps — average cost relative to mid price. Includes half the spread as baseline.
  • avg_slippage_vs_tob_bps — average cost beyond the top of book. Isolates execution quality from spread cost.
  • avg_spread_bps — average spread at the time of each trade. Helps contextualize slippage: high slippage in a wide-spread environment is different from high slippage in a tight market.

Results are ordered worst-first (DESC) so the most expensive groups appear at the top.

Variations​

By ECN only​

Drop counterparty to get a cleaner venue-level comparison:

Slippage by ECN and passive/aggressiveDemo this query
SELECT
t.ecn,
t.passive,
count() AS trade_count,
round(avg(
CASE t.side
WHEN 'buy' THEN (t.price - (m.best_bid + m.best_ask) / 2)
/ ((m.best_bid + m.best_ask) / 2) * 10000
WHEN 'sell' THEN ((m.best_bid + m.best_ask) / 2 - t.price)
/ ((m.best_bid + m.best_ask) / 2) * 10000
END
), 3) AS avg_slippage_bps
FROM fx_trades t
ASOF JOIN market_data m ON (symbol)
WHERE t.timestamp IN '$yesterday'
GROUP BY t.ecn, t.passive
ORDER BY t.ecn, t.passive;

Time-bucketed analysis​

Add SAMPLE BY to see how execution quality changes throughout the day:

Hourly slippage by ECNDemo this query
SELECT
t.timestamp,
t.ecn,
count() AS trade_count,
round(avg(
CASE t.side
WHEN 'buy' THEN (t.price - (m.best_bid + m.best_ask) / 2)
/ ((m.best_bid + m.best_ask) / 2) * 10000
WHEN 'sell' THEN ((m.best_bid + m.best_ask) / 2 - t.price)
/ ((m.best_bid + m.best_ask) / 2) * 10000
END
), 3) AS avg_slippage_bps
FROM fx_trades t
ASOF JOIN market_data m ON (symbol)
WHERE t.timestamp IN '$yesterday'
SAMPLE BY 1h;

Cost by size bucket​

How does execution cost scale with order size? Bucket fills by quantity, then use HORIZON JOIN with PIVOT to see markout and spread at multiple horizons in a single wide row per symbol and bucket:

Cost by size bucket — pivoted (buy side)Demo this query
WITH fills AS (
SELECT
t.symbol,
t.price,
t.quantity,
h.offset,
(m.best_bid + m.best_ask) / 2 AS mid,
m.best_ask - m.best_bid AS spread,
CASE
WHEN t.quantity < 100000 THEN 'S'
WHEN t.quantity < 1000000 THEN 'M'
WHEN t.quantity < 10000000 THEN 'L'
ELSE 'XL'
END AS size_bucket
FROM fx_trades t
HORIZON JOIN market_data m ON (symbol)
LIST (0, 5s, 1m) AS h
WHERE t.side = 'buy'
AND t.timestamp IN '$yesterday'
)
SELECT * FROM fills
PIVOT (
count() AS n,
avg((mid - price) / price * 10000) AS markout_bps,
avg(spread / mid * 10000) AS spread_bps
FOR offset IN (0 AS at_fill, 5000000000 AS t_5s, 60000000000 AS t_1m)
GROUP BY symbol, size_bucket
)
ORDER BY symbol, size_bucket;

The result has columns like at_fill_n, at_fill_markout_bps, t_5s_markout_bps, t_1m_spread_bps, etc. Compare across size buckets:

  • Markout degradation with size: If t_5s_markout_bps becomes more negative as bucket size increases, larger fills are systematically more toxic — the market moves against you more after big trades.
  • Spread widening with size: If at_fill_spread_bps increases for larger buckets, you're trading in wider markets when you trade big — possibly because you only get filled on large clips when spreads are wide.
  • Sample size caveat: XL buckets may have very few fills. Check at_fill_n before drawing conclusions.

Adjust the bucket thresholds to match your typical trade sizes. The boundaries above (100K / 1M / 10M) are reasonable for major FX pairs.

Counterparty cost attribution​

Which counterparties are the most expensive to trade with, all-in? Group by counterparty, ECN, and passive/aggressive, then pivot across horizons to see whether the cost is immediate (spread) or delayed (adverse selection):

Counterparty cost attribution — pivoted (buy side)Demo this query
WITH cp_costs AS (
SELECT
t.symbol,
t.counterparty,
t.ecn,
t.passive,
t.price,
t.quantity,
h.offset,
m.best_bid,
m.best_ask,
(m.best_bid + m.best_ask) / 2 AS mid
FROM fx_trades t
HORIZON JOIN market_data m ON (symbol)
LIST (0, 5s, 1m) AS h
WHERE t.side = 'buy'
AND t.timestamp IN '$yesterday'
)
SELECT * FROM cp_costs
PIVOT (
count() AS fills,
sum(quantity) AS volume,
avg((mid - price) / price * 10000) AS markout_bps
FOR offset IN (0 AS at_fill, 5000000000 AS t_5s, 60000000000 AS t_1m)
GROUP BY symbol, counterparty, ecn, passive
)
ORDER BY t_1m_markout_bps;

Ordered by t_1m_markout_bps ascending, the most toxic counterparties appear first. Read the results across horizons:

  • Large negative at_fill_markout_bps that stays flat: You paid a wide spread upfront but the market didn't move further. The cost is the spread, not adverse selection — this counterparty is expensive but not toxic.
  • Small negative at_fill_markout_bps that deepens at t_5s and t_1m: The initial fill looked reasonable, but the market moved against you afterwards. This counterparty is delivering informed or toxic flow.
  • Passive rows with deepening negative markout: The counterparty is systematically picking off your resting orders just before the market moves. This is the most actionable signal — consider tightening or withdrawing quotes to this counterparty on the affected ECN.

Intraday cost profile​

When is it cheapest to trade? Group by hour(t.timestamp) and pivot across horizons to build a heatmap of execution cost throughout the day:

Intraday cost profile — hourly heatmap (buy side)Demo this query
WITH hourly AS (
SELECT
t.symbol,
t.price,
t.quantity,
hour(t.timestamp) AS hour_utc,
h.offset,
m.best_bid,
m.best_ask,
(m.best_bid + m.best_ask) / 2 AS mid
FROM fx_trades t
HORIZON JOIN market_data m ON (symbol)
LIST (0, 5s, 1m) AS h
WHERE t.side = 'buy'
AND t.timestamp IN '$yesterday'
)
SELECT * FROM hourly
PIVOT (
count() AS n,
avg((mid - price) / price * 10000) AS markout_bps,
avg((best_ask - best_bid) / mid * 10000) AS spread_bps
FOR offset IN (0 AS at_fill, 5000000000 AS t_5s, 60000000000 AS t_1m)
GROUP BY symbol, hour_utc
)
ORDER BY symbol, hour_utc;

Each row is one symbol-hour combination with fill count, markout, and spread at each horizon. Look for:

  • Spread spikes: Hours with high at_fill_spread_bps are wide-market periods (typically Asia session for EUR/USD, or around fixes and rollovers). Execution during these windows is inherently more expensive.
  • Markout divergence: If t_1m_markout_bps is significantly worse during certain hours while at_fill_spread_bps is similar, the problem isn't wider spreads — it's adverse selection concentrated in those hours. Route less flow or quote wider during those windows.
  • Session boundaries: The London/NY overlap (12:00–16:00 UTC) typically shows the tightest spreads and flattest markouts for major pairs. Deviations from this pattern are worth investigating.

Daily P&L attribution​

Roll up execution costs into a daily P&L view per symbol and ECN. Unlike the bps-based metrics above, this uses absolute P&L ((mid - price) * quantity) so you can see dollar impact:

Daily P&L attribution (buy side)Demo this query
WITH daily AS (
SELECT
t.symbol,
t.ecn,
t.price,
t.quantity,
t.timestamp::date AS trade_date,
h.offset,
(m.best_bid + m.best_ask) / 2 AS mid
FROM fx_trades t
HORIZON JOIN market_data m ON (symbol)
LIST (0, 1m, 5m) AS h
WHERE t.side = 'buy'
AND t.timestamp IN '$yesterday'
)
SELECT * FROM daily
PIVOT (
count() AS fills,
sum(quantity) AS volume,
sum((mid - price) * quantity) AS pnl
FOR offset IN (0 AS at_fill, 60000000000 AS t_1m, 300000000000 AS t_5m)
GROUP BY trade_date, symbol, ecn
)
ORDER BY trade_date, symbol, ecn;

Each row is one date-symbol-ECN combination. The three P&L columns tell different stories:

  • at_fill_pnl — immediate spread cost. How much you lost to the bid-ask spread at the moment of execution.
  • t_5m_pnl — realized P&L including short-term market impact. This is the more complete measure of execution cost.
  • t_5m_pnl - at_fill_pnl — post-fill market movement. Positive means the market moved in your favor after the fill (mean reversion); negative means adverse selection eroded your position further.

Track these daily to spot trends. A venue that shows deteriorating t_5m_pnl over several days may be attracting more informed flow, even if at_fill_pnl stays stable.

Interpreting results​

  • Passive vs aggressive: Passive fills (limit orders) typically show lower or negative slippage since they provide liquidity. Aggressive fills (market orders) cross the spread and show higher slippage.
  • ECN differences: Venues with deeper liquidity tend to show lower slippage for large orders. Differences in latency and matching engine behavior also play a role.
  • Counterparty patterns: Some counterparties may consistently offer better or worse fills. Persistent adverse slippage from a counterparty may indicate information asymmetry.
  • Spread context: Always consider avg_spread_bps alongside slippage. An ECN with higher slippage but tighter spreads may still offer better all-in execution cost.