When to Use
Use this skill in post-trade Transaction Cost Analysis (TCA) and algorithmic execution reviews, when you already know an order cost money and need to know which half of the execution stack to fix: the path between the PM's decision and the order reaching the venue, or the algorithm's behaviour once it was working the order.
It splits the executed leg of Implementation Shortfall into two additive components, both expressed as cost-signed basis points of the decision price $P_{\text{decision}}$ (positive = money lost, for buys and sells):
- Timing / delay slippage — what the price did between decision and arrival, before the algorithm had any influence.
- Sizing / market-impact slippage — what the price did between arrival and the final fill, while the order was being worked.
When NOT to Use
- As a complete Implementation Shortfall. Perold (1988) decomposes IS into delay cost + trading cost + opportunity cost + explicit fees. This engine measures the first two — the price cost on shares that actually filled. It never sees an end-of-horizon price, so it cannot compute opportunity cost on the unfilled residual, and it takes no commissions or taxes. For the full four-component shortfall use
implementation-shortfall-minimization. - On a materially underfilled order, as the headline cost number. At a 40% fill the opportunity cost on the missing 60% can exceed everything reported here. The report sets
is_partial_filland excludes that term; do not present the remainder as the cost of the order. - As proof that the dispatch path is slow. The timing component is whatever the price did during the delay. Over a short delay on a liquid name that is mostly drift and news, not latency. Correlate the bps figure with
delay_secondsacross many trades before re-engineering anything. - As a market-adjusted measure. Neither component is decontaminated of index/beta movement, so a timing figure measured during a broad market move is partly beta. Pair with
benchmark-relative-performance-attributionif you need the stock-specific part. - As an automatic control input.
strategy_action_recommendationis a single-trade triage hint, not a risk control. Retuning a live participation ceiling from one trade's attribution fits noise; seeexecution-algo-parameter-optimization-via-backtest.
Prerequisites
- Trade execution details:
side('BUY'or'SELL'— nothing else is accepted),order_qty, optionalfilled_qty(defaults to a full fill),decision_price,arrival_price,average_exec_price(quantity-weighted). - All three prices finite and strictly positive, on the same quotation basis and currency.
- Timestamps
decision_time_iso,arrival_time_iso,completion_time_isoas timezone-aware ISO-8601 strings, in non-decreasing order. Naive timestamps are rejected. - A materiality threshold in bps (default 1.0) below which a component counts as noise rather than a driver. This is a desk reporting convention, not a standard — set it from your own cost distribution.
Workflow
-
Validate before computing. Reject non-finite or non-positive prices, unrecognised sides, non-positive or over-filled quantities, and naive or out-of-order timestamps.
- Decision point — never attribute unvalidated data. A NaN price makes every comparison in the classifier false, which lands the trade in the "nothing material" branch and reports corrupt input as
OPTIMAL. Fail loudly; a TCA engine that cannot compute a cost must not emit a clean bill of health. - Decision point — an unrecognised
sideis a data error, not a sell. Defaulting anything that is not'BUY'to $-1$ turns a $+70$ bps cost on a mistyped buy into a $-70$ bps gain of identical magnitude.
- Decision point — never attribute unvalidated data. A NaN price makes every comparison in the classifier false, which lands the trade in the "nothing material" branch and reports corrupt input as
-
Decompose, normalising every term on $P_{\text{decision}}$ (with $\text{Side} = +1$ for BUY, $-1$ for SELL):
- $\text{IS}{\text{total}} = \text{Side} \times \frac{\bar{P}{\text{exec}} - P_{\text{decision}}}{P_{\text{decision}}} \times 10{,}000$
- $\text{IS}{\text{timing}} = \text{Side} \times \frac{P{\text{arrival}} - P_{\text{decision}}}{P_{\text{decision}}} \times 10{,}000$
- $\text{IS}{\text{sizing}} = \text{Side} \times \frac{\bar{P}{\text{exec}} - P_{\text{arrival}}}{P_{\text{decision}}} \times 10{,}000$
- Decision point — the sizing term is divided by $P_{\text{decision}}$, not $P_{\text{arrival}}$. That single choice is what makes the decomposition additive. Normalising the impact leg on the arrival price is defensible in isolation but breaks $\text{IS}{\text{total}} \equiv \text{IS}{\text{timing}} + \text{IS}_{\text{sizing}}$.
- Verify the identity in full precision, then round for reporting — and report the directly computed total, not the sum of the rounded components.
-
Weight to the intended notional if the order underfilled. Canonical IS divides by $Q_{\text{order}} \times P_{\text{decision}}$, so the per-share cost contributes only $Q_{\text{filled}} / Q_{\text{order}}$ of itself:
executed_is_contribution_bps$= \text{IS}_{\text{total}} \times \text{fill ratio}$.- Decision point — if
is_partial_fillis true, this report is not the order's total cost. Retrieve the opportunity cost separately before quoting a headline number.
- Decision point — if
-
Rank by adverse cost, never by absolute magnitude.
- A component that made money is never a slippage driver. Ranking by $|\cdot|$ lets a $-50$ bps timing gain outrank a $+20$ bps sizing cost and recommend
ACCELERATE_ORDER_DISPATCH— advice that would forfeit the gain and leave the only real cost untouched. - Neither component materially adverse $\implies$
FAVORABLE_EXECUTION(total materially negative) orZERO_SLIPPAGE. - Both materially adverse and within the tie band $\implies$
BOTH_DRIVERS_MATERIAL(REDUCE_DELAY_AND_PARTICIPATION). An exact tie is not zero slippage: $+50/+50$ is 100 bps of real cost. - Otherwise the larger adverse component wins:
TIMING_DRIVEN_SLIPPAGE(ACCELERATE_ORDER_DISPATCH) orSIZING_DRIVEN_SLIPPAGE(REDUCE_PARTICIPATION_RATE_CEILING), withsecondary_driver_materialset when the loser is also adverse — fixing one leg then leaves most of the cost in place.
- A component that made money is never a slippage driver. Ranking by $|\cdot|$ lets a $-50$ bps timing gain outrank a $+20$ bps sizing cost and recommend
-
Report contribution shares against gross cost $|\text{IS}{\text{timing}}| + |\text{IS}{\text{sizing}}|$, not against the net total, so offsetting legs cannot produce percentages in the thousands.
-
Audit Report Generation: output structured
SlippageAttributionAuditReport, carrying the materiality threshold the verdict was judged against.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Reporting corrupt data as
OPTIMAL: with NaN prices every bps figure is NaN, and bothabs(nan) > abs(nan)comparisons evaluate false, so a naive classifier falls through to its "nothing material" branch. The worst input produces the most reassuring output. Validate prices before attributing. - Treating an exact tie as zero slippage: a classifier built from two strict
>comparisons has no branch for $|\text{timing}| = |\text{sizing}|$. A $+50/+50$ split — 100 bps of genuine cost — silently lands in the else-branch labelled "minimal slippage". - Ranking components by absolute value: this promotes a favourable leg to "primary slippage driver" and inverts the recommendation. Only adverse components can drive remediation.
- Reversing sign conventions for sell orders: mapping "anything not BUY" to $-1$ means one typo (
'BUYY','B','LONG', an empty string) reports a cost as a gain of exactly the same size, and nothing in the output looks wrong. - Dividing contribution shares by the net total: when the legs offset, $+500$ bps timing against $-499$ bps sizing yields $50{,}000%$ and $-49{,}900%$. Normalise on gross cost.
- Claiming the identity is verified while substituting the sum for the total: writing
total = timing + sizingguarantees the printed numbers agree and checks nothing. Verify in full precision and report the independently computed total. - Conflating delay slippage with market impact: blaming the execution algorithm when most of the cost accrued before the order ever reached the venue.
- Quoting a partial fill's per-share cost as the order's cost: it omits opportunity cost on the residual and overstates the contribution to IS by $Q_{\text{order}} / Q_{\text{filled}}$.
- Measuring the delay from naive timestamps: across a DST transition or between venues in different zones the duration is silently wrong, which makes an
ACCELERATE_ORDER_DISPATCHrecommendation unfalsifiable. - Warning on every trade: logging each routine attribution at WARNING buries the material ones under thousands of lines in a batch run.
Verification
- Timing-driven BUY: $P_{\text{decision}} = $100.00$, $P_{\text{arrival}} = $100.50$, $\bar{P}_{\text{exec}} = $100.70$ $\Rightarrow$ total $= +70.0$ bps, timing $= +50.0$ bps, sizing $= +20.0$ bps, shares $71.4% / 28.6%$, driver
TIMING_DRIVEN_SLIPPAGE, recommendationACCELERATE_ORDER_DISPATCH,secondary_driver_materialtrue. - Sign convention: a SELL at $P_{\text{decision}} = $100.00$, $P_{\text{arrival}} = $99.90$, $\bar{P}_{\text{exec}} = $99.20$ gives $+80.0$ bps of cost ($+10.0$ timing, $+70.0$ sizing); the same SELL filled at $$100.50$ gives $-50.0$ bps and
FAVORABLE_EXECUTION. - Tie: $$100.00 \to $100.50 \to $101.00$ must yield
BOTH_DRIVERS_MATERIAL/REDUCE_DELAY_AND_PARTICIPATIONat $+100.0$ bps — neverZERO_SLIPPAGE. - Favourable leg: $$100.00 \to $99.50 \to $99.70$ (timing $-50.0$, sizing $+20.0$) must yield
SIZING_DRIVEN_SLIPPAGE, notTIMING_DRIVEN_SLIPPAGE. - Offsetting legs: $$100.00 \to $105.00 \to $100.01$ must keep both contribution shares within $[-100%, 100%]$.
- Partial fill: 4,000 of 10,000 filled at the timing-driven prices above gives
fill_ratio$= 0.4$,is_partial_filltrue,executed_is_contribution_bps$= 28.0$ whiletotal_is_slippage_bpsstays $+70.0$. - Rounding: a SELL at $$1234.56 \to $1240.01 \to $1231.77$ reports timing $-44.15$, sizing $+66.74$, total $+22.60$ — the directly computed total, one ulp above the $22.59$ sum of the rounded parts.
- Negative checks: non-finite or non-positive prices, an unrecognised
side,filled_qty$\le 0$ or $>$order_qty, naive/malformed/out-of-order timestamps, and a negative materiality threshold must each raise. - Run
python -m unittest discover -s skills/execution-slippage-attribution-timing-vs-sizing/scriptsand confirm all tests pass.
Related Skills
implementation-shortfall-minimization— the full Perold decomposition including opportunity cost and explicit fees.post-trade-execution-quality-scorecardexecution-algo-parameter-optimization-via-backtesttransaction-cost-analysis-tca-integration