Issue 09 · September 2026 · 2 free tapes · every cash minute since 2013
Dukascopy, a Swiss bank, gives away one-minute candles for its index CFDs, and open-source tools download years of them with one command. We built our first intraday backtests on these files.
Then we checked every cash-session minute against the NYSE calendar. In 2013, about one cash minute in ten is a placeholder: a flat candle with zero volume, written because no quote arrived. And the files show quotes on 84 days when the NYSE was closed.
As downloaded, free Dukascopy minute data is not ready for a backtest: drop the placeholder minutes and the non-NYSE days first.
Checked: every one-minute candle in two tapes, the US Tech 100 and US 500 index CFDs (bid side), on NYSE sessions from January 2013 to August 2026.
01 / WHAT A PLACEHOLDER IS
A placeholder is a minute in which nothing was quoted
Each Dukascopy day file holds 1,440 one-minute candles, one per minute. When no quote arrives in a minute, the file still gets a candle. Its open, high, low and close all equal the last price, and its volume is zero.
Take Tuesday 5 November 2013. Inside the cash session, 20 minutes of the US Tech 100 file are placeholders (Figure 1).
We did not make these candles. We downloaded that day again, straight from Dukascopy, on 21 September 2026. The same 20 minutes are there.
02 / WHEN THEY HAPPEN
Placeholders crowd the early years
In the US Tech 100 file, placeholders fall from a tenth of cash minutes in 2013 to under 1% two years later. The US 500 cleans up later and relapses once (Figure 2). Both are nearly clean in recent years.
How you download matters too. dukascopy-node, an open-source downloader, drops zero-volume candles by default; its docs say they come mainly from weekends and bank holidays. The same minutes then show up as gaps, and a forward-fill in your code quietly puts them back.
03 / WHAT THEY DO TO A BACKTEST
A placeholder fills your order at a stale price
Three things go wrong. An order that fills on a placeholder gets the last quote, which may be minutes old. An opening range built partly from placeholders looks calmer than the market was. And any rule that measures minute ranges sees smaller ones than the market had.
The risk is largest in the years with the most placeholders, 2013 and 2014. In Issue 06, most of the out-of-sample shortfall against buy-and-hold comes from those two years. We cannot tell how much of it is the strategy and how much the data, which is one reason that verdict now reads unproven.
04 / THE HOLIDAY PROBLEM
The files trade on days the NYSE never opened
Count the US Tech 100 day files as sessions and you get more than 4,200 "days" since 2013, one in six of them a Sunday.
The NYSE was open on about 3,400 of them. On 84 weekdays when it was closed, all of them holidays such as Thanksgiving and the Fourth of July, the file still shows quotes during cash hours.
A backtest on the tape's own calendar trades those days, and one that counts rows as trading days annualises its returns wrongly. Our back issues now use NYSE sessions (updated 27 September).
The transferable part
Count placeholders inside the session. A placeholder has open = high = low = close and zero volume. Drop them before you build 5- or 10-minute bars, and never fill an order on one.
Put every frame on the exchange calendar. pandas_market_calendars gives NYSE sessions, holidays and early closes. Take no entries on days the exchange was closed.
Count missing minutes too. If your downloader drops zero-volume candles, placeholders turn into gaps. Check what your code does with a gap before it fills one.
Here is the checker we ran. It is 37 lines of Python and needs pandas and pandas_market_calendars. It reports each check by year.
#!/usr/bin/env python3
"""tape_check.py - does your 1-minute data hold every minute the NYSE was open?
Usage: python tape_check.py your_data.csv [--tz UTC] (--tz = timezone of your timestamps)
Needs: pip install pandas pandas_market_calendars
Input: a timestamp column plus open, high, low, close, volume (any capitalisation; o/h/l/c/v also work)."""
import sys
import pandas as pd, pandas_market_calendars as mcal
path = sys.argv[1]
tz = sys.argv[sys.argv.index('--tz') + 1] if '--tz' in sys.argv else 'UTC'
df = pd.read_parquet(path) if path.endswith('.parquet') else pd.read_csv(path)
df.columns = [c.strip().lower() for c in df.columns]
df = df.rename(columns={'open': 'o', 'high': 'h', 'low': 'l', 'close': 'c', 'volume': 'v'})
t = df[next(c for c in df.columns if c not in ('o', 'h', 'l', 'c', 'v'))]
if pd.api.types.is_numeric_dtype(t):
t = pd.to_datetime(t, unit='ms' if t.max() > 1e12 else 's', utc=True) # epoch timestamps
t = pd.to_datetime(t)
t = t.dt.tz_localize(tz) if t.dt.tz is None else t
df.index = pd.DatetimeIndex(t.dt.tz_convert('America/New_York'))
df = df[~df.index.duplicated()]
full = mcal.get_calendar('NYSE').schedule(df.index.min().date(), df.index.max().date())
sched = full[(full.market_open >= df.index.min()) & (full.market_close <= df.index.max() + pd.Timedelta(minutes=1))] # whole sessions only
want = mcal.date_range(sched, frequency='1min', closed='left', force_close=False).tz_convert('America/New_York')
flat = (df.o == df.h) & (df.h == df.l) & (df.l == df.c) & (df.v == 0) # a bar where nothing traded
got = df.index.intersection(want)
yr = pd.DataFrame({'open minutes': pd.Series(1, index=want).groupby(want.year).sum(),
'missing': pd.Series(~want.isin(df.index), index=want).groupby(want.year).sum(),
'flat, zero volume': flat[got].groupby(got.year).sum()}).fillna(0).astype(int)
yr['missing %'] = (100 * yr['missing'] / yr['open minutes']).round(1)
yr['flat %'] = (100 * yr['flat, zero volume'] / yr['open minutes']).round(1)
print(yr.to_string())
cash = df.between_time('09:30', '15:59')
traded = cash.index[~flat[cash.index].to_numpy()]
closed = sorted(set(traded.date) - set(full.index.date))
print('\nDays with trades at 09:30-16:00 ET while the NYSE was closed: %d %s' % (len(closed), [str(d) for d in closed[:8]]))
print('Flat and missing minutes are prices your backtest never really had. If the numbers look odd, check --tz first:')
print('a wrong timezone shifts the session and shows up as extra flat or missing minutes and closed-day trades.')Usage: python tape_check.py your_data.csv [--tz UTC] (a .parquet path works too; --tz names the timezone of your timestamps and defaults to UTC).
Your turn
Send us the strategy you are about to risk money on. We test one reader strategy a month for free; the first runs in Issue 13 on 27 October. Reply to this email, or write to [email protected].
Method
Data. Dukascopy one-minute bid candles for two index CFDs: US Tech 100 (USATECHIDXUSD) from January 2013 and US 500 (USA500IDXUSD) from April 2013, to 28 and 31 August 2026 respectively. We download Dukascopy's own per-day files and add nothing: no forward fill, no repair.
Calendar. NYSE sessions from pandas_market_calendars, with holidays and early closes, whole sessions only. Cash minutes run from 09:30 ET to 16:00 ET, or to the early close.
Definitions. A placeholder minute has open = high = low = close and volume 0. A missing minute is a cash minute with no candle. A non-NYSE day is a weekday on which the NYSE was closed and the file has quotes between 09:30 and 16:00 ET.
Raw check. On 21 September 2026 we downloaded the 5 November 2013 file again from Dukascopy. All 390 cash-session closes match our tape exactly, and so do the 20 placeholder minutes.
Checker tests. On a synthetic file with planted defects (15 missing minutes, 20 placeholders, quotes on Thanksgiving, the 29 November early close and a partial first day), the checker returns the exact counts.
Right of reply. We sent these findings to Dukascopy on 23 September. Dukascopy acknowledged receipt but had not replied by 28 September.
Past performance is no guarantee of future results. We check anyway.
This is research on historical data, not investment advice. Full disclaimer.





