In this article
After this article, you can
- Check matching signal and price identifiers.
- Identify changed data meaning after removing an index.
Terms at hand: Backtesting · Order execution · Commission · Drawdown · Stop loss · Take profit (TP)
A Python backtest needs a chain from data to information available at the time, orders, fills and a reconciled ledger. Preserve record identifiers as well as timestamps. A fast equity plot cannot tell you whether a signal used a later release, an incomplete bar or a price your execution model never allowed.
Choose the environment for the research question
MT5’s Strategy Tester supports multicurrency Expert Advisors, testing and optimization. Python is another way to organize data, custom calculations and research, rather than a necessary escape from a one-pair limit. Compare the actual data, contract and execution requirements. Keep the same frozen dataset and rules when investigating differences between implementations.
Save the data and its meaning
Record the instrument and product, provider, time zone, quote side, sampling interval and download range. Check duplicate identifiers, record order and missing periods. A market closure is not automatically a corrupt file; filling a gap can invent observations. Preserve the original file alongside the transformation steps.
MetaTrader5’s copy_rates_range returns time, OHLC, tick volume, spread and real volume fields. It does not provide a complete historical commission or swap tariff. Available bars depend on terminal history and settings, so requesting a date range does not prove it was returned completely. Its time arguments and returned data use UTC. Use explicit time zones before converting any session schedule.
pandas and NumPy support data calculations. Backtrader provides an event-driven workflow. vectorbt’s portfolio simulation also updates cash and asset state and offers orders, signals and custom order-function modes. Neither label guarantees realistic execution or a fixed runtime. Choose the smallest useful stack, record versions and check its license, data rights and supported environment. Data and deployment may have separate costs.
Removing the index can attach a signal to another bar
Our test has three completed fictional price records: A = 100, B = 102, C = 104. A separate flag Series contains C: 1, A: 0, B: 0 in that order. Flag 1 identifies only a candidate for further modeling, not an order or execution price.
pandas aligns Series by label. The result of prices * flags is A: 0, B: 0, C: 104. Converting both Series to arrays and multiplying by position gives 100, 0, 0. Assigning that result to A, B, C makes A the candidate. Check IDs before to_numpy() instead of checking only array lengths. Correct alignment still requires checking when the signal became available.
Assumptions: Fictional records, unique IDs and arbitrary prices. No costs or financial result. Multiplication by a flag demonstrates record assignment, not a complete backtest. Official material: pandas — Series and label alignment.
Define when a record becomes usable
A bar labelled by its opening time is not fully observed at that instant. An H1 bar opening at 10:00 has its completed OHLC only after the period ends. In pandas resampling, closed controls interval membership and label its displayed edge. Changing a label does not change when an aggregate was available.
Join macroeconomic observations by the publication time of the available vintage, not just the month they describe. merge_asof requires sorted merge keys; backward selects an earlier or equal key while nearest can select a later one. Neither option knows whether a value was subsequently revised. Define tolerances and the order of events sharing a timestamp.
Own record-matching test. A decision is stamped 10:04 UTC. The only saved releases are 10:00 with value 50 and 10:05 with value 60. With no tolerance restriction, backward matching selects 50, four minutes earlier. Nearest selects 60, only one minute away but still one minute in the future. An inclusive two-minute backward tolerance gives no match. Record that missing value; substituting the nearer future record would change the information set.
Our separate closed-bar recipe assumes high values 100 through 119 for the first twenty fictional bars, with each Open = High − 0.5, Low = High − 2 and Close = High − 1. Bar 21 has O120/H121/L118/C120.5; bar 22 has O120.6/H122/L119/C121. These arbitrary prices are not market data or an FX trade.
previous_high = bars["high"].shift(1).rolling(20).max()
signal = bars["close"] > previous_high
next_bar_signal = signal.shift(1, fill_value=False)
At bar 21 the previous twenty-bar maximum is 119, so 120.5 > 119 is true. Including the current High makes the threshold 121 and the condition false. The last line places the observed signal in bar 22; it does not supply a fill price, prove an order was accepted or choose an exit. Keep only completed input bars and specify position size, order type and event order separately.
Separate the signal, fill and costs
In Backtrader’s documented standard model, a Market order placed after the current bar is processed uses the next available bar’s opening price. Other settings or execution models can change timing. Inspect what happens when both a stop and a target fall within one bar: OHLC alone does not identify their order of observation.
Own one-trade accounting model. Buy 10,000 EUR at an assigned EUR/USD Ask of 1.1002; the simultaneous Bid is 1.1000. Later sell all units at an assigned Bid of 1.1012, with Ask 1.1014. A pip is 0.0001 USD per EUR. The fill-to-fill difference is ten pips, or 10 USD. With commission of 1 USD on each side, net profit is 8 USD. Funding and additional slippage are assumed zero in this exercise. Deducting another two-pip entry spread would instead report 6 USD and count an already included price difference twice.
Specify financing, conversions, contract size, margin rules and rejected orders for the intended product. For several instruments, model shared cash, available margin and the processing order of simultaneous requests. Adding independent equity curves can miss orders that a shared account could not fund. A random slippage model also needs a stated distribution, seed and library version.
A ledger makes the performance claim checkable
For a separate equal-R illustration, exactly forty completed wins at +2R and sixty losses at −1R give gross gains 80R, gross losses 60R and net 20R. Profit factor is 80/60 ≈ 1.33 and mean outcome 0.2R per trade. A cost of 0.1R on every trade leaves forty outcomes of +1.9R and sixty of −1.1R: gains 76R, losses 66R, net 10R, profit factor about 1.15 and mean 0.1R. R has the same monetary size throughout; this arithmetic is not a real backtest.
Own re-entry counterexample. Conditions on five bars are true, true, true, false, true. Both policies enter on bar 1; a stop closes the position on bar 2 before entry is evaluated. There are no later exits. A policy that enters whenever flat and true re-enters on bar 2 and remains in that position. A policy requiring a new false before re-arming waits through bars 2 and 3, re-arms on bar 4 and enters on bar 5. Both count two entries, at different times. This is an explicitly chosen state machine, not a default claim about either library.
Preserve both open-position equity and closed-trade results. Include starting capital when measuring drawdown and separate deposits and withdrawals from strategy returns. Do not add a return, Sharpe ratio or drawdown statistic that cannot be reconstructed from the relevant dated ledger and equity series.
Keep selection and withheld-data results separate
Fix the chronological training and test windows before selecting the preferred rules. Record every instrument, parameter set, filter and split tried, including rejected versions. A small parameter count does not mean only a small number of hypotheses were tested. Bailey and co-authors’ paper examines repeated strategy selection and backtest overfitting; this article does not estimate a PBO from its fictional examples.
A high win rate alone is not a diagnostic. Another invented hundred-trade set with seventy-five gains at +0.2R and twenty-five losses at −1R has a 75% hit rate yet loses 10R before costs: gains 15R, losses 25R, profit factor 0.6. Verify observations, sample dependence, execution, costs and parameter sensitivity rather than diagnosing bias from universal hit-rate or profit-factor thresholds.
What to do tomorrow
- Create an isolated project and save the untouched data with identifiers, source, UTC timestamps, quote side and version information. This offline learning exercise requires neither live credentials nor an order.
- Work through a few specified records. Log when each signal is available, which next event can execute it, and how a missing or revised record is handled.
- Choose the position-state rules: whether repeated true conditions add, reverse, re-enter after a stop or wait for re-arming. Check expected entry identifiers.
- Reconcile costs and each fill, then derive outcomes from the ledger. Run the same frozen rules with stated alternative execution assumptions.
- Preserve the trial log, withheld windows, versions and limitations. Compare like periods and uncertainty; a fixed performance drop does not identify its cause.
Check your understanding
One short exercise
About 2 min · you can try again
Prices are ordered A, B, C; flags are C: 1, A: 0, B: 0. Which record does Series label alignment select?
- C with price 104.
- A with price 100.
- Series with different orders cannot be aligned.
Check the answer and explanation
C with price 104.
The only flag 1 belongs to ID C, whose price is 104.
Frequently asked
Do I need advanced Python skills to get started?
Start with enough Python to read and inspect data, use functions and distinguish an index from array position. Reproduce a small, fully specified model before scaling it. The article’s alignment example is a data test, not a complete strategy; correct timing, state, exits and execution still need definitions. Neither thirty lines nor a fixed learning schedule establishes readiness. Save versions and verify intermediate records instead of relying only on the final plot.
Why pick backtrader over vectorbt, or the other way around?
Compare the documented simulation mode with your requirements. Backtrader uses an event-driven workflow, and its standard Market-order model executes at the next available bar’s Open. vectorbt has order, signal and custom order-function modes; its simulation updates cash and asset state. Vectorized input does not mean that every execution is independent of earlier state. Defaults, costs and event order need checking in either tool. No dataset-independent speed claim or automatic realistic validation follows from using one library.
Where do I get reliable historical data?
Identify provider, product, quote side, time zone and available history. MetaTrader5’s copy_rates_range provides bar fields including OHLC and spread, with UTC time and terminal history limits. It does not provide a complete historical tariff of commission and funding or prove live fills. yfinance is an independent tool using Yahoo data with usage notices; CCXT serves exchange data. Check the actual dataset’s completeness and suitability rather than ranking a tool name as inherently reliable. Preserve the original file and describe every repair.
How do I tell when a backtest is too good to be true?
Check causality and accounting directly: reproduce signals from records available then, inspect fills and cost units, preserve selection attempts and evaluate predetermined withheld windows. Win rate and profit factor do not diagnose a bug on their own. In the article’s hypothetical set, 75 wins of +0.2R and 25 losses of −1R lose 10R before costs despite a 75% hit rate. Unusual results justify investigating the data and model; a threshold alone does not establish look-ahead bias or overfitting.
Sources & bibliography
-
Backtrader Backtrader documentation — Introduction · Official documentation of Backtrader's event-driven model, Strategy/Cerebro classes and data integration. www.backtrader.com ↗
-
vectorbt vectorbt usage documentation · Official examples of vectorized backtesting with NumPy/pandas and parameter sweeps. vectorbt.dev ↗
-
O'Reilly Media Yves Hilpisch — Python for Finance, 2nd Edition (2018) · A book on Python applications in financial analysis, modeling and risk management. www.oreilly.com ↗
-
MQL5 MetaTrader 5 Python Integration — official reference · Official documentation of the MetaTrader5 Python package for historical prices, ticks, account information and trading operations. www.mql5.com ↗
-
pandas pandas — Time series / date functionality · Documents pandas date conversion, time-series indexing, resampling and shifting operations. pandas.pydata.org ↗
-
MetaQuotes copy_rates_range — Python Integration · Documents bar fields, UTC time arguments and terminal history limits. It does not supply the article’s fictional execution or cost tariff. www.mql5.com ↗
-
pandas pandas.merge_asof · Documents sorted merge keys, direction, tolerance and exact-match options. The 10:04/10:00/10:05 counterexample is our own. pandas.pydata.org ↗
-
vectorbt Portfolio — workflow and simulation modes · Documents sequential state updates, orders, signals and custom order functions. Our five-bar re-arming comparison is an explicit fictional protocol, not a claim about library defaults. vectorbt.dev ↗
