Strategy Coding FAQ

Freqtrade-specific patterns, common pitfalls, and the coding habits that separate a strategy that holds up live from one that only looks good in a backtest. Pairs with how Freqle's checks work and the official freqtrade docs.

Writing strategies that stay robust (avoiding overfitting)

Overfitting — or curve-fitting — is when a strategy is tuned so tightly to past price history that it memorises the noise instead of capturing a real edge. It backtests beautifully and then falls apart the moment it meets data it hasn't seen. It's the single most common reason a "profitable" strategy loses money live, so Freqle scores every League strategy for it (see Robustness). Here's how to stay on the right side of it while you code.

1. Prefer fewer, meaningful parameters

Every tunable number is a knob an optimiser can twist to fit the past. A strategy with twenty hyperopt parameters can be made to fit almost any history — and almost none of that fit will transfer. Favour a handful of parameters that each express a real market idea ("only enter in an uptrend", "don't trade the chop") over a dense grid of thresholds whose only justification is that they backtested well.

2. Validate on data the tuning never saw

If you hyperopt, never judge the result on the same window you optimised. Split your history: optimise on one stretch, then run an untouched later stretch as an out-of-sample test. If performance collapses out-of-sample, the in-sample result was curve-fit. On Freqle, the Walk forward tab does exactly this — a backtest on recent data the strategy was never shaped around.

3. Distrust a suspiciously smooth equity curve

A backtest that only ever goes up, wins 95% of trades, or shows a tiny max drawdown is usually too good to be true — often a sign of lookahead bias rather than genius. Real edges are noisy. If your curve looks perfect, assume a bug until you've proven otherwise.

4. Beat buy-and-hold, honestly

In a bull market almost anything makes money. The bar isn't "did it profit" — it's "did it beat simply holding the coins over the same window, after fees". Freqle shows this as the vs market figure. If your strategy trails a plain hold, the complexity isn't earning its keep.

5. Think in ranges, not single numbers

One backtest is one sample. Ask what happens if the trades had arrived in a different order, or if you'd started a month later. Freqle answers this with a Monte Carlo resample — you can reason the same way while coding: a strategy whose result hinges on two or three big trades is fragile, however good the total looks.

Common freqtrade pitfalls

Lookahead bias
Never use data a candle couldn't have known yet. shift(-n), using the current (unclosed) candle, or reading a resampled higher-timeframe value before it closes all leak the future. Use merge_informative_pair for higher timeframes — it aligns and shifts safely. Freqle's Lookahead analysis hunts these.
Recursive / unstable indicators
Some indicators give different values depending on how much history they're fed. If your signal changes when the startup window changes, live behaviour won't match the backtest — set a sufficient startup_candle_count. Don't just match the period: EMA/DEMA typically need ~3x their period to actually converge, KAMA/TEMA/ADOSC ~5x, RSI/ATR/MACD ~8x, and ADX/ADXR/DX more like 12x+ — a period-14 RSI wants a startup count closer to 112 than 14. A few indicators (AD, OBV) accumulate over the whole series and never fully converge no matter the warmup; more startup_candle_count won't fix those, only judging whether the strategy uses them as a running comparison rather than against a fixed threshold. See Recursive analysis.
A candle's timestamp is when it opened, not closed
A 5m candle stamped 00:00 covers 00:00–00:05 and only finishes at 00:05 — that's when its close, and therefore its indicator values, become available. Expecting the bot to act "at" the timestamp shown in the dataframe is the single most common source of "why is my bot always one candle late" confusion. It isn't late; that's the earliest the data could exist.
Signal candle vs trade candle
The candle where your entry/exit condition becomes true (the signal candle) is not the candle your order actually fills on — that's the following candle at the earliest, and later still with a slow-filling limit order. Any exit logic computed from "the entry candle's ATR" needs to store that value at entry time (e.g. in custom_exit's state or the trade's tag) rather than recompute it from whatever candle happens to be current when the callback runs — those are two different candles with two different indicator values.
crossed_above/crossed_below plus a strict AND condition can silently never fire
qtpylib.crossed_above(a, b) is only true on the single candle where the cross happens — unlike a > b, which stays true for as long as the condition holds. Combined with a second strict condition ANDed in (e.g. crossed_above(close, upper) & (rsi > 75)), the entry can miss every time: if RSI passes 75 one candle after the cross, the cross has already happened and won't happen again until price separates and re-crosses. Use a plain comparison instead of crossed_* when it needs to combine with other AND conditions, and reserve crossed_* for single-condition triggers.
process_only_new_candles = False is (almost always) pure waste
populate_indicators/populate_entry_trend/populate_exit_trend only ever receive closed-candle data — there's no other concept of "now" for them to use, in backtesting or live. Setting this to False re-runs them every throttle_secs (default 5s) instead of once per candle close, recomputing identical values for no benefit. It only matters for order-book-level logic (spoofing/wall detection), and that belongs in confirm_trade_entry/custom_exit instead, which already run every loop without this flag.
Batch-backtesting a timerange in chunks skews the result
If you (or a script) split one long timerange into several separate freqtrade backtesting runs, every trade still open at a batch boundary gets force-exited — an exit that wouldn't have happened in a single continuous run. More batches means more artificial forced exits, and the distortion compounds: splitting the same period into 2 batches vs 3 can flip a strategy from a loss to a profit. Only the final batch should ever show a forced exit; any earlier than that means the split is corrupting the numbers. Freqle never does this — every backtest and its monthly breakdown come from one continuous run, sliced by trade close date afterward, not re-run per month.
Spot vs futures mode
Shorting is impossible on spot — a can_short strategy must run in futures mode, where pairs settle in USDT (BTC/USDT:USDT) and leverage/liquidation apply. Freqle runs such strategies in isolated-margin futures automatically.
Why dry/live results drift from the backtest even on clean code
Some divergence is structural, not a bug to fix: slippage (the orderbook moves in the gap between fetching it and the order filling), exchange rate limits and cooldowns that don't exist in simulation, market impact from your own order size on thin pairs, and — if your live config uses a dynamic pairlist (e.g. VolumePairList) — trading a different set of pairs live than whatever static list you backtested against. A strategy can be bias-free and still perform differently live for reasons outside the code entirely.