Skip to main content

A simple HTTP-based web service to query exchange calendars.

Project description

Exchange Calendar Service

PyPI Python Support PyPI Downloads

An simple HTTP service for querying exchange calendars for stock exchanges. Built on top of exchange_calendars and exchange_calendars_extensions.

Features

  • RESTful API for exchange calendar queries.
  • Support for 60+ global exchanges.
  • Query holidays, special open/close days, monthly and quarterly expiry days, and more.
  • Support for customization hooks.
  • Docker image available for easy deployment.

Installation

The package requires Python 3.11 or later.

As a tool

If you are primarily interested in running the service as a tool and without any customizations, you can use uv's tool support:

uvx exchange-calendar-service

This will start the service via Uvicorn on http://localhost:8080 by default. See http://localhost:8080/docs for auto-generated API docs.

Alternatively, install and run via pipx:

pipx install exchange-calendar-service
exchange-calendar-service

As a dependency

Add the PyPI package as a dependency to your Python project via uv:

uv add exchange-calendar-service

Or edit pyproject.toml directly:

[project]
dependencies = [
    "exchange-calendar-service=^0.1.0",
]

In a Python virtual environment, you can start the service via a script:

exchange-calendar-service

or by running the Python module directly:

python -m exchange_calendar_service

Container image

For easy deployment, the service is available as a ready-to-use container image on GitHub Container Registry.

docker run -it --rm -p 8080:8080 ghcr.io/jenskeiner/exchange_calendar_service:latest

Examples

Assuming the service is running on http://localhost:8080, here are some examples using curl. Note that you can also conveniently use the auto-generated API docs at http://localhost:8080/docs to try out the endpoints.

Supported exchanges

curl "http://localhost:8080/v1/exchanges"

returns a list of supported exchange MIC codes.

[
  "XAMS",
  "XBRU",
  "XBUD",
  "XCSE",
  "XDUB",
  "XETR",
  "XHEL",
  "XIST",
  "XLIS",
  "XLON",
  "XMAD",
  "XOSL",
  "XPAR"
]

Information about a specific exchange

curl "http://localhost:8080/v1/exchanges/XLON"

returns Information about the London Stock Exchange.

{
  "mic": "XLON",
  "tz": "Europe/London"
}

Describe a day on an exchange

curl "http://localhost:8080/v1/exchanges/XLON/days/2024-03-12"

Result (business day):

{
  "date": "2024-03-12",
  "business_day": true,
  "session": {
    "open": "08:00:00",
    "close": "16:30:00"
  },
  "tags": [
    "regular"
  ]
}
curl "http://localhost:8080/v1/exchanges/XLON/days/2024-12-15"

Result (non-business day):

{
  "date": "2024-12-15",
  "business_day": false,
  "tags": [
    "weekend"
  ]
}

Query days in a date range:

curl "http://localhost:8080/v1/exchanges/XLON/days?start=2024-12-23&end=2024-12-27"

Returns a list of descriptions of the days in range.

[
  {
    "date": "2024-12-23",
    "business_day": true,
    "session": {
      "open": "08:00:00",
      "close": "16:30:00"
    },
    "tags": [
      "regular"
    ]
  },
  {
    "date": "2024-12-24",
    "name": "Christmas Eve",
    "business_day": true,
    "session": {
      "open": "08:00:00",
      "close": "12:30:00"
    },
    "tags": [
      "special close"
    ]
  },
  {
    "date": "2024-12-25",
    "name": "Christmas",
    "business_day": false,
    "tags": [
      "holiday"
    ]
  },
  {
    "date": "2024-12-26",
    "name": "Boxing Day",
    "business_day": false,
    "tags": [
      "holiday"
    ]
  },
  {
    "date": "2024-12-27",
    "business_day": true,
    "session": {
      "open": "08:00:00",
      "close": "16:30:00"
    },
    "tags": [
      "regular"
    ]
  }
]

Configuration

The service can be configured via an .env file and/or environment variables. Environment variables must use the prefix EXCHANGE_CALENDAR_SERVICE__ to map to the correct setting.

