Skip to main content

forexfactory

Fetch the Forex Factory economic calendar once and reuse it everywhere — a shared local parquet cache any of your projects can read, with the data fidelity needed for expected-vs-surprise analysis.

PyPI version Python versions CI License: MIT

Quick Start

pip install forexfactory
forexfactory populate
forexfactory query --currency USD --impact high

That's it. The parquet path is printed to stdout — pipe it straight into pandas.

PARQUET=$(forexfactory query --currency USD --impact high)
python -c "import pandas as pd; print(pd.read_parquet('$PARQUET').head())"

CLI Reference

forexfactory populate

Build the local parquet cache from on-disk raw JSON files (zero network calls by default):

forexfactory populate

Default scope: USD currency, high and holiday impact, all months on disk (~195 months, 2010-01 to 2026-03). Widen the scope with repeatable flags:

forexfactory populate --currency USD --currency EUR --impact high --impact medium

Force-rebuild every month unconditionally (bypasses the manifest skip-check — use after a schema migration):

forexfactory populate --force --raw-dir out

forexfactory refresh

Fetch months not yet in the cache from Forex Factory over the network:

forexfactory refresh

Gap-fills from the last cached month through the current month with a polite 1-second delay between requests. Does not overwrite already-settled months.

forexfactory query

Print the absolute path of the result parquet to stdout — nothing else — making it shell-friendly:

forexfactory query --currency USD --impact high

By default returns data-bearing events (with forecast/actual fields) and bank holidays. To include speeches and other no-data events:

forexfactory query --currency USD --impact high --include-no-data

If the requested currency/impact combination has not been populated, the command exits non-zero and prints actionable guidance to stderr.

forexfactory status

Report cache location, date range, scope, schema version, and settled/matured state:

forexfactory status
forexfactory status --json   # machine-readable JSON

forexfactory --version

Print the installed package version:

forexfactory --version

Library API

forexfactory.get(...) — Path to filtered parquet

import forexfactory
from pathlib import Path

path: Path = forexfactory.get(currencies=["USD"], impacts=["high"])

import pandas as pd
df = pd.read_parquet(path)

forexfactory.get(...) returns a pathlib.Path to a filtered parquet file in the local cache. All parameters are keyword-only:

path = forexfactory.get(
    currencies=["USD"],          # list of currency codes (default: ["USD", "EUR", "GBP", "JPY"])
    impacts=["high"],            # list of impact levels (default: ["high", "medium", "holiday"])
    start="2024-01",             # first month to include (YYYY-MM, optional)
    end="2024-12",               # last month to include (YYYY-MM, optional)
    include_no_data=False,       # include speeches and no-data events (default: False)
    cache_dir=None,              # override cache directory (default: ~/.cache/forexfactory)
    auto_fetch=True,             # auto-refresh matured months before returning (default: True)
)

forexfactory.read(...) — DataFrame convenience loader

forexfactory.read(...) complements get() — it accepts the same keyword-only parameters but returns a pandas.DataFrame instead of a Path, so you never need to handle a file path yourself:

import forexfactory

df = forexfactory.read(currencies=["USD"], impacts=["high"])
# df is a pandas.DataFrame with a datetime_utc DatetimeIndex and datetime_utc column

The returned DataFrame has:

  • A DatetimeIndex named datetime_utc, sorted ascending
  • datetime_utc retained as a column (for groupby/merge ergonomics)
  • All schema columns as stored — no automatic type coercion
  • A siteId column always present (null for pre-v1.1 cached months)

forexfactory.surprise(df) and forexfactory.surprise_z(df) — opt-in surprise metrics

Surprise helpers operate on the DataFrame returned by read() and return row-aligned pd.Series. They are opt-in — read() returns the plain schema; you compose surprise yourself:

import forexfactory

df = forexfactory.read(currencies=["USD"], impacts=["high"])
df["surprise"] = forexfactory.surprise(df)    # raw actual − forecast (D-01)
df["surprise_z"] = forexfactory.surprise_z(df)  # z-scored over each ebaseId's full history (D-02)
  • surprise(df): actual − forecast, verbatim. NaN when either is NaN. Sign is numeric — no polarity adjustment.
  • surprise_z(df): z-scored over each ebaseId's full history in df. NaN for groups with fewer than 2 releases or zero standard deviation.

forexfactory.actual_initial(df) and forexfactory.actual_revised(df) — point-in-time vintages

Forex Factory preserves the figure that printed on release day and reports any later restatement in the next release's revision cell, rather than overwriting the original. That makes the cache a point-in-time archive: scraping 2010 today returns 2010's first prints. These helpers expose both vintages.

df = forexfactory.read(currencies=["USD"], impacts=["high"])
df["actual_initial"] = forexfactory.actual_initial(df)  # what printed on the day
df["actual_revised"] = forexfactory.actual_revised(df)  # latest known value
  • actual_initial(df): the first-printed value — the stored actual, named so the point-in-time guarantee is explicit at the call site. Use this for backtests: surprise(df) is already built on it, so it carries no look-ahead.
  • actual_revised(df): the next release's revision when one was published, otherwise the first print. The most recent release in each series has no successor yet and so returns its own print.

Two vintages, not a full revision triangle — later restatements (GDP third estimates, annual benchmark revisions) appear only when Forex Factory surfaces them in a subsequent revision cell. Across the reference cache 45.8% of released events carry a revision.

Output Schema

The cache stores data as parquet with the following columns (schema_version 3):

Core fields (DATA-01)

Column Type Description
datetime_utc datetime64[ns, UTC] Event timestamp in UTC
currency string Currency code (e.g. USD)
impact string Impact level (high, holiday, medium, low)
title string Event name
id Int64 Forex Factory event ID (nullable)
leaked boolean Whether Forex Factory marked the event as leaked

Raw value strings (verbatim from FF)

Column Type Description
forecast_raw string Raw forecast value string (e.g. "4.3%", "202K", "")
actual_raw string Raw actual value string
previous_raw string Raw previous value string
revision_raw string Raw revision value string

Parsed numerics

Magnitude suffixes expanded (K=×1e3, M=×1e6, B=×1e9, T=×1e12). Percent divided by 100 ("4.3%" → 0.043). Unparseable or empty strings become null (NaN).

Column Type Description
forecast float64 Parsed forecast numeric (null if unparseable)
actual float64 Parsed actual numeric
previous float64 Parsed previous numeric
revision float64 Parsed revision numeric

Surprise flags and identity

Column Type Description
actualBetterWorse Int64 FF surprise flag: 1=better, 2=worse, 0=neutral/n/a (nullable)
revisionBetterWorse Int64 FF revision flag: 1=better, 2=worse, 0=neutral/n/a (nullable)
ebaseId Int64 FF metric series identifier (nullable)
country string Country code (e.g. US, UK, JN, EZ)
hasDataValues boolean True for data releases with forecast/actual/previous; False for speeches and holidays
siteId object FF site identifier (nullable — populated for new scrapes, null for pre-v1.1 cached months; prerequisite for the v2 graph API)

Cache Layout

The cache lives at ~/.cache/forexfactory/ by default (override with --cache-dir or the FOREXFACTORY_CACHE_DIR environment variable):

~/.cache/forexfactory/
|-- manifest.json          # populated scope + per-month provenance
|-- queries/               # per-scope result parquets
|   `-- USD_high_....parquet
`-- YYYY-MM.parquet        # one file per calendar month

Project Structure

forexfactory/
|-- pyproject.toml
|-- requirements.txt
|-- README.md
|-- LICENSE
|-- CHANGELOG.md
|-- CONTRIBUTING.md
|-- src/forexfactory/
|   |-- __init__.py
|   |-- cli.py
|   |-- _cache.py
|   |-- _pipeline.py
|   |-- _populate.py
|   |-- _query.py
|   |-- _refresh.py
|   |-- _scrape.py
|   `-- py.typed
|-- tests/
|   |-- test_cache.py
|   |-- test_cli.py
|   |-- test_docs.py
|   |-- test_pipeline.py
|   |-- test_populate.py
|   |-- test_query.py
|   |-- test_refresh.py
|   `-- test_scrape.py
`-- out/                    # optional raw-staging dir (populated on re-scrape)

Data Source

Data is scraped from the Forex Factory economic calendar. For personal/research use — please respect their terms of service and rate limits.

License

MIT

Metadata

Release files for forexfactory 1.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for forexfactory 1.1.2
File Size Uploaded Provenance
forexfactory-1.1.2.tar.gz 77.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for forexfactory 1.1.2
File Interpreter ABI Platform
forexfactory-1.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 120.4 kB

Release files / forexfactory-1.1.2.tar.gz

Download URL forexfactory-1.1.2.tar.gz
Size 77.1 kB
Tags Source
SHA-256 checksum
How to use checksums
1e303a02a28250f39ab49e788a22862211969d4d51b5431d1d862972237d9d0a
BLAKE2b-256 checksum
How to use checksums
79f80eec23aab4b173a8edcff07a4d58db3a8397c122a7a0ef61c4fca040fa9c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.

Transparency log

Release files / forexfactory-1.1.2-py3-none-any.whl

Download URL forexfactory-1.1.2-py3-none-any.whl
Size 43.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1a9f041961c4b517b96d0c5b716326d2ed6ce26fb61239c883971c3f38e59ea9
BLAKE2b-256 checksum
How to use checksums
d6f59739af540a4bddd4466133b597ff00091f93f669bd44d92f404444a7a76d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.

Transparency log

Security for forexfactory 1.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for forexfactory-1.1.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page