Building M1 bars from bid ticks or from mid prices in pandas
Posted: Fri Oct 23, 2026 1:02 am
Building M1 bars from bid ticks or from mid prices in pandas
If you collect tick data and build your own bars, the price you use changes the results more than people expect.
Most retail platforms draw charts from the bid. So the M1 bars in MT5 are bid bars. If you build bars from mid prices, the average of bid and ask, your bars sit half a spread higher. If you build them from the ask, a full spread higher.
That matters for any test that uses bar levels to decide entries. A long entry on a breakout of the M1 high using bid bars would actually fill at the ask, so the real entry is a spread above the bar high. Using mid bars, it's half a spread above.
In pandas, with a DataFrame of ticks indexed by timestamp and columns bid and ask:
bars = df['bid'].resample('1min').ohlc()
ask_bars = df['ask'].resample('1min').ohlc()
Keep both. Then simulate longs entering at the ask and exiting at the bid, and shorts the reverse. That reproduces the real cost without guessing a fixed spread.
A small detail: resample with the default settings labels each bar by its start time and includes ticks from the start up to but not including the next minute. MT5 does the same, so the bars should line up if your timestamps are in server time.
Empty minutes produce NaN rows. I drop them with dropna rather than forward filling, because a minute with no ticks is information.
If you collect tick data and build your own bars, the price you use changes the results more than people expect.
Most retail platforms draw charts from the bid. So the M1 bars in MT5 are bid bars. If you build bars from mid prices, the average of bid and ask, your bars sit half a spread higher. If you build them from the ask, a full spread higher.
That matters for any test that uses bar levels to decide entries. A long entry on a breakout of the M1 high using bid bars would actually fill at the ask, so the real entry is a spread above the bar high. Using mid bars, it's half a spread above.
In pandas, with a DataFrame of ticks indexed by timestamp and columns bid and ask:
bars = df['bid'].resample('1min').ohlc()
ask_bars = df['ask'].resample('1min').ohlc()
Keep both. Then simulate longs entering at the ask and exiting at the bid, and shorts the reverse. That reproduces the real cost without guessing a fixed spread.
A small detail: resample with the default settings labels each bar by its start time and includes ticks from the start up to but not including the next minute. MT5 does the same, so the bars should line up if your timestamps are in server time.
Empty minutes produce NaN rows. I drop them with dropna rather than forward filling, because a minute with no ticks is information.