Documentation

Historical forex data in Python: sample, coverage, API, and audit paths

Use these docs to load historical forex data in Python, inspect release coverage, choose between file downloads and API/update interest, and validate known-gap caveats before backtesting: 74 symbols, 518 Parquet files, and 300,359,356 rows through 2026-05-31.

Fast answers

If Google sent you here, start with the proof path

The docs are built around measurable workflow evidence: the sample benchmark, the current R2 coverage report, and the QA distinction between structural blockers and known source-observed gaps.

How do I load historical forex data in Python?

Download the EUR/USD M1 Parquet sample, run pandas.read_parquet, inspect timestamp bounds and OHLC null counts, then compare the release coverage page before choosing a paid bundle.

Start with the sample

Should I use a forex data API or files?

Use APIs for live or frequently refreshed workflows. Use audited local Parquet files when the job is historical research, repeatable backtests, or feature generation without rate limits.

See API/update path

What proves the release is usable?

The proof layer is release coverage, row counts, file counts, loader benchmarks, and explicit known-gap caveats. It is not a broad gap-free promise.

Open coverage report

Major-8 proof

79,042,363

rows across 56 Parquet files

Coverage window

2000-05-30

to 2026-07-05

QA blockers

0

files needing structural review

Known gaps

51

files flagged for source-observed gaps

These docs are a buyer workflow map, not a gap-free claim. Start with the free sample, inspect release coverage, then use the quality/audit pages when your strategy depends on exact pair-date completeness.

Answer-first routing

Choose the shortest path from query to proof

GSC already shows documentation impressions for Python, API, and historical exchange-rate intent. Instead of browsing the whole docs page, use the route that matches the job you are trying to finish.

I searched for forex python or historical exchange rates python

Start with the sample, confirm the schema in pandas, then use the Python guide for repeatable local analysis.

Open Python workflow

I need a historical forex data download I can trust

Check audited coverage, known-gap posture, and the current paid bundle path before assuming a pair or year is included.

Review historical data options

I want recurring updates or a forex data API

The live offer starts with audited file delivery and a waitlist for manifest/API demand, not unsupported self-serve claims.

See API/update path

I already have files and need a quality answer

Use the checker or audit path when the problem is duplicate timestamps, invalid OHLC, missing bars, or platform import risk.

Scope an audit

Developer answer

Historical forex data in Python without API friction

For historical backtesting, local Parquet files are usually simpler than repeated candle API pulls. The current public EUR/USD M1 sample has 31,680 rows and loaded with pandas.read_parquet in a median 3.81 ms in the latest local benchmark.

first-python-check
import pandas as pd

df = pd.read_parquet("EURUSD_M1_sample.parquet")
print(df.dtypes)
print(df["timestamp"].min(), df["timestamp"].max())
print(df[["open", "high", "low", "close"]].isna().sum())

forex python

Use local Parquet files with pandas when the job is repeated historical research, backtesting, or feature generation.

Open Python guide

best programming language for forex data analysis

Start with Python or R for research, add DuckDB/SQL for repeatable Parquet scans, and reserve compiled languages for production systems.

Compare language choices

python forex historical data

Use the sample file to confirm timestamp parsing, OHLC types, row count, and missing-bar checks before scaling a notebook to the full release.

Validate sample file

forex python api

Use APIs for live or frequently refreshed workflows; use audited local files when you need repeatable historical scans without rate limits.

Join API/update waitlist

historical exchange rates python

Start with the EUR/USD sample, inspect timestamp and OHLC columns, then compare release coverage before expanding to more pairs.

Download sample

Start with the proof you need

If you found this page while comparing historical forex prices or evaluating a forex historical data download, use the shortest path: sample first, coverage second, known-gap review third, paid bundle fourth.

Step 1

Validate the schema

Download the free EUR/USD M1 sample to confirm your loader, timestamp handling, and local workflow before spending money.

Get the sample

Step 2

Check release coverage

Review the audited symbol and timeframe coverage before assuming a pair or year is present in the current release.

Open coverage report

Step 3

Read the caveats

The release has 0 structural review blockers, but 51 files still carry known source-observed gap flags.

Review gap status

File Structure

Each currency pair is provided as a separate Parquet file per timeframe. The Major-8 bundle currently covers 8 pairs and 56 Parquet files; the full bundle coverage report lists the wider symbol set. The naming convention is:

EURUSD_M1.parquet    # 1-minute bars
EURUSD_M5.parquet    # 5-minute bars
EURUSD_M15.parquet   # 15-minute bars
EURUSD_H1.parquet    # 1-hour bars
EURUSD_H4.parquet    # 4-hour bars
EURUSD_D1.parquet    # Daily bars
EURUSD_W1.parquet    # Weekly bars

Columns

