Trade Execution Modeling
Overview
Provide more realistic execution assumptions for backtests, including slippage models, market-impact estimation, and execution-algorithm principles. This skill is for backtest simulation only and does not involve live order execution.
Slippage Models
Why Slippage Models Are Needed
Idealized backtest: filled at the close, zero slippage Real world: 1. The order book has a bid-ask spread 2. Large orders push prices (market impact) 3. Execution is delayed (there is latency from signal to fill) No slippage model -> overly optimistic backtest -> losses in live trading
Do not retype these models. All four are implemented and tested in
src/quantlib/impact.py; import them. The tested versions validate their inputs —
a zero ADV raises instead of dividing by zero, and a negative delay_bars raises
instead of silently introducing look-ahead bias.
pythonfrom src.quantlib.impact import fixed_slippage, linear_impact, sqrt_impact, delayed_execution
1. Fixed Slippage Model
pythonfixed_slippage(price=100.0, direction=1, bps=5.0) # 100.05 (buy pays up) fixed_slippage(price=100.0, direction=-1, bps=5.0) # 99.95 (sell receives less)
direction is 1 to buy or -1 to sell, and must be exactly one of those — it
multiplies the impact, so an unchecked 2 would silently double the modelled cost.
bps defaults to DEFAULT_SLIPPAGE_BPS (5.0).
Reference fixed-slippage assumptions by market:
| Market | Instrument | Suggested Slippage (bps) | Notes |
|---|---|---|---|
| China A-share large cap | CSI 300 constituents | 3-5 | Good liquidity |
| China A-share small cap | CSI 1000 constituents | 5-10 | Average liquidity |
| China micro-cap | market cap < 5 billion RMB | 10-30 | Poor liquidity |
| US large cap | AAPL / MSFT | 1-3 | Excellent liquidity |
| Hong Kong stocks | Hang Seng constituents | 5-10 | Less liquid than A / US |
| BTC spot | BTC-USDT | 2-5 | Good OKX liquidity |
| ETH spot | ETH-USDT | 3-8 | Slightly worse than BTC |
| Small altcoins | other -USDT pairs | 10-50 | Liquidity varies widely |
2. Linear Impact Model
impact = impact_coeff × volume_traded / adv
python# 100k shares against 1M ADV = 10% participation; at coeff 0.1 that is a 1% move. linear_impact(price=100.0, direction=1, volume_traded=100_000, adv=1_000_000, impact_coeff=0.1) # 101.0
Marginal impact is constant here, which overstates the cost of very large orders.
impact_coeff defaults to DEFAULT_LINEAR_IMPACT_COEFF (0.1).
Reference impact coefficients:
| Market | impact_coeff | Notes |
|---|---|---|
| China A-share large cap | 0.05-0.10 | 10% daily price-limit system |
| China A-share small cap | 0.10-0.20 | Liquidity premium |
| US equities | 0.03-0.08 | Market-maker buffering |
| Crypto | 0.05-0.15 | 24h trading is dispersed |
3. Square-Root Impact Model
impact = η × σ × sqrt(volume_traded / adv)
python# 250k against 1M ADV = 25% participation; 0.5 × 0.02 × sqrt(0.25) = 0.005 = 50bps. sqrt_impact(price=100.0, direction=1, volume_traded=250_000, adv=1_000_000, volatility=0.02, eta=0.5) # 100.5 (100.49999999999999 in binary floating point)
volatility is daily return volatility as a decimal fraction. eta defaults to
DEFAULT_SQRT_IMPACT_ETA (0.5); 0.3-0.8 is the usual calibrated range.
Advantages of the square-root model:
- Strongest empirical support (standard in financial literature)
- Marginal impact declines for larger orders (intuitive)
- Parameters can be estimated from historical data
Naming. This impact term is often labelled "Almgren-Chriss", and it does come from that literature, but it is not Almgren-Chriss optimal execution. There is no trading trajectory, no permanent/temporary impact split and no risk-aversion parameter here, and none is implemented anywhere in this repository. Call it a square-root impact function, and do not claim an optimal schedule was computed.
Slippage Model Selection Decision Tree
Backtest capital vs instrument ADV: ├── Capital < 0.5% of ADV -> fixed slippage (5bps) is enough ├── Capital 0.5-5% -> linear impact model └── Capital > 5% -> square-root impact model (required)
Execution Algorithm Principles
VWAP (Volume Weighted Average Price)
Goal: execute at the day's volume-weighted average price VWAP = Σ(Price_i × Volume_i) / Σ(Volume_i) Execution logic: 1. Forecast the intraday volume profile (typically U-shaped) 2. Split the order according to the predicted profile 3. Execute proportionally in each time slice Typical China A-share VWAP volume profile (U-shaped): 09:30-10:00 15% (active open) 10:00-11:30 25% (normal morning session) 13:00-14:00 15% (weak afternoon session) 14:00-14:30 15% (afternoon recovery) 14:30-15:00 30% (active close) VWAP in backtests: - Daily backtest: use the VWAP field directly as the fill price - Minute backtest: simulate VWAP order slicing
TWAP (Time Weighted Average Price)
Goal: execute evenly over a specified time window TWAP = simple time-sliced execution Execution logic: 1. Define an execution window (for example 09:30-11:30) 2. Divide it into N time buckets 3. Execute total_size / N in each bucket Pros and cons: + Simple, no need to forecast volume - Easier to cause impact during low-volume periods - Less adaptive than VWAP
Simulating Execution Delay in Backtests
pythonsignals = delayed_execution(raw_signal, delay_bars=1) # T+1: trade tomorrow on today's signal signals = delayed_execution(raw_signal, delay_bars=0) # same-bar execution
- China A-shares:
delay_bars=1(T+1 rule) - Crypto:
delay_bars=0or1
A negative delay_bars raises. It would pull future signal values into the past,
which is look-ahead bias and silently inflates every backtest containing it — the
tested implementation refuses rather than letting that pass unnoticed.
Integrated Transaction-Cost Model
Total Cost Breakdown
Total trading cost = explicit cost + implicit cost Explicit cost: - Commission: China A-shares 2-3 bps, crypto 0.02-0.1% - Stamp duty (China A-share sell side): 0.05% (sell orders only) - Transfer fee: negligible Implicit cost: - Bid-ask spread: 0.5-5bps - Market impact: depends on trade size and liquidity - Opportunity cost: loss from not filling at the best price
Reference Trading Costs by Market
| Cost Item | China A-shares | Hong Kong | US | Crypto (OKX) |
|---|---|---|---|---|
| Commission (one way) | 0.025% | 0.05% | 0 (zero commission) | 0.08% (maker) |
| Stamp duty | 0.05% (sell) | 0.1% (both sides) | 0 | 0 |
| Bid-ask spread | 0.03-0.1% | 0.05-0.2% | 0.01-0.05% | 0.01-0.05% |
| Total one-way | ~0.1% | ~0.2% | ~0.03% | ~0.1% |
| Total round-trip | ~0.2% | ~0.4% | ~0.06% | ~0.2% |
Cost Settings in Backtests
json{ "commission": 0.001, "comment": "0.1% one-way commission, already includes stamp duty and spread" }
Recommendations:
- China A-shares:
commission = 0.001(conservative, includes all costs) - Crypto:
commission = 0.001(including slippage) - Hong Kong / US equities:
commission = 0.001-0.002
Backtest Execution Assumptions
Relevant config.json Settings
json{ "commission": 0.001, "engine": "daily", "interval": "1D" }
Advanced Execution Assumptions (implemented in signal_engine.py)
pythonfrom src.quantlib.impact import delayed_execution class SignalEngine: def __init__(self): # Execution assumption parameters self.execution_delay = 1 # T+1 delay self.slippage_bps = 5 # Fixed 5bps slippage self.max_participation = 0.05 # Maximum participation rate 5% def generate(self, data_map): for code, df in data_map.items(): # 1. Generate raw signal raw_signal = self._compute_signal(df) # 2. Apply execution delay delayed_signal = delayed_execution(raw_signal, self.execution_delay) # 3. Apply volume filter (do not trade when liquidity is too low) volume_ok = df['volume'] > df['volume'].rolling(20).mean() * 0.3 delayed_signal[~volume_ok] = 0 signals[code] = delayed_signal
Analysis Framework
Evaluate the Impact of Transaction Costs
Step 1: Estimate annual turnover Annual turnover = annual trade count × 2 (buy + sell) / number of positions Step 2: Compute annual cost drag Annual cost = annual turnover × total one-way cost Step 3: Evaluate the impact on returns Net return = gross return - annual cost Example: Annual turnover = 12 (monthly rebalance) One-way cost = 0.1% Annual cost = 12 × 0.1% = 1.2% If annualized return is only 5% -> costs eat 24% of returns!
Sensitivity Analysis for Execution Assumptions
markdown### Backtest Results Under Different Slippage Assumptions | Slippage (bps) | Annual Return | Sharpe | Max Drawdown | |-----------|---------|--------|---------| | 0 (ideal) | 15.2% | 1.35 | -18.5% | | 3 | 13.8% | 1.22 | -19.0% | | 5 | 12.9% | 1.15 | -19.2% | | 10 | 11.1% | 0.98 | -19.8% | | 20 | 7.5% | 0.65 | -20.5% | Conclusion: the strategy still has meaningful profitability under 10bps slippage
Output Format
markdown## Execution Cost Analysis ### Strategy Trading Characteristics | Metric | Value | |------|-----| | Average annual trade count | 48 | | Annual turnover | 4.8x | | Average holding days | 25 | | Average order size | ¥50,000 | ### Cost Estimate | Cost Item | Per Trade | Annualized | |--------|------|------| | Commission | 0.025% | 0.24% | | Stamp duty | 0.025% | 0.12% | | Estimated slippage | 0.03% | 0.29% | | **Total** | **0.08%** | **0.65%** | ### Cost Impact - Gross return: 12.5% - Net return: 11.85% - Cost drag: -0.65% (5.2% of gross return) - Conclusion: cost impact is manageable ### Optimization Suggestions 1. Lower turnover (lengthen holding period) 2. Avoid trading during low-liquidity windows 3. Use limit orders instead of market orders
Notes
- Backtest only: this system does not execute live trades; the execution model is used only to improve backtest realism
- Conservative assumptions: in backtests, it is better to overestimate transaction costs than to underestimate them
- China A-share T+1 rule: trades cannot be executed on the same day the signal is generated, so execution must be delayed by 1 day
- Price-limit constraints: when China A-shares are locked at limit-up / limit-down, no fill is possible; those dates should be skipped in backtests
- Volume constraints: order size should not exceed 5-10% of the day’s traded volume, otherwise the impact model becomes invalid
- Backtest overfitting: even with slippage included, the strategy may still overfit; out-of-sample validation matters more
commissionin config: the default0.001(0.1%) is a reasonable all-in cost estimate- The models are implemented, not improvised:
src/quantlib/impact.pyholds all four, tested. Import them rather than retyping; the tested versions reject a zero ADV, a negative order size and a negative execution delay, all of which the retyped versions used to accept silently

