A backtest that fills every trade at the quoted tick price will systematically overstate performance, because live execution on Deriv synthetic indices pays a spread and, on some contract types, absorbs slippage that the historical tick series does not show directly. This article describes a practical method for estimating those costs from tick data, applying them inside a Python backtest and inside DBot, and then adjusting strategy parameters so the simulated results reflect live execution rather than an idealised feed. The method is an approximation, not a measurement of your future fills, and it should be treated as a filter on a strategy rather than a validation of it.
Trading involves significant risk and you can lose your entire stake. Test every strategy on a demo account first.
Where the cost actually sits on Deriv
On Deriv, the cost of a fixed-stake contract is not quoted as a commission. It is embedded in the payout. For Rise/Fall and Digits, you stake an amount and receive a payout if the contract wins; the difference between the fair probability implied by the underlying and the payout ratio is the platform’s margin. This means that “spread” in the exchange sense is not the right mental model for those contracts. What matters is the payout ratio relative to the true probability of the outcome, plus the fact that entry occurs at the tick price observed at the moment the contract is bought, which may differ from the tick your bot used to make the decision.
For Multipliers, the cost appears as a commission on the opened position and a spread applied to the underlying price. Multipliers are the contract type where stop loss and take profit by price exist; for fixed-stake contracts, risk is controlled by stake, duration and session limits, not by a price-based stop. Accumulators carry a growth rate and a barrier; the relevant cost is the relationship between the growth rate and the probability of the barrier being touched, which is not a simple spread either.
The practical consequence: a single “spread in pips” parameter is only meaningful for Multipliers and for forex underlyings. For synthetic index fixed-stake contracts you must model cost as a payout adjustment, not as a price offset. Conflating the two is the most common modelling error in community backtests.
Estimating cost from tick data
Tick data from the Deriv API gives you a sequence of quotes with timestamps. It does not give you the payout your contract would have received at that instant, nor the fill price your order would have achieved. You therefore have to reconstruct both from observable quantities.
For fixed-stake contracts, the cleanest approach is to record, alongside each historical tick, the payout ratio the platform was offering at that time for the contract type, duration and stake you intend to use. The Deriv API exposes contract proposals, and you can log proposal responses during a demo session to build a table of payout ratios indexed by underlying, contract type, duration and stake. If you cannot log proposals for the full history, use the current ratio as a constant and accept that it is an approximation; state that assumption explicitly in your notes.
For Multipliers, estimate the spread as the difference between the bid and ask implied by the platform’s quoted prices at the moment of order, and estimate commission as the stated percentage of position size. Both are visible in the platform and in API responses; neither should be guessed.
Slippage is the residual. It is the difference between the price at which your bot decided and the price at which the contract was actually opened. On synthetic indices with short durations, this is dominated by the latency between decision and order submission, plus the granularity of the tick series. You can estimate an upper bound by measuring, on demo, the distribution of (fill price − decision price) across a sample of orders. Record the sample size and the median and 90th percentile; do not report a single average without the distribution.
A cost model built from fewer than a few hundred demo orders is a hypothesis, not an estimate. Label it as such.
Worked example: adjusting a Rise/Fall backtest
Suppose a Rise/Fall strategy on a synthetic index with a 5-tick duration. The historical tick series shows 1,000 signals. In the raw backtest, 540 win and 460 lose, a win rate of 54%. Stake is 1 unit per contract. Assume the payout ratio observed on demo for this contract is 0.95, meaning a winning contract returns 0.95 units of profit per 1 unit staked, and a losing contract returns −1 unit.
Raw expectancy per contract = (0.54 × 0.95) + (0.46 × −1) = 0.513 − 0.46 = +0.053 units.
Now add a slippage model. Suppose demo measurement shows that 20% of orders fill one tick worse than the decision price, and on this contract one tick of adverse movement flips the outcome in 30% of those cases. That is, of the 1,000 signals, 200 are affected by slippage, and 60 of those (30% of 200) change from a win to a loss. The adjusted record is 480 wins and 520 losses.
Adjusted expectancy = (0.48 × 0.95) + (0.52 × −1) = 0.456 − 0.52 = −0.064 units.
The strategy flips from positive to negative expectancy under a slippage assumption that is plausible but not extreme. This is the purpose of the exercise: not to predict the exact live result, but to see whether the edge survives a reasonable cost model. If it does not, the backtest was measuring the absence of costs, not the presence of an edge.
Note that the break-even win rate for this payout is 1 / (1 + 0.95) = 0.5128, or 51.3%. The raw backtest cleared it by 2.7 percentage points; the adjusted backtest missed it by 3.3 points. Reporting only the raw figure would have been misleading.
Implementing the adjustment in Python and DBot
In Python, keep the cost model as a separate function applied to the signal stream, so that you can toggle it and report results with and without costs. A minimal structure:
def apply_costs(signals, payout_ratio, slip_rate, flip_rate):
wins = losses = 0
for s in signals:
outcome = s["outcome"] # 1 win, 0 loss, from raw ticks
if random.random() < slip_rate:
if random.random() < flip_rate:
outcome = 1 - outcome
if outcome:
wins += 1
else:
losses += 1
n = wins + losses
expectancy = (wins / n) * payout_ratio - (losses / n)
return wins, losses, expectancy
Run this with several values of `sliprate` and `fliprate` drawn from your demo measurements, and report the range of expectancies rather than a single number. If the strategy is only profitable at `slip_rate = 0`, it is not profitable.
In DBot, the same logic is applied differently because DBot executes live rather than simulating. The relevant adjustment is to the strategy parameters: widen the entry threshold so that marginal signals are skipped, lengthen the duration if the contract type allows it so that tick-level noise has less influence on the outcome, and reduce stake so that the cost per contract is a smaller fraction of the expected edge. A numbered sequence of DBot blocks for a Rise/Fall strategy with a cost-aware filter:
- Trade parameters block: set stake to the minimum permitted, set duration to the shortest value your analysis supports, set the underlying and contract type.
- Tick analysis block: compute the signal condition over a window of N ticks.
- Condition block: require the signal to exceed a threshold that is larger than the measured slippage in tick units; if the signal is within the slippage band, do not trade.
- Purchase block: buy the contract only when the condition passes.
- Session limit block: enforce a maximum number of contracts per session, a daily loss limit and a maximum consecutive-loss count; stop the bot when any is reached.
The threshold in step 3 is the parameter you tune. Set it too low and you trade signals that costs will erase; set it too high and you trade too rarely to gather a sample. The correct value is the one at which the adjusted expectancy, not the raw expectancy, remains positive across an out-of-sample period.
Limitations
This approach does not measure your actual fills. It estimates them from a demo sample, and demo execution differs from live execution in ways that are not fully observable: order routing, queue position on Multipliers, and the platform’s own handling of fast markets. It does not model the payout ratio changing over time, which it does. It does not account for the behavioural difference between running a bot on demo and running it with real money, which is real and which no backtest captures. It does not tell you whether the strategy will be profitable; it tells you whether the strategy survives a plausible cost model, which is a weaker and more honest claim. And it does not substitute for the hard limits in premise 5: a daily loss limit, a maximum drawdown and a consecutive-loss cap must be set in the bot regardless of what the backtest says.
Frequently Asked Questions
Can I use a single spread number for all Deriv contracts? No, because fixed-stake contracts embed cost in the payout ratio rather than in a price offset, so a spread parameter is only meaningful for Multipliers and forex underlyings. For Rise/Fall, Digits and Accumulators you must model cost as a payout adjustment.
How many demo orders do I need before the slippage estimate is useful? There is no universal threshold, but a sample small enough that a handful of orders changes the median is not an estimate. Record the sample size and report the distribution, not just the mean, and treat any figure from a small sample as a hypothesis to be retested.
Does a positive adjusted backtest mean the strategy is ready for real money? No. A backtest is a filter, not proof; it must be followed by a recorded demo sample, out-of-sample testing, and a report of how many configurations were tried, before any real-money deployment at minimum stake.
Key Takeaways
- Fixed-stake Deriv contracts embed cost in the payout ratio; model cost as a payout adjustment, not as a price spread.
- Estimate slippage from a recorded demo sample and report the distribution, not a single average.
- Recompute expectancy and break-even win rate after applying costs; a strategy that only works at zero cost does not work.
- Tune entry thresholds so that signals within the slippage band are skipped, and keep the hard session limits in the bot regardless of backtest results.
- Report how many configurations you tried; a good result from many attempts is weaker evidence than it appears.
The full method, including the cost-model function and the DBot block sequence above, is maintained in the ORSTAC repository alongside the open-source bots. The bots run on Deriv, where a free demo account is available to test them before any real-money deployment. Discussion of cost models, backtest methodology and DBot implementation is welcome in the ORSTAC Telegram group.
Trading involves significant risk and you can lose your entire stake. Test every strategy on a demo account first.