ColumnTypeDescription
timestampdatetime64[ns]UTC timestamp for the bar open
openfloat64Opening price
highfloat64Highest price during the bar
lowfloat64Lowest price during the bar
closefloat64Closing price
volumeint64Tick volume (number of ticks)
Note: All timestamps are in UTC. Normal weekend market closures are not filled with synthetic bars. Release contents vary by bundle, so check the coverage report before purchasing.

Loader Benchmark

The current benchmark was generated on 2026-06-11 with pandas 2.3.3 and pyarrow 22.0.0. It measures local loading of the public sample and the current EUR/USD M1 release file, so use it as workflow evidence rather than a hardware-independent promise.

FileMethodRowsFile sizeMedian load
EUR/USD M1 sample Parquetpandas.read_parquet31,6800.754 MB3.81 ms
Same sample exported to CSVpandas.read_csv31,6801.687 MB18.42 ms
EUR/USD M1 full release Parquetpandas.read_parquet5,539,28988.471 MB52.05 ms
refresh benchmark
npm run data:loader-benchmark

# Outputs:
# data/loader-benchmark-report.json
# docs/LOADER_BENCHMARK_REPORT.md
Caveat: Fast loading does not prove complete market coverage. Pair/date coverage and known source-observed gaps must still be reviewed on the release coverage page.

Python / Pandas

Parquet works directly with pandas, pyarrow, DuckDB, and most data-science workflows without first expanding the delivery into large CSV files.

If you are still deciding whether to buy, pair this section with the sample download and the release coverage report so you can validate both schema and scope before checkout.

Basic Loading

python
import pandas as pd

# Load a single pair
df = pd.read_parquet('EURUSD_M1.parquet')

# View the data
print(df.head())
print(f"Rows: {len(df):,}")
print(f"Date range: {df['timestamp'].min()} to {df['timestamp'].max()}")

Loading Multiple Pairs

python
from pathlib import Path

# Load all major pairs
majors = ['EURUSD', 'GBPUSD', 'USDJPY', 'USDCHF', 'AUDUSD', 'USDCAD', 'NZDUSD', 'EURGBP']
data = {}

for pair in majors:
    data[pair] = pd.read_parquet(f'{pair}_H1.parquet')

# Access individual pair
eurusd = data['EURUSD']

Set Timestamp as Index

python
df = pd.read_parquet('EURUSD_H1.parquet')
df = df.set_index('timestamp')

# Now you can slice by date
df_2023 = df['2023']
df_q1 = df['2023-01':'2023-03']

Resample to Different Timeframes

python
# Load M1 data and resample to H4
df = pd.read_parquet('EURUSD_M1.parquet')
df = df.set_index('timestamp')

df_h4 = df.resample('4H').agg({
    'open': 'first',
    'high': 'max',
    'low': 'min',
    'close': 'last',
    'volume': 'sum'
}).dropna()

R

Use the arrow package to read Parquet files in R.

R
# Install arrow package (first time only)
install.packages("arrow")

library(arrow)

# Load data
df <- read_parquet("EURUSD_H1.parquet")

# View structure
str(df)
head(df)

# Convert timestamp to POSIXct
df$timestamp <- as.POSIXct(df$timestamp, origin = "1970-01-01", tz = "UTC")

With tidyverse

R
library(arrow)
library(dplyr)
library(lubridate)

df <- read_parquet("EURUSD_D1.parquet") %>%
  mutate(
    timestamp = as_datetime(timestamp),
    year = year(timestamp),
    returns = (close - lag(close)) / lag(close)
  )

# Annual summary
df %>%
  group_by(year) %>%
  summarize(
    total_return = prod(1 + returns, na.rm = TRUE) - 1,
    volatility = sd(returns, na.rm = TRUE) * sqrt(252)
  )

MT4 / MT5 Conversion Notes

The live paid release is Parquet-first. We do not sell MT4/MT5 conversion artifacts until the exact generated files have been audited for timestamp alignment, broker symbol mapping, and platform import behavior.

If your team needs broker-platform conversion or a quality check on existing MetaTrader history, start with the data audit request. That lets us scope the symbol names, timeframe set, and validation evidence before promising an import package.

Self-Conversion Checklist

  • Confirm whether your platform expects broker-local time, UTC, or a custom session offset.
  • Map symbols exactly, including broker suffixes such as EURUSDm.
  • Export only the timeframes you can validate against visible charts after import.
  • Compare imported first bar, last bar, row count, and several spot-check dates against the source Parquet file.
  • Run a small Strategy Tester pass before trusting long backtests from converted files.
Important: Do not treat Parquet-to-platform conversion as a file-format task only. Most bad imports come from timezone assumptions, symbol naming, duplicate bars, or platform limits.

CSV Conversion

The current release is delivered as Parquet. Convert to CSV locally when you need a smaller slice for spreadsheet review, platform imports, or a tool that does not read Parquet.

Excel Limits

  1. Convert a filtered date range rather than an entire M1 history file.
  2. Open Excel and choose Data → From Text/CSV.
  3. Check timestamp parsing and row count before using the sheet for decisions.
