Commission on spot, maker and taker fees on perps, and flat slippage, each declared in the file so a shared package carries its own assumptions. The accounting is the kScript (legacy) engine's; the book-estimate slippage model is the one piece of it this release refuses rather than approximates.
Commission (spot)
On instrument: "spot" (the default), commissionPercent is charged on every fill as a percent of the fill's notional, on entries and exits alike. Fees reduce equity immediately and are reported per trade (fees on each trade record) and in the totals as stats.feesPaid.
Maker and taker fees (perps)
On instrument: "perps", fills are charged makerFeePercent or takerFeePercent instead of commission, by how the fill reached the market:
- Taker (
takerFeePercent): market-crossing fills. Market entries, stop entries, protective stops, trailing stops, signal exits,closeAll, liquidation, and the flattening leg of a reversal a market or stop entry triggered. - Maker (
makerFeePercent): limit-bound fills. Limit entries, take-profit limit legs, and the flattening leg of a reversal a limit entry triggered. stats.feesPaidis exactlymakerFeesPaid + takerFeesPaidon perps.
// Perps maker-fee routing: a resting limit entry pays the maker rate, the market close pays the taker rate.
import { input, line, lower, ohlcv, output } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_equity } from "./gen/outputs";
import { strategy } from "./gen/strategy";
import { Cross, Sma } from "./sdk/ta";
strategy({ initialCapital: 10000, instrument: "perps", leverage: 2, makerFeePercent: 0.1, takerFeePercent: 0.05, funding: "off", qtyType: "fixed", qtyValue: 1, pyramiding: 1 });
input("close", ohlcv.close);
output("equity", line, lower, { description: "Strategy equity" });
const fastMa = new Sma(4);
const slowMa = new Sma(9);
const cross = new Cross();
let close: f64 = NaN;
let crossed: i32 = 0;
export function init(): void {}
export function state(): i32 {
close = in_close();
const fast = fastMa.update(close);
const slow = slowMa.update(close);
if (isNaN(fast) || isNaN(slow)) return 0;
crossed = cross.update(fast, slow);
return 1;
}
export function finalize(): void {
if (crossed == 1) strategy.long("Long").limit(close * 0.999).send();
if (crossed == -1) strategy.close("Long");
out_equity(strategy.equity());
emitRow();
}
export function reset(): void {
fastMa.reset();
slowMa.reset();
cross.reset();
close = NaN;
crossed = 0;
}The resting limit just under the market is a maker fill when a later bar trades through it (makerFeesPaid grows); the market close crosses the market and pays the taker rate (takerFeesPaid grows); feesPaid is their exact sum.
One fee schedule per instrument
Declaring the other instrument's fee settings never double-charges; the build warns and the broker ignores them:
- Perps plus a nonzero
commissionPercent:STRATEGY_PERPS_COMMISSION_IGNORED, fees come from the maker and taker rates. - Spot plus
makerFeePercentortakerFeePercent:STRATEGY_SPOT_MAKER_TAKER_IGNORED, the run charges commission only. Declare one of the pair and the warning names just that one;onLiquidationon spot warns the same way.
Funding on perps is a holding cashflow, not a fill cost, and has its own stats and series; see perps.
Flat slippage
slippageBps applies adversely to every fill that crosses the market: market entries, stop entries, protective stops and trailing stops. Buys fill at price * (1 + bps / 10000), sells mirror. Limit fills, including take-profit legs, are exempt. Slippage is priced into the fill itself: the trade records carry the slipped price, and there is no separate slippage line to subtract, which is why the backtest card's slippage column reads 0 on this lane while its header echoes the declared rate.
Flat slippage is honest about being a constant: it neither grows with your order size nor tightens on liquid pairs.
The book estimate
kScript's slippageModel="bookEstimate" walks recorded order-book depth to price market-crossing fills by size. This release attaches no order book to any host, so a package declaring slippageModel: "bookEstimate" is refused by name at validation (wrun_strategy_slippage_model_unsupported) rather than silently priced at the flat rate. Declare "fixed" with a slippageBps that is realistic for the pair; the book-aware model is a named next step.
The backtest lane
om backtest @scope/name takes no cost flags: --fee-bps, --slippage-bps, --latency-bars and a book size are refused by name (wrun_strategy_costs_in_sheet), because a package with its own broker settings would otherwise carry two sets of assumptions. Change the declaration, or link a setting to a param and pass params.
Practical guidance
- Set
slippageBpsto what a taker actually pays on the pair, andcommissionPercentor the maker and taker rates to your tier. Most retail rules die here. - Size matters: a strategy that trades a fraction of equity and one that trades at 10x see very different fee bills on the same signals, because fees are a percent of notional.
- Read
feesPaidagainstnetProfitbefore reading the win rate: a strategy that crossed the spread 34 times in a quarter has paid for it.