Data Quality in Algo Trading: The Bugs That Destroy Backtests
A strategy that looks profitable in backtesting and fails immediately in production is one of the most demoralizing experiences in algo trading. The most common cause is not a flawed strategy — it is bad data that made it look good. Data quality bugs are silent: they do not raise exceptions, they produce results that look plausible and are wrong.
Bug 1: Timestamp Unit Mismatch
import polars as pl
df = pl.read_parquet("trades.parquet")
ts = df["time"][0]
if ts > 10**14:
print(f"WARNING: microseconds detected ({ts}) — divide by 1000")
else:
print(f"OK: milliseconds ({ts})")
Bug 2: Undisclosed Gaps
df = pl.read_parquet("trades.parquet").sort("time")
gaps = (
df.with_columns(pl.col("time").diff().alias("gap_ms"))
.filter(pl.col("gap_ms") > 3_600_000)
.select(["time", "gap_ms"])
)
print(f"Gaps > 1h: {len(gaps)}")
Bug 3: Duplicate Trades
n_dupes = df["trade_id"].n_unique()
print(f"Total: {len(df):,} | Unique: {n_dupes:,} | Dupes: {len(df)-n_dupes:,}")
Bug 4: Price Outliers
df = df.with_columns(
pl.col("price").rolling_median(window_size=1000).alias("median")
).with_columns(
(pl.col("price") / pl.col("median")).alias("ratio")
)
outliers = df.filter((pl.col("ratio") > 10) | (pl.col("ratio") < 0.1))
print(f"Price outliers: {len(outliers)}")
Why Disclosure Matters
No dataset is perfect. Every multi-year historical archive has some combination of the above issues. The difference between a trustworthy dataset and an untrustworthy one is not the absence of bugs — it is whether the bugs are documented. A Data Quality Disclosure lets you decide whether known issues affect your use case. An undocumented dataset gives you no basis for that decision.
Summary
The five most common data quality bugs: timestamp unit mismatch, undisclosed gaps, schema changes mid-dataset, duplicate trade records, price outliers. All five are detectable with short Python scripts. All five are silent. Run these checks on any dataset before building a strategy on top of it.