Note: M1 files contain millions of rows. Excel has a 1,048,576 row limit. For M1 data, use Python or load specific date ranges.

Convert Parquet to CSV (Python)

python
import pandas as pd

# Convert to CSV
df = pd.read_parquet('EURUSD_D1.parquet')
df.to_csv('EURUSD_D1.csv', index=False)

# Or filter first to reduce size
df_2023 = df[df['timestamp'].dt.year == 2023]
df_2023.to_csv('EURUSD_D1_2023.csv', index=False)

Backtesting Examples

Common patterns for running backtests with the historical data.

Simple Moving Average Crossover

python
import pandas as pd
import numpy as np

df = pd.read_parquet('EURUSD_H1.parquet')
df = df.set_index('timestamp')

# Calculate moving averages
df['sma_20'] = df['close'].rolling(20).mean()
df['sma_50'] = df['close'].rolling(50).mean()

# Generate signals
df['signal'] = np.where(df['sma_20'] > df['sma_50'], 1, -1)
df['signal'] = df['signal'].shift(1)  # Avoid lookahead bias

# Calculate returns
df['returns'] = df['close'].pct_change()
df['strategy_returns'] = df['signal'] * df['returns']

# Performance
total_return = (1 + df['strategy_returns']).prod() - 1
print(f"Strategy Return: {total_return:.2%}")

With Backtesting.py

python
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
import pandas as pd

class SmaCross(Strategy):
    n1 = 20
    n2 = 50

    def init(self):
        self.sma1 = self.I(lambda x: pd.Series(x).rolling(self.n1).mean(), self.data.Close)
        self.sma2 = self.I(lambda x: pd.Series(x).rolling(self.n2).mean(), self.data.Close)

    def next(self):
        if crossover(self.sma1, self.sma2):
            self.buy()
        elif crossover(self.sma2, self.sma1):
            self.sell()

# Load data
df = pd.read_parquet('EURUSD_H1.parquet')
df = df.set_index('timestamp')
df.columns = ['Open', 'High', 'Low', 'Close', 'Volume']

bt = Backtest(df, SmaCross, cash=10000, commission=.0002)
stats = bt.run()
print(stats)

Multi-Pair Analysis

python
import pandas as pd
import numpy as np

pairs = ['EURUSD', 'GBPUSD', 'USDJPY', 'AUDUSD']
results = {}

for pair in pairs:
    df = pd.read_parquet(f'{pair}_D1.parquet')
    df = df.set_index('timestamp')

    # Calculate daily returns
    df['returns'] = df['close'].pct_change()

    results[pair] = {
        'mean_return': df['returns'].mean() * 252,  # Annualized
        'volatility': df['returns'].std() * np.sqrt(252),
        'sharpe': (df['returns'].mean() / df['returns'].std()) * np.sqrt(252)
    }

pd.DataFrame(results).T

Available Timeframes

CodeTimeframeBars per DayCoverage note
M11 Minute1,440Varies by pair and release coverage
M55 Minutes288Derived from audited M1 bars
M1515 Minutes96Derived from audited M1 bars
H11 Hour24Derived from audited M1 bars
H44 Hours6Derived from audited M1 bars
D1Daily1Derived from audited intraday bars
W1Weekly0.2Derived from audited intraday bars

FAQ

These are the buyer and implementation questions that come up most often before someone moves from docs to a paid historical forex data download.

What timezone are the timestamps in?

All timestamps are in UTC. This is the standard for forex data and avoids daylight saving time issues.

Are weekend gaps included?

No. The data runs from Sunday ~22:00 UTC to Friday ~22:00 UTC each week. Weekend periods with no trading are excluded.

What is tick volume?

Tick volume counts the number of price changes (ticks) during each bar. It's a proxy for activity/liquidity, though not actual traded volume (forex is decentralized).

Why Parquet instead of CSV?

Parquet keeps typed columns, compresses efficiently, and works well with pandas, pyarrow, DuckDB, and warehouse-style workflows. It also avoids forcing every buyer to download and open large flat CSV files before doing real analysis.

How do I convert to CSV?

See the CSV section above. One line of Python: df.to_csv('file.csv')

Is bid/ask spread included?

The current OHLCV release does not include separate bid and ask columns. Model spread, slippage, and commission costs explicitly in your backtest, and validate those assumptions against your broker or execution venue.

Can I use this for machine learning?

Yes. Start with the sample and coverage report so you understand pair coverage, timestamp cadence, and missing-source-backed intervals before training a model.

Does this prove the data is gap-free?

No. The current Major-8 quality report shows 0 structural review blockers and 51 files with known source-observed gap flags. That is why the docs point to coverage, backfill status, and audit pages before purchase.

Need proof before buying?

Download the sample, inspect the coverage report, review known-gap status, or request an audit if your team needs custom validation.