6. Execution config
July 26, 2026 · View on GitHub
An ExecutionConfig records the cost / slippage / commission / fill assumptions that produced a study's runs. It lives on Study.execution_config (one per study; MAY be nil when the producer did not capture it — e.g. legacy bundles or ad-hoc smoke tests).
ExecutionConfig is small, dict-shaped, and always inline in the msgpack body. It is NOT eligible for Parquet blob extraction; there are no @blob references anywhere under it.
Why on the study, not the bundle?
Two studies inside the same algorithm_id bundle MAY validly use different execution assumptions — e.g. an in-sample sweep run zero-cost next to an out-of-sample validation study run under realistic fees. Anchoring execution_config to Study (rather than to Bundle) keeps each study self-describing and lets one bundle hold optimistic / pessimistic overlays of the same algorithm side by side.
Shape
ExecutionConfig (msgpack map)
├── blotter_type: str | nil # producer-defined identifier of the
│ # blotter / matching engine used by
│ # this study's runs. Identity-only.
│ # Behavioural details live under the
│ # *_model fields below.
├── slippage_model: dict | nil # {"type": "<name>", "params": {...}}
├── commission_model: dict | nil # same shape as slippage_model
├── fill_model: dict | nil # same shape as slippage_model
├── trading_costs: array[TradingCost] # per-symbol cost overrides (may be empty)
└── metadata: map # free-form (runtime flags, blotter
# module path, etc.)
All fields are OPTIONAL. Every combination of present / absent fields is a valid ExecutionConfig. Absence of a *_model field means the producer did not capture that model's identity — it does NOT imply "no slippage / no commission / full fill".
Absence of the whole ExecutionConfig (i.e. Study.execution_config == nil) means the producer did not capture any cost information for this study.
Pluggable model pattern
The three pluggable model fields (slippage_model, commission_model, fill_model) all use the same shape:
{
"type": "<string identifier>", # required if the map is present
"params": <map> # optional; producer-defined shape per type
}
The type field is a string that names a model class. Its params field is a map whose shape depends on type. Producers MUST emit type when the outer map is present. Readers MUST tolerate a missing params (treat as empty map).
TradingCost
Each entry in trading_costs is a per-symbol cost snapshot:
TradingCost (msgpack map)
├── symbol: str | nil # target symbol (upper-cased, e.g. "BTC/EUR"),
│ # or nil for a market-level default that
│ # applies to any symbol not otherwise named.
├── fee_percentage: float # percentage-of-value fee (0.0025 = 0.25%)
├── slippage_percentage: float # flat slippage as % of price
├── fee_fixed: float # fixed fee per trade in trading currency
└── slippage_model: dict | nil # optional nested {"type": "...", "params": {...}}
# overrides slippage_percentage for this symbol
At most one entry with symbol == nil SHOULD appear in a trading_costs array; readers encountering multiple MAY pick the first and ignore the rest.
Reserved built-in type names
The following type values are RESERVED by this specification. Each reserved type has a fixed set of params keys — these key names are part of the format and MUST be used verbatim by conforming producers. Consumers of a reserved type MAY rely on the params schema below.
Producers that need parameters not covered by a reserved schema SHOULD invent a new type name (see 10. Extensions § x- prefix) rather than extending a reserved name with extra keys.
slippage_model.type
"NoSlippage"
No slippage — fills at the exact modelled price.
params key | Type | Default | Notes |
|---|---|---|---|
| (none) | — | — | params MAY be {} or absent. |
"PercentageSlippage"
Flat percentage-of-price slippage. Buys fill higher, sells lower.
params key | Type | Default | Notes |
|---|---|---|---|
percentage | float | 0.001 | Decimal fraction (0.001 = 0.1%). |
"FixedSlippage"
Flat absolute slippage in price units. Buys fill at price + amount, sells at price − amount.
params key | Type | Default | Notes |
|---|---|---|---|
amount | float | 0.01 | In trading_symbol price units. |
"FixedBasisPointsSlippage"
Slippage expressed in basis points of price. slippage = price × basis_points / 10000.
params key | Type | Default | Notes |
|---|---|---|---|
basis_points | float | 5 | 1 bp = 0.01%. |
"VolumeImpactSlippage"
Power-law market-impact slippage. impact = base_percentage × (amount / volume) ^ impact_power. Falls back to base_percentage when volume is unavailable.
params key | Type | Default | Notes |
|---|---|---|---|
base_percentage | float | 0.001 | Base slippage as a decimal. |
impact_power | float | 0.5 | Exponent for participation rate. 0.5 = square-root impact. |
"VolumeShareSlippage"
Quadratic-in-participation slippage with a volume cap. impact = price_impact × (amount / volume)². In addition, at most volume_limit × volume of the bar's volume can be filled per bar.
params key | Type | Default | Notes |
|---|---|---|---|
volume_limit | float | 0.025 | Maximum fraction of bar volume fillable in one bar (2.5%). |
price_impact | float | 0.1 | Coefficient for the quadratic price impact term. |
commission_model.type
"NoCommission"
Zero fee on every trade.
params key | Type | Default | Notes |
|---|---|---|---|
| (none) | — | — | params MAY be {} or absent. |
"PercentageCommission"
Commission as a percentage of notional. fee = price × amount × percentage.
params key | Type | Default | Notes |
|---|---|---|---|
percentage | float | 0.001 | Decimal fraction (0.001 = 0.1%). |
"FixedCommission"
Fixed fee per trade regardless of trade size.
params key | Type | Default | Notes |
|---|---|---|---|
amount | float | 1.0 | Flat fee per trade, in trading_symbol units. |
fill_model.type
"FullFill"
Orders fill in full at the first opportunity.
params key | Type | Default | Notes |
|---|---|---|---|
| (none) | — | — | params MAY be {} or absent. |
"VolumeBasedFill"
Fill quantity is capped by a fraction of bar volume. The remainder stays open and is re-evaluated on subsequent bars.
params key | Type | Default | Notes |
|---|---|---|---|
max_volume_fraction | float | 0.1 | Maximum fraction of bar volume fillable per bar (10%). |
Vendor-defined type names
Producers MAY emit type values not on the reserved list above. Vendor-defined types SHOULD use the x-<vendor>- prefix from 10. Extensions, e.g. "x-quantos-AdaptiveSlippage". Their params schema is producer-defined and consumers MUST treat them as opaque (see reader tolerance below).
Reader tolerance
Conforming readers MUST:
- Accept
execution_config: niland treat it as "no info captured". - Accept an
ExecutionConfigthat is present but has every fieldnilor empty. Treat as "no info captured". - Accept unknown
typevalues in any ofslippage_model,commission_model,fill_model, or a nestedTradingCost.slippage_model. Unknown types are advisory metadata: readers SHOULD surface them to callers as opaque{type, params}pairs, and MUST NOT invalidate any runs, metrics, or summaries when they cannot reconstruct the model. - Accept unknown keys inside
paramsfor knowntypevalues. Preserve them on round-trip. - Accept unknown top-level keys inside
ExecutionConfig. Preserve them on round-trip.
Conforming readers MUST NOT:
- Reject a bundle solely because its
ExecutionConfigreferences an unknown modeltype. - Attempt to infer semantics for an unknown
typefrom its name.
Reference-implementation note (non-normative)
The reference implementation uses a process-local class-name registry (populated automatically via __init_subclass__ on SlippageModel, CommissionModel, FillModel) to reconstruct concrete model instances from {type, params} pairs. The registry is not persisted; bundles carry only the type name and its parameters, keeping the on-disk format free of Python-class references.
This mechanism is a property of the reference reader/writer, not of the format itself. Conforming implementations in other languages MAY use any mechanism they like to map type names to code, or MAY skip reconstruction entirely and treat all model definitions as opaque {type, params} records.