himanshuj16/algo-trading-skills/adverse-selection-measurement-for-passive-orders
- community
Checking that the SKILL.md file is still in the repository.
Use when passive fills keep happening just before the market moves against you, measuring forward markouts in basis points against the prevailing mid at each horizon. Aggressive orders pay the spread instead and are measured elsewhere.
Install
npx vskill@latest install himanshuj16/algo-trading-skills/adverse-selection-measurement-for-passive-ordersSKILL.md
When to Use
Invoke this skill to evaluate the execution quality of passive / liquidity- providing algorithms — market makers, passive limit strategies, and any resting-order logic. If your fills consistently happen right before the market moves against you (you buy, price drops; you sell, price rises), you are suffering adverse selection: informed flow is picking off your resting quotes, and you are writing a free option to the informed (Glosten & Milgrom, 1985).
The skill produces a MarkoutEngine that computes, for each fill and each forward horizon, the basis-point price drift from the fill (or from the fill- time mid) to the future mid, then aggregates into a per-horizon markout curve with distribution statistics. A persistently negative curve = toxic flow.
When NOT to Use
- Active / aggressing orders (market orders, crossing sweeps). Adverse
selection is a passive phenomenon — you were resting and got picked off. Active orders pay the spread up front; measure that with execution-slippage- attribution-timing-vs-sizing or implementation-shortfall-minimization.
- Latency-arbitrage diagnostics. A sharp negative markout in the first
1–10 ms is a symptom of stale-quote latency arbitrage against you, but the fix lives in the feed-handler/cancellation path (tick-to-trade-latency- measurement), not here. Use this skill to detect, then route to the latency skill to remediate.
- Alpha / signal research. Markouts measure execution friction, not
predictive power. For signal strength use backtest-reporting-standardized- tearsheet or factor research skills.
- Markets without a clean mid (some OTC, illiquid single-name options with
wide stale quotes). The mid-to-mid markout is only as honest as the mid; a stale or gappy mid produces nonsense markouts. Use multi-source-price- reconciliation-tie-breaking to fix the mid first.
- Tick-by-tick attribution of a handful of fills. With < ~30 fills per
horizon the mean is noisy; report the distribution (median, IQR) and do not over-interpret the sign of the mean.
Prerequisites
- Python 3.10+,
numpy. - A ledger of passive fills — each a `(trade_id, timestamp, side, fill_price,
quantity)`. Active/aggressing fills must be filtered out upstream; this engine assumes every fill it receives is passive.
- A high-resolution mid-price series
(timestamp, mid)covering every fill
timestamp and max(horizons) seconds forward. Timestamps must be strictly ascending, finite, with positive mids (validated at evaluation time).
- Clock alignment between the fill ledger and the market-data series —
same epoch, same time base. See clock-skew-correction-for-tick-timestamps.
Workflow
- Filter to passive fills. Exclude market/aggressing orders upstream;
adverse selection only applies to resting liquidity.
- Configure horizons and basis:
```python from adverse_selection_measurement_for_passive_orders import ( MarkoutEngine, MarkoutConfig, PassiveFill, )
config = MarkoutConfig( horizons_sec=[0.1, 1.0, 5.0, 60.0], # 100ms, 1s, 5s, 1m markout_basis="fill_to_mid", # or "arrival_to_mid" (mid-to-mid) quantity_weighted=True, # share-weighted MEAN only require_asof_mid=True, # no-lookahead guard, both bases max_mid_staleness_sec=None, # set it; see step 2b ) engine = MarkoutEngine(config) ```
Set the staleness bound deliberately. max_mid_staleness_sec is None (off) by default because no universal value exists — the engine will not invent one. Derive it from the instrument's own quote-update cadence: a bound well above the typical inter-quote gap catches only session gaps, halts and dead feeds; a bound near that gap discards live data. Left unset, a markout can be measured against a mid from before a halt and still look like clean evidence.
- Evaluate:
``python report = engine.evaluate_fills(fills, market_timestamps, market_mids) ``
- Check the data verdict before the toxicity verdict.
report.has_sufficient_data/report.evaluable_horizons— how many
horizons actually produced a markout. If this is 0, `is_toxic=False` means "not measured", not "healthy" — the message reads INSUFFICIENT DATA. Never gate on is_toxic without checking this first.
report.missing_pre_fill— fills skipped by the no-lookahead guard.report.stale_asof_mid— fills skipped because the fill-time mid was
older than max_mid_staleness_sec.
report.stats[h].truncated— fills dropped at horizonhbecause the
market data did not cover fill_ts + h.
report.stats[h].stale— fills dropped athbecause the prevailing mid
there was over-age.
Then read the curve:
report.average_markouts_bps[h]— mean markout per horizon (backward-compat).report.stats[h]—count, mean, median, p25, p75, std, truncated, stale.
Only mean_bps honours quantity_weighted; the median, quartiles and std are always unweighted order statistics.
report.is_toxic— True if a majority of the evaluable horizons have a
negative mean. Horizons with no data are excluded entirely rather than counted as healthy.
report.toxicity_ratio— negative share of the evaluable horizons.
- Diagnose the shape (see Decision Points):
- Sharp negative in the first 100 ms → stale-quote / latency arbitrage.
- Gradual negative over seconds–minutes → directional adverse selection
(your alpha is wrong or you are on the wrong side of informed flow).
- Positive at short horizons, negative later → you capture the spread but
bleed to informed flow over the holding period.
Decision Points
| Situation | Action | |
|---|---|---|
| Curve sharply negative in first 100 ms | Stale-quote latency arbitrage. Tighten feed-handler / cancellation latency (tick-to-trade-latency-measurement); widen quote skew in fast markets. | |
| Curve slopes negative over 1–5 min | Directional adverse selection — your alpha or quote side is wrong. Re-examine the signal, not the plumbing. | |
| Positive short, negative long horizon | You earn the spread but leak to informed flow. Consider faster scratch-outs / hedging, or shorter holding period. | |
evaluable_horizons == 0 / has_sufficient_data False | Not a healthy-flow result. Nothing was measured. Extend the market-data window and re-run before drawing any conclusion; never feed this verdict to a gate. | |
| Some horizons evaluable, others not | The verdict covers the evaluable ones only. Check stats[h].truncated / .stale per horizon before comparing the curve day-over-day — the shape is not comparable across different evaluable sets. | |
stale_asof_mid > 0 or stats[h].stale > 0 | The prevailing mid was older than max_mid_staleness_sec — a session gap, halt, or dead feed. Do not lower the bound to make the count go away; fix the feed or exclude the period. | |
missing_pre_fill > 0 | Some fills have no as-of mid (market data starts after the fill). Extend the market-data window backward, or fix clock alignment (clock-skew-correction-for-tick-timestamps). | |
stats[h].truncated > 0 | Market data ends before fill_ts + h; the horizon's mean is computed on the surviving fills only and is biased. Extend the window forward by max(horizons). | |
| Mean negative but median positive | Bimodal: a few badly-selected fills dominate the mean. Inspect the distribution; consider a robust threshold on the median, not the mean. | |
quantity_weighted flips the verdict vs unweighted | Large fills are being selected differently from small ones. Route the size dimension to queue-position-modeling-for-passive-orders. | |
arrival_to_mid curve negative but fill_to_mid positive | Your fills are better than mid (price improvement) but the mid drifts against you afterward — pure adverse selection on the resting side. | |
| Few fills (<30/horizon) | Distribution is noisy. Report median + IQR; do not gate on the mean sign. |
Common Pitfalls
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/adverse-selection-measurement-for-passive-orders/SKILL.md
Popularity
- Repository stars
- 50
- Repository forks
- 7
- 7-day trend score
- 0
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/himanshuj16/algo-trading-skills/adverse-selection-measurement-for-passive-orders)