Skip to main content

TradingHours Library

Project description

TradingHours API Docs

TradingHours.com Python Library

PyPI version Python versions GitHub Actions

TradingHours.com licenses Market Holidays and Trading Hours data for over 1,000 exchanges and trading venues around the world. This library allows clients to easily integrate market holidays and trading hours data into existing applications. This package downloads all available data from TradingHours.com, allowing you to work with the data locally.

About the Data

We support over 1,000 exchanges and trading venues, including all major currencies. See all supported markets.

Our comprehensive data covers:

  • Market holidays
  • Trading hours
  • Half-days / Irregular schedules
  • Non-settlement dates
  • Currency holidays
  • Detailed trading phases

How is the data collected?

Our global research team collects and verifies trading hours and market holidays using primary sources exclusively. Manual and automated checks ensure the highest degree of accuracy and reliability.

Once data is collected, we continually monitor for changes to ensure the data is always up-to-date. Data updates occur daily.

Getting Started

To get started, you'll need an active subscription. Learn more »

  1. Install the tradinghours package

    pip install tradinghours
    
  2. Set your API Key (Click here to get your key)

    export TRADINGHOURS_TOKEN=<your-key-goes-here>
    

You can also install with mysql or postgres dependencies, if you wish to use one of these. You can read more about this in advanced configuration options.

pip install tradinghours[mysql] 
# or 
pip install tradinghours[postgres]

Alternatives

Instead of using this Python Library, clients can also use the web-based Trading Hours API. The web-based API is programming language agnostic.

Contents


Importing Data

Run the following command to download and import official data. Ensure you have set the TRADINGHOURS_TOKEN environment variable.

$ tradinghours import
Downloading..... (0.824s)
Ingesting.......................... (12.066s)

You can check the current data status with the following subcommand:

$ tradinghours status --extended
Collecting timestamps.... (0.213s)
TradingHours Data Status:
  Remote Timestamp:   Thu Oct 26 02:08:17 2023
  Local Timestamp:    Thu Oct 26 03:12:40 2023

Reading local data.... (0.426s)
Extended Information:
  Currencies count:   30
  Markets count:      1012

Markets

View Available Markets

from tradinghours import Market

for market in Market.list_all()[:3]:
    print(market)

>>> Market: AE.ADX Abu Dhabi Securities Exchange Asia/Dubai
    Market: AE.DFM Dubai Financial Market Asia/Dubai
    Market: AE.DGCX Dubai Gold & Commodities Exchange Asia/Dubai

You can also use an * to filter the list of markets based on their fin_id:

from tradinghours import Market

for market in Market.list_all("US.*")[:3]:
  print(market)

>>> Market: US.BTEC.ACTIVES.ASIA BrokerTec America/New_York
    Market: US.BTEC.ACTIVES.LDN BrokerTec America/New_York
    Market: US.BTEC.ACTIVES.US BrokerTec America/New_York

Get a Specific Market

from tradinghours import Market

# Get by either FinID or MIC
market = Market.get('US.NYSE')
market = Market.get('XNYS')

# Easily see what attributes an object has
# (You can call this on any object)
market.pprint() # same as pprint(market.to_dict())
>>> {'exchange_name': 'New York Stock Exchange',
     'market_name': 'Canonical',
     'security_group': None,
     'timezone': 'America/New_York',
     'weekend_definition': 'Sat-Sun',
     'fin_id': 'US.NYSE',
     'mic': 'XNYS',
     'acronym': 'NYSE',
     'asset_type': 'Securities',
     'memo': 'Canonical',
     'permanently_closed': None,
     'replaced_by': None,
     'country_code': 'US'}

If a market is marked "permanently closed," it may be replaced or superseded by another market. By default, the newer market will be returned automatically. You can retrieve the older market object for historical analysis by using the follow=False parameter.

from tradinghours import Market

# AR.BCBA is permanently closed and replaced by AR.BYMA
market = Market.get('AR.BCBA')
original = Market.get('AR.BCBA', follow=False)