Here's an example .env file:

exchanges='["XLON", "XNYS"]'  # Limit to these exchanges.
init=customize:init  # Set to a callable to customize calendars on startup. Format: `module:callable`.

Environment variables to the same effect:

export EXCHANGE_CALENDAR_SERVICE_EXCHANGES='["XLON", "XNYS"]'
export EXCHANGE_CALENDAR_SERVICE_INIT="customize:init"

Limiting the supported exchanges

By default, the service will support all available exchanges. In some situations, it may be convenient to limit the supported exchanges to a subset of the available exchanges. Particularly, limiting the number of exchanges improves the startup time of the service. This is because exchange_calendars initializes session data on creation of each exchange calendar. This data is not exposed via this service, but instantiating a lot of calendars can take a noticeable amount of time.

Customization

The service support customizations by executing custom code at startup time.

Init via Environment Variable

Set EXCHANGE_CALENDAR_SERVICE_INIT to a module path pointing to a callable, in the format module:callable. The callable must accept one argument (Settings). On startup, the service will import the callable and invoke it with the settings object as the single argument. This can be used to apply any customizations to the calendars, e.g. adding new calendars, removing existing calendars, registering calendar aliases, et cetera.

For example, setting EXCHANGE_CALENDAR_SERVICE_INIT="customize:init" will execute the init function from the customize module. See the example for details on how calendars can be customized.

export EXCHANGE_CALENDAR_SERVICE_INIT="customize:init"
uv run python -m exchange_calendar_service.app

Via Entrypoints

Custom code can also be discovered automatically via entry points in the exchange_calendar_service.init group. All discovered entrypoints are called sequentially, but in no particular order.

To register an entrypoint, add to your pyproject.toml:

[project.entry-points."exchange_calendar_service.init"]
my_customizer = "my_package:init_function"

Multiple packages can register entrypoints, and all will be called. This allows customization via installed dependencies without needing to set environment variables.

API Reference

Response Model

The response JSON Schema for a single day on a single exchange looks like this:

