Trading costs and market impact
The run charges the spread, commission and slippage declared in the graph. It does not model the market impact of a large order.
Position duration and entry timing
Execution leaves a timing footprint. Inspect holding duration and entry-hour buckets together with the number of trades in each bucket.
Open full sizeTwo quantities that scale differently
The friction on a trade splits into a part that scales with turnover and a part that scales with size. The first covers the spread you cross, the commission you pay and the financing you carry overnight. It attaches to each round trip, so a configuration that trades twice as often pays it twice as often.
The second is market impact: the price moves against an order that consumes a visible share of the available volume. Measured across equities, futures, FX and crypto, it grows with the square root of the size traded and stays close to independent of how the order is sliced.
These two behave differently under a sweep. Turnover-scaled friction is a constant per trade and ranks configurations by how often they trade. Impact depends on the notional you intend to run, which the backtest does not carry, so it changes the answer at the moment you decide how much capital the result deserves.
A charge the engine adds by itself
The cost the engine applies with no cost node placed is zero. Each run poses model: "declared" in the cost dictionary, which sets the venue model to a zero floor before your nodes add to it. The result then carries a gross-run label, and the label names the spread, commission, slippage and financing that went uncharged.
The zero baseline makes the charge equal to what you declared. It replaces earlier defaults that could shape a result with broker assumptions unrelated to your graph, and the additive keys then add once.
How a node reaches the engine
The cost family ships 26 nodes, of which 17 are validated by executable checks, and 9 report a declared need in place of a figure. They compose into a small set of keys, and each key carries its own unit.
extra_rt_bp holds commissions and slippage in basis points of round-turn notional. sigma_slip_k holds a slippage expressed in multiples of the ATR at entry, for a venue whose spread widens with volatility. extra_drag_R holds a flat drag in R per trade, kept in its own unit because no conversion to basis points exists without a notional. cost_mult multiplies the net friction, for a stress reading.
The replacement keys are separate: round_turn_bp from the venue-profile node, and swap_long_bp, swap_short_bp and crypto_apr_pct from the financing node. A node whose label declares an override replaces the model; the others add to it, because a label reading taker fee describes a commission your venue also charges.
extra_rt_bp bp of round-turn notional commissions + slippage sigma_slip_k multiples of entry ATR volatility-scaled slippage extra_drag_R R per trade flat drag, unconverted cost_mult multiplier stress on the net friction round_turn_bp bp of round-turn notional venue override (replaces)
The order several nodes compose in
Fees add first, in basis points: commissions, taker fees and the fixed half-spread paid at entry and at exit. Slippages then add in basis points and are multiplied by the exec-efficiency node. A degraded execution degrades the slippage and leaves the commission untouched, since a commission does not slip.
The two sums meet in extra_rt_bp. The stress multiplier applies last, engine-side, on the net friction, so it prices a venue where fees and slippage widen together. A run with a stress node and no slippage node reports that the multiplier had no slippage to degrade.
Two halves of one node stay separate when they carry different units. The vol-scaled spread declares a fixed part in basis points and a volatility coefficient in multiples of ATR, and each half reaches its own key with its own journal line. Declaring the coefficient alone charges the coefficient alone.
A class resolved by the cost table
Some charges depend on the instrument, so composition runs once per symbol. The per-class commission node declares a line for FX, metals, stocks and crypto, and the line charged is the one matching the class of the symbol being traded. The class comes from the engine cost table; the shape of a ticker string does not determine it.
The funding node charges a perpetual rate on a crypto perp. On any other class the node reports the mismatch and charges zero, because the engine reads that rate inside its crypto branch alone. Charging it on a spot symbol would compose a key that no code path reads, which reads afterwards as a cost that applied.
When the class cannot be resolved, the node reports the failure and charges zero. A line picked at random would bill one class at another class's tariff, and the figure would then carry the appearance of a measurement.
Charged, and not charged, side by side
A cost node placed produces a journal line, and the run note prints both halves: what was charged with its detail, and what went uncharged with the input that was missing. A parameter left blank contributes zero and appears under the second half.
Blank and zero read differently in that journal. A blank parameter records a silence, and a declared zero records a decision, so a node placed without its numbers does not read as a node that applied. This is what makes a gross run detectable after the fact, including inside an exported paper.
costs DECLARED by your nodes (the engine adds none): charged: Taker fee -> commission x 2 fills; Spread (vol-scaled) -> half-spread at entry and exit NOT charged: Slippage - volume-share (order quantity unavailable; the run is denominated in R)
Open full sizeOne fill, at the next bar open
Entry happens at the open of the bar following the signal, and a stop or target is detected at the wick of the bar, above or below its close. Settlement is immediate, with no delay on cash availability. Placing the nodes that name these behaviours confirms them and changes no figure in the result, which the journal states as a confirmed guarantee.
One consequence follows for the sections below. The run executes a position in a single fill, at one price, with no execution schedule and no child orders. A model of impact needs a trajectory over time, so the shape of the fill sets the boundary of what can be charged.
Why the size axis stays unmodelled
The run is denominated in R, the unit of risk you declared, and carries no order quantity and no currency ledger. Neither the run specification nor the executor transports a lot size. Each charge described above is therefore expressible per round trip or per trade, and each charge that would depend on how much you trade is left out.
The nodes concerned report a declared need in place of a figure, each naming its own missing input. Volume-share slippage requires the order quantity beside the bar volume the engine already holds. Almgren-Chriss market impact requires a fractional execution schedule, since permanent and temporary impact are defined over a trajectory. Delayed settlement requires a currency ledger for a cash delay to constrain anything. Buying power and a margin-call model both require the notional per trade, so a capital figure and a point value. The break-even cost sweep charges per trade, and the trade count travels in no context object today.
The exit condition is one node and the keys it would feed. The account node declares capital and unit; wiring it into composition yields a notional per trade, which opens a volume-share key and the temporary and permanent impact coefficients. Until that wiring exists, an impact figure would be a number with no measurement behind it.
Costing a result without an impact model
Cost the effect at the first measurement. A gross figure and a net figure read side by side, with the turnover that separates them and the cost per round trip you assumed, give a comparison that survives a later revision of the cost. A cost check deferred to implementation arrives after the research has anchored on the gross number.
Then compute the break-even cost: the gross edge divided by the turnover, which gives the cost per round trip at which the net contribution reaches zero. Compare it against what the instrument costs to trade. A break-even below realistic costs gives a documented no-go carrying the number that would reopen it.
The stress multiplier stands in for the missing size axis. A cost parameter is held fixed for a run, so this sensitivity is built by hand: set a level, rerun, repeat, and read where the verdict changes. That level is a distance to the edge of viability. It prices no impact, and it does tell you how much unmodelled friction the result absorbs.
Last, state the participation rate the result assumes. The square-root law makes impact a function of size over daily volume, so a notional small against the volume of the bars you traded keeps the unmodelled term small. Writing that assumption down beside the verdict keeps it available when the account grows.
Where these results come from
The turnover hurdle and its effect on which effects survive are measured in the anomaly literature. The impact law and the execution frontier come from the optimal-execution literature, and the shipped knowledge pack carries both summaries.
What is being claimed, and what is left out
The charge applied to a run comes from the cost nodes placed on the canvas.
- Scope
- The engine cost floor is set to zero per run, so the figure covers what your nodes declare on the bars that were loaded.
- Effect
- A graph wiring no cost node produces a gross result, which overstates what an account would have kept.
- Control
- The run note prints the charged items and the uncharged ones with their missing input, and the gross label travels into the verdict and the exported paper.
Size-dependent market impact is charged at zero.
- Scope
- Any run, at any notional, since the run is denominated in R and carries no order quantity.
- Effect
- A result overstates the net edge by the impact its intended size would incur, and the overstatement grows with the square root of that size.
- Control
- The nodes concerned report a declared need naming the missing input, and the stress multiplier gives a sensitivity across friction levels.
Two cost channels have no equivalent on an installed build.
- Scope
- A build whose package excludes the kernel engine, where the cross-sectional backend charges the declared round-turn fraction.
- Effect
- A volatility-scaled slippage and a drag in R are excluded from the charge on that path, since it holds no entry ATR and no R.
- Control
- The run label names them as excluded from the charge, beside the basis points that were applied.
Production packaging includes the strategy engine and fails when its source is unavailable. The explicit non-deliverable demo configuration can omit it and use a labelled generic cross-sectional fallback. That fallback does not support execution claims.
No figure described above is a statement about future performance, and the harness carries no signal to trade. A result concerns the series that was submitted.
Check a cost assumption in units
A worked example with made-up numbers: 10 units of gross P&L less 3 spread, 2 commission and 4 slippage leaves 1 unit. Keeping the same run and changing only slippage to 8 leaves -3 units. Reproduce that subtraction first; then inspect the run journal to learn whether each real cost was charged, declared zero or left blank. A flat estimate cannot prove market impact for the size you intend to trade.