print(f'{market.fin_id} replaced by {market.replaced_by} on {market.permanently_closed}')
print(f'{original.fin_id} replaced by {original.replaced_by} on {original.permanently_closed}')

>>> AR.BYMA replaced by None on None
    AR.BCBA replaced by AR.BYMA on 2017-04-17

Market Status

The Market.status method will return a MarketStatus representing the status of the market at a specific datetime.

from tradinghours import Market
import datetime as dt

market = Market.get("US.NYSE")
status = market.status()
# The default datetime is the current time.
now = dt.datetime.now(dt.timezone.utc)
print(
  status.status == market.status(now).status
)
>>> True

To use a different datetime, create a timezone-aware datetime object.

from tradinghours import Market
from zoneinfo import ZoneInfo
import datetime as dt

christmas_noon = dt.datetime(2024,12,25,12,tzinfo=ZoneInfo("America/New_York"))
status = Market.get("US.NYSE").status(christmas_noon)

status.pprint() # same as pprint(status.to_dict())
>>> {'status': 'Closed',
     'reason': 'Christmas',
     'until': '2024-12-26 04:00:00-05:00',
     'next_bell': '2024-12-26 09:30:00-05:00',
     'phase': None,
     'market': 'Market: US.NYSE New York Stock Exchange America/New_York'}

Market Holidays

from tradinghours import Market

market = Market.get('US.NYSE')
holidays = market.list_holidays("2024-01-01", "2024-12-31")
for holiday in holidays[:3]:
    print(holiday)

>>> MarketHoliday: US.NYSE 2024-01-01 New Year's Day
    MarketHoliday: US.NYSE 2024-01-15 Birthday of Martin Luther King, Jr
    MarketHoliday: US.NYSE 2024-02-19 Washington's Birthday

Trading Hours

Phases

To get opening and closing times for a particular date range, use the Market.generate_phases method. This will return a generator yielding tradinghours.models.Phase objects, representing specific datetimes based on the "general schedule" of a market, considering holidays and potential schedule changes.

from tradinghours import Market

market = Market.get('XNYS')
for phase in list(market.generate_phases("2023-09-01", "2023-09-30"))[:3]:
    print(phase)

>>> Phase: 2023-09-01 04:00:00-04:00 - 2023-09-01 09:30:00-04:00 Pre-Trading Session
    Phase: 2023-09-01 06:30:00-04:00 - 2023-09-01 09:30:00-04:00 Pre-Open
    Phase: 2023-09-01 09:30:00-04:00 - 2023-09-01 09:30:00-04:00 Call Auction

Schedules

To get the "general schedule" that phases are based on, use Market.list_schedules(). This will provide a list of tradinghours.models.Schedule objects, representing the schedule without consideration of holidays. The schedule will include 'Regular,' 'Partial,' and potentially other irregular schedules. Interpreting these schedule objects can be difficult. In most cases, you will want to use the Market.generate_phases method above.

US.NYSE is one of the simplest examples for schedules:

from tradinghours import Market

market = Market.get('XNYS')
for schedule in market.list_schedules():
    print(schedule)

>>> Schedule: US.NYSE (Partial) 06:30:00 - 09:30:00    Mon-Fri Pre-Trading Session
    Schedule: US.NYSE (Partial) 09:30:00 - 13:00:00    Mon-Fri Primary Trading Session
    Schedule: US.NYSE (Partial) 13:00:00 - 13:30:00    Mon-Fri Post-Trading Session
    Schedule: US.NYSE (Regular) 04:00:00 - 09:30:00    Mon-Fri Pre-Trading Session
    Schedule: US.NYSE (Regular) 06:30:00 - 09:30:00    Mon-Fri Pre-Open
    Schedule: US.NYSE (Regular) 09:30:00 - 09:30:00    Mon-Fri Call Auction
    Schedule: US.NYSE (Regular) 09:30:00 - 16:00:00    Mon-Fri Primary Trading Session
    Schedule: US.NYSE (Regular) 15:50:00 - 16:00:00    Mon-Fri Pre-Close
    Schedule: US.NYSE (Regular) 16:00:00 - 20:00:00    Mon-Fri Post-Trading Session

US.MGEX is a more complex example, which has multiple irregular schedules and overnight trading sessions:

from tradinghours import Market

market = Market.get('US.MGEX')
for schedule in market.list_schedules()[-11:-5]:
    print(schedule)

# US.MGEX has multiple irregular schedules and overnight trading sessions
>>> Schedule: US.MGEX (Regular) 19:00:00 - 07:45:00 +1 Sun-Thu Primary Trading Session
    Schedule: US.MGEX (Thanksgiving2022) 08:00:00 - 08:30:00    Wed Pre-Open
    Schedule: US.MGEX (Thanksgiving2022) 08:30:00 - 12:15:00    Fri Primary Trading Session
    Schedule: US.MGEX (Thanksgiving2022) 08:30:00 - 13:30:00    Wed Primary Trading Session
    Schedule: US.MGEX (Thanksgiving2022) 14:30:00 - 16:00:00    Wed Post-Trading Session
    Schedule: US.MGEX (Thanksgiving2022) 16:45:00 - 08:30:00 +2 Wed Pre-Open

The string representation created by print(schedule) is using the format shown below. Other available fields are also listed. These fields are based on the data that is returned from the API's download endpoint described here.

from tradinghours import Market
schedule = Market.get('US.MGEX').list_schedules()[-6]

print(schedule.get_string_format())
schedule.pprint() # same as pprint(schedule.to_dict())

>>> Schedule: {fin_id} ({schedule_group}) {start} - {end_with_offset} {days} {phase_type}
    {'fin_id': 'US.MGEX', # Fin ID of the market of this schedule
     'schedule_group': 'Thanksgiving2022', # Used to group phases together. If there is no holiday then the “Regular” phase applies.
     'schedule_group_memo': None, # additional description for the schedule_group
     'timezone': 'America/Chicago', # timezone of the market
     'phase_type': 'Pre-Open', # normalized name for the phase
     'phase_name': 'Pre-Open', # name for the phase as it is used by the market
     'phase_memo': None, # additional description for the phase_name
     'days': 'Wed', # days of the week that this schedule applies to
     'start': '16:45:00', # start time of the phase
     'end': '08:30:00', # end time of the phase
     'offset_days': 2, # number of days that need to be added to the end time
     'duration': 143100, # total length of this phase in seconds
     'min_start': None, # earliest possible start when random start/stop times apply
     'max_start': None, # latest possible start when random start/stop times apply
     'min_end': None, # earliest possible end when random start/stop times apply
     'max_end': None, # latest possible end when random start/stop times apply
     'in_force_start_date': None, # date that this schedule starts being in effect
     'in_force_end_date': None, # date that this schedule stops being in effect
     'season_start': None, # the start of the season, if this is seasonal
     'season_end': None, # the end of the season
     'end_with_offset': '08:30:00 +2', # string representation of the end time with offset_days concatenated
     'has_season': False} # Indicator whether this schedule only applies to a specific season

As mentioned earlier, it can be very error-prone to interpret these schedules yourself, so we recommend sticking to the generate_phases method as much as possible.

Currencies

List Currencies

from tradinghours import Currency

for currency in Currency.list_all()[:3]:
    print(currency)

>>> Currency: AUD Australian Dollar
    Currency: BRL Brazilian Real
    Currency: CAD Canadian Dollar

Currency Holidays

from tradinghours import Currency

currency = Currency.get('AUD')
for holiday in currency.list_holidays("2023-06-01", "2023-12-31")[:3]:
    print(holiday)

>>> CurrencyHoliday: AUD 2023-06-12 King's Birthday
    CurrencyHoliday: AUD 2023-10-02 Labor Day
    CurrencyHoliday: AUD 2023-12-25 Christmas Day

Advanced

Optional Advanced Configuration

Configuration can be changed by creating a tradinghours.ini file in the current directory.

These are possible and optional values, for which explanations follow:

[api]
token = YOUR-TOKEN

[data]
db_url = postgresql://postgres:password@localhost:5432/your_database
table_prefix = thstore_
remote_dir = path/to/empty/folder

[control]
check_tzdata = False

Database

  • [data]
    • db_url
      • A connection string to a database. Please read the caveats before using this setting.
      • This allows you to download the data once and let your team members use the same database.
    • table_prefix
      • Every table created in the database will be prefixed with this. 'thstore_' is the default.
      • This can be used to avoid conflicts with existing tables.
    • remote_dir
      • The folder in which to save the raw CSV files after downloading with tradinghours import.
      • The content of these CSV files will immediately be ingested into the database defined in db_url and then not used anymore.
      • Unless you want to access the raw CSV files directly, there is no reason to change this.

Caveats

  • This package has been tested with MySQL 8.4 and PostgreSQL 15.8
  • Dependencies:
    • Running pip install tradinghours[mysql] or pip install tradinghours[postgres] installs pymysql or psycopg2-binary, respectively.
    • You can install any other package (e.g. mysqlclient), as long as it allows sqlalchemy to communicate with the chosen database.
  • Data ingestion:
    • Tables used by this package (identified by the table_prefix) are dropped and recreated every time tradinghours import is run.
    • To avoid any complications with existing data, we recommend creating a separate database for the tradinghours data, and making this the only database the db_url user has access to.
Schema
  • The tables are named after the CSV files, with _ instead of - and prefixed with the table_prefix setting.
  • To allow flexibility with updates to the raw data, where columns might be added in the future, tables are created dynamically, based on the content of the CSV files.
  • Columns of the tables are named after the columns of the CSV files, although in lower case and with underscores instead of spaces.

Time Zones

This package employs zoneinfo for timezone management, utilizing the IANA Time Zone Database, which is routinely updated. In certain environments, it's essential to update the tzdata package accordingly. tradinghours automatically checks your tzdata version against PyPI via HTTP request, issuing a warning if an update is needed.

To update tzdata run this command: pip install tzdata --upgrade

To disable this verification and prevent the request, add this section to your tradinghours.ini file:

[control]
check_tzdata = False

Model Configuration

Change String Format

from tradinghours import Currency

Currency.set_string_format("{currency_code}: {financial_capital} - {financial_capital_timezone}")
currency = Currency.get("EUR")
print(currency)

Currency.reset_string_format()
print(currency)

>>> EUR: Frankfurt - Europe/Berlin
    Currency: EUR Euro

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

tradinghours-0.4.0.tar.gz (52.7 kB view details)

Uploaded Source

Built Distribution

tradinghours-0.4.0-py3-none-any.whl (31.6 kB view details)

Uploaded Python 3

File details

Details for the file tradinghours-0.4.0.tar.gz.

File metadata

  • Download URL: tradinghours-0.4.0.tar.gz
  • Upload date:
  • Size: 52.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.32.3

File hashes

Hashes for tradinghours-0.4.0.tar.gz
Algorithm Hash digest
SHA256 955b917c55cb4f1aab6aa60d46a12df5189a17f44a0383dfd780120bd7985ee5
MD5 2fef66f64af61c5b15492dbdd39f758d
BLAKE2b-256 ee99a1de21b4c39418879e504c0decdfa51cde65dc021c81c0bd54133f02d198

See more details on using hashes here.

File details

Details for the file tradinghours-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for tradinghours-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dc22e7a14c80408c99798013dde33cbb39b4cab55615b28759e40bb60f251552
MD5 521b20a967b790fb04e9f319c78916ab
BLAKE2b-256 c8fc918ecb1fc2ebfaeb091441a621b56c8b72e529eeff3841f297788065e749

See more details on using hashes here.

Supported by

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