{
  "$defs": {
    "BusinessDay": {
      "properties": {
        "date": {
          "format": "date",
          "title": "Date",
          "type": "string"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "tags": {
          "items": {
            "$ref": "#/$defs/Tags"
          },
          "title": "Tags",
          "type": "array",
          "uniqueItems": true
        },
        "business_day": {
          "const": true,
          "default": true,
          "title": "Is Business Day",
          "type": "boolean"
        },
        "session": {
          "$ref": "#/$defs/Session"
        }
      },
      "required": [
        "date",
        "tags",
        "session"
      ],
      "title": "BusinessDay",
      "type": "object"
    },
    "NonBusinessDay": {
      "properties": {
        "date": {
          "format": "date",
          "title": "Date",
          "type": "string"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "tags": {
          "items": {
            "$ref": "#/$defs/Tags"
          },
          "title": "Tags",
          "type": "array",
          "uniqueItems": true
        },
        "business_day": {
          "const": false,
          "default": false,
          "title": "Is Business Day",
          "type": "boolean"
        }
      },
      "required": [
        "date",
        "tags"
      ],
      "title": "NonBusinessDay",
      "type": "object"
    },
    "Session": {
      "properties": {
        "open": {
          "format": "time",
          "title": "Open",
          "type": "string"
        },
        "close": {
          "format": "time",
          "title": "Close",
          "type": "string"
        }
      },
      "required": [
        "open",
        "close"
      ],
      "title": "Session",
      "type": "object"
    },
    "Tags": {
      "enum": [
        "special open",
        "special close",
        "quarterly expiry",
        "monthly expiry",
        "month end",
        "holiday",
        "weekend",
        "regular"
      ],
      "title": "Tags",
      "type": "string"
    }
  },
  "discriminator": {
    "mapping": {
      "False": "#/$defs/NonBusinessDay",
      "True": "#/$defs/BusinessDay"
    },
    "propertyName": "business_day"
  },
  "oneOf": [
    {
      "$ref": "#/$defs/BusinessDay"
    },
    {
      "$ref": "#/$defs/NonBusinessDay"
    }
  ]
}

The fields date, business_day and tags are always present:

  • date: The date in ISO format.
  • business_day: Whether the day is a business day or not.
  • tags: A list of tags associated with the day.

The response may optionally provide a name field, e.g. for holidays or special days.

If the day is a business day, the response contains the session field which provides the start and end time of the trading session.

Note: Session open and close times are always in the exchange's timezone.

Tags

While the business_day partitions the days into business and non-business days, tags allow to attach more fine-grained information to individual days. Each day can carry multiple tags, e.g. "holiday" and "weekend". The meaning of the tags is as follows:

  • special open: The trading session starts at a non-standard time, typically later than usual.
  • special close: The trading session ends at a non-standard time, typically earlier than usual.
  • quarterly expiry: Indicates quarterly expiry days, typically the third Thursday in March, June, September and December.
  • monthly expiry: Indicates monthly expiry days, typically the third Thursday in the other months.
  • month end: The last trading day in the respective month.
  • holiday: A holiday on which the exchange is closed.
  • weekend: A weekend day on which the exchange is regularly closed.
  • regular: The day has regular trading session times.

Examples

A regular trading day:

{
  "date": "2026-01-08",
  "business_day": true,
  "session": {
    "open": "08:00:00",
    "close": "16:30:00"
  },
  "tags": [
    "regular"
  ]
}

A regular weekend day:

{
  "date": "2026-01-10",
  "business_day": false,
  "tags": [
    "weekend"
  ]
}

A holiday that would otherwise be a business day:

{
  "date": "2026-01-01",
  "name": "New Year's Day",
  "business_day": false,
  "tags": [
    "holiday"
  ]
}

A holiday that is also a wekend day:

{
  "date": "2022-12-25",
  "name": "Christmas",
  "tags": [
    "weekend",
    "holiday"
  ],
  "business_day": false
}

A special close day that is also the last trading day of a month:

{
  "date": "2022-12-30",
  "name": "New Year's Eve",
  "business_day": true,
  "session": {
    "open": "08:00:00",
    "close": "12:30:00"
  },
  "tags": [
    "special close",
    "month end"
  ]
}

API versioning

There is currently only one version of the API. All endpoints are under /v1/.

Reference Endpoints

These endpoints return reference data for the supported exchanges.

GET /exchanges

Get a list of supported exchanges' MIC codes.

Example request:

curl http: //localhost:8080/v1/exchanges

Response:

[
  "XAMS",
  "XLON",
  "XNYS",
  "XSWX"
]

GET /exchanges/{mic}

Get information about a specific exchange.

Path parameters:

  • mic - MIC code of the exchange

Example request:

curl http://localhost:8080/v1/exchanges/XLON

Response:

{
  "mic": "XLON",
  "tz": "Europe/London"
}

Single Exchange Endpoints

These endpoints return information about one or more days for a single exchange.

GET /exchanges/{mic}/days/{day}

Describe a single day for an exchange.

Path parameters:

  • mic - MIC code of the exchange
  • day - Date in ISO format (e.g., 2024-12-25)

Example request:

curl "http://localhost:8080/v1/exchanges/XLON/days/2024-12-25"

Response:

{
  "date": "2024-12-25",
  "name": "Christmas Day",
  "business_day": false,
  "tags": [
    "holiday"
  ]
}

GET /exchanges/{mic}/days

Get days in a date range that match criteria.

Path Parameters:

  • mic - MIC code of the exchange

Query Parameters:

  • start (required) - Start date in ISO format (inclusive)
  • end (required) - End date in ISO format (inclusive)
  • business_day (optional) - Filter to only business days (true) or non-business days (false)
  • include_tags (optional, repeatable) - Only include days with all the given tags
  • exclude_tags (optional, repeatable) - Exclude days with any of the given tags
  • order (optional, default: asc) - Sort order: asc or desc
  • limit (optional) - Maximum number of days to return

Example request:

curl "http://localhost:8080/v1/exchanges/XLON/days?start=2024-12-24&end=2024-12-27&business_day=false"

Response:

[
  {
    "date": "2024-12-25",
    "name": "Christmas",
    "business_day": false,
    "tags": [
      "holiday"
    ]
  },
  {
    "date": "2024-12-26",
    "name": "Boxing Day",
    "business_day": false,
    "tags": [
      "holiday"
    ]
  }
]

Example request:

curl "http://localhost:8080/v1/exchanges/XLON/days?start=2024-12-24&end=2024-12-31&include_tags=special%20close&include_tags=month%20end&order=asc"

Response:

[
  {
    "date": "2024-12-31",
    "name": "New Year's Eve",
    "business_day": true,
    "session": {
      "open": "08:00:00",
      "close": "12:30:00"
    },
    "tags": [
      "special close",
      "month end"
    ]
  }
]

GET /exchanges/{mic}/days/{day}/next

Get the next (or previous) days matching criteria relative to a reference day.

Path Parameters:

  • mic - MIC code of the exchange
  • day - Reference date in ISO format

Query Parameters:

  • direction (optional, default: forward) - Search direction: forward or backward
  • inclusive (optional, default: true) - Include the reference day if it matches
  • end (optional) - End date to bound the search (inclusive)
  • business_day (optional) - Filter to only business days or non-business days
  • include_tags (optional, repeatable) - Only include days with all the given tags
  • exclude_tags (optional, repeatable) - Exclude days with any of the given tags
  • limit (optional) - Maximum number of days to return
  • order (optional, default: asc) - Sort order of results: asc or desc

Example:

curl "http://localhost:8080/v1/exchanges/XLON/days/2024-12-20/next?direction=forward&limit=3&business_day=false"
[
  {
    "date": "2024-12-21",
    "business_day": false,
    "tags": [
      "weekend"
    ]
  },
  {
    "date": "2024-12-22",
    "business_day": false,
    "tags": [
      "weekend"
    ]
  },
  {
    "date": "2024-12-25",
    "name": "Christmas",
    "business_day": false,
    "tags": [
      "holiday"
    ]
  }
]

Development

Clone this repository and run uv sync and you are good to go.

Testing

Run the full test suite with coverage:

uv run pytest -v tests/ --cov=exchange_calendar_service

Building the Docker Image

From the project root:

docker build -f docker/Dockerfile -t exchange-calendar-service .

Running the Container

docker run -p 8080:8080 exchange-calendar-service

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

exchange_calendar_service-0.1.0.tar.gz (112.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

exchange_calendar_service-0.1.0-py3-none-any.whl (24.6 kB view details)

Uploaded Python 3

File details

Details for the file exchange_calendar_service-0.1.0.tar.gz.

File metadata

File hashes

Hashes for exchange_calendar_service-0.1.0.tar.gz
Algorithm Hash digest
SHA256 fb955e8f4fecff0dfb19b806560db10ff227a4371434aec9f77e544dcd19bcf5
MD5 d77519810f93dea3f880b21e91d8a430
BLAKE2b-256 b23e80d9168891b4728de2036cca3c0aacffefa18659f1e467f6da7360105968

See more details on using hashes here.

Provenance

The following attestation bundles were made for exchange_calendar_service-0.1.0.tar.gz:

Publisher: release.yml on jenskeiner/exchange_calendar_service

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exchange_calendar_service-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for exchange_calendar_service-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 08960d24c1b7d8d8078e7c40db37bb04247bd1d6360fb3a208e0fef698c12222
MD5 baad0d96f8fc759c928442d89bf7cbc1
BLAKE2b-256 5e071dc342b28d9ed55e35965f93bfc3d3681425279bad0e3c8800b8960f3002

See more details on using hashes here.

Provenance

The following attestation bundles were made for exchange_calendar_service-0.1.0-py3-none-any.whl:

Publisher: release.yml on jenskeiner/exchange_calendar_service

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page