Python · TardisDevParser on GitHub

What it does

Tardis.dev sells historical market data for crypto exchanges: trades, order book snapshots, and more. This tool downloads that data and saves it to disk. Nothing else. It does not parse the data, clean it, or store it in a database. It is the first step in a larger pipeline, and it only does that first step.

The tool has two commands. tardis-fetch downloads data. tardis-inspect checks your API key and shows what data is available, without downloading anything.

Installation

python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

You need a Tardis.dev API key. Set it once as an environment variable.

cp .env.example .env   # edit .env with your real key
export TARDIS_API_KEY=your-tardis-dev-api-key-here

Checking your key and browsing data

Before downloading anything, use tardis-inspect to confirm your key works and to see what is available. It makes small, read-only requests, so nothing is downloaded.

# Confirm the API key works
tardis-inspect check-key

# List every exchange Tardis.dev has data for
tardis-inspect exchanges

# See data types, symbols, and date ranges for one exchange
tardis-inspect describe binance

Fetching data

A fetch job needs five things: an exchange, one or more data types, one or more symbols, a start date, and an end date. You can set these with command-line flags, or put them in a YAML config file.

tardis-fetch \
  --exchange binance \
  --data-types trades,book_snapshot_25 \
  --symbols BTCUSDT,ETHUSDT \
  --from-date 2024-01-01 \
  --to-date 2024-01-07 \
  --output-dir ./data

Or with a config file, which is easier to reuse across runs:

exchange: binance
data_types:
  - trades
  - book_snapshot_25
symbols:
  - BTCUSDT
  - ETHUSDT
from_date: "2024-01-01"
to_date: "2024-01-07"
output_dir: ./data
format: csv
overwrite: false
tardis-fetch --config config/example.yaml

If you use both, command-line flags win. A flag overrides the config file, and the config file overrides the TARDIS_API_KEY environment variable. This lets you keep one reusable config file and change just one or two values per run.

Downloaded files are written to <output_dir>/<exchange>/<data_type>/<symbol>/<date>.csv.gz. If a file already exists, the fetch skips it. Pass --overwrite to re-download it anyway.

How it works

FetchOptions, in config.py, holds the settings for one fetch job. It validates them: an exchange must be set, at least one data type and one symbol must be given, the start date can't be after the end date, and an API key must be present from one of the three sources. If any of that is wrong, it raises TardisConfigError with a message saying what is missing.

@dataclass
class FetchOptions:
    exchange: str
    data_types: list[str]
    symbols: list[str]
    from_date: date
    to_date: date
    output_dir: Path = Path("./data")
    api_key: str = ""
    format: str = "csv"
    overwrite: bool = False

    def __post_init__(self) -> None:
        if not self.exchange:
            raise TardisConfigError("exchange is required")
        if not self.data_types:
            raise TardisConfigError("at least one data_type is required")
        # ... symbols, dates, and api_key are checked the same way

TardisClient, in client.py, does the actual downloading. For each combination of data type, symbol, and day in the date range, it builds a URL, sends a GET request with the API key as a bearer token, and streams the response to a temporary file. Once the download finishes, it renames the temporary file to its final name. This way a failed or interrupted download never leaves a partial file at the path a later run would treat as "already downloaded".

def fetch_one(self, data_type: str, symbol: str, day: date) -> Path:
    dest = self.local_path(data_type, symbol, day)
    if dest.exists() and not self.options.overwrite:
        return dest

    url = self.build_url(data_type, symbol, day)
    response = self.session.get(url, stream=True, timeout=60)
    self._raise_for_status(response, url)

    dest.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = dest.with_suffix(dest.suffix + ".part")
    with open(tmp_path, "wb") as f:
        for chunk in response.iter_content(chunk_size=1024 * 256):
            if chunk:
                f.write(chunk)
    tmp_path.replace(dest)
    return dest

The client also retries failed requests. If Tardis.dev responds with a 429 (rate limit) or a 5xx (server error), it waits and tries again, up to five times, with a growing delay between attempts. A 401 or 403 response means the API key was rejected, and raises TardisAuthError right away instead of retrying, since retrying a bad key will never succeed.

TardisInspector, in inspector.py, is the read-only companion used by tardis-inspect. check_credentials() confirms an API key works by sending a HEAD request for a symbol and date that is known to be available, so it can check the key without downloading real data. list_exchanges() and describe_exchange() call Tardis.dev's public metadata API to list exchanges and show what data types, symbols, and date ranges each exchange has.

Errors

Three exception types cover the ways a fetch can fail, so a caller can tell them apart.

  • TardisConfigError — the fetch options are invalid, for example a missing API key or an end date before the start date.
  • TardisAuthError — the API key was rejected by Tardis.dev.
  • TardisAPIError — some other request failed, for example no data at the requested URL, or an unexpected server response.

tardis-fetch catches all three at the top level and exits with a different status code for each, so the error type is visible from the shell as well as the message.

Project structure

src/tardis_reader/
    config.py          # FetchOptions: loads, merges, and validates fetch settings
    client.py          # TardisClient: downloads data from the Datasets API
    inspector.py        # TardisInspector: checks the API key, lists available data
    exceptions.py        # TardisConfigError / TardisAuthError / TardisAPIError
    cli.py              # tardis-fetch entrypoint
    inspect_cli.py       # tardis-inspect entrypoint

config/
    example.yaml        # Sample fetch config

tests/
    test_config.py
    test_client.py
    test_inspector.py

Tests cover option parsing and validation, the download/skip/retry/error paths in the client, and the credential check and metadata lookups in the inspector. Run them with pytest.