| name | execution-model |
| description | Trade execution modeling (backtest only) — slippage formulas (linear / square-root impact), VWAP/TWAP execution logic, market-impact cost estimation, and execution-assumption configuration. |
| category | strategy |
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.
from src.quantlib.impact import fixed_slippage, linear_impact, sqrt_impact, delayed_execution
1. Fixed Slippage Model
fixed_slippage(price=100.0, direction=1, bps=5.0)
fixed_slippage(price=100.0, direction=-1, bps=5.0)
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
linear_impact(price=100.0, direction=1, volume_traded=100_000, adv=1_000_000, impact_coeff=0.1)
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)
sqrt_impact(price=100.0, direction=1, volume_traded=250_000, adv=1_000_000,
volatility=0.02, eta=0.5)
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
signals = delayed_execution(raw_signal, delay_bars=1)
signals = delayed_execution(raw_signal, delay_bars=0)
- China A-shares:
delay_bars=1 (T+1 rule)
- Crypto:
delay_bars=0 or 1
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
{
"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
{
"commission": 0.001,
"engine": "daily",
"interval": "1D"
}
Advanced Execution Assumptions (implemented in signal_engine.py)
from src.quantlib.impact import delayed_execution
class SignalEngine:
def __init__(self):
self.execution_delay = 1
self.slippage_bps = 5
self.max_participation = 0.05
def generate(self, data_map):
for code, df in data_map.items():
raw_signal = self._compute_signal(df)
delayed_signal = delayed_execution(raw_signal, self.execution_delay)
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
### 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
## 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
commission in config: the default 0.001 (0.1%) is a reasonable all-in cost estimate
- The models are implemented, not improvised:
src/quantlib/impact.py holds 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