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. Usemerge_informative_pairfor 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; morestartup_candle_countwon'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:00covers 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_belowplus a strict AND condition can silently never fireqtpylib.crossed_above(a, b)is only true on the single candle where the cross happens — unlikea > 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 ofcrossed_*when it needs to combine with other AND conditions, and reservecrossed_*for single-condition triggers.process_only_new_candles = Falseis (almost always) pure wastepopulate_indicators/populate_entry_trend/populate_exit_trendonly ever receive closed-candle data — there's no other concept of "now" for them to use, in backtesting or live. Setting this toFalsere-runs them everythrottle_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 inconfirm_trade_entry/custom_exitinstead, 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 backtestingruns, 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_shortstrategy 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.