Package: tradinghours
TradingHours.com Python LibraryTradingHours.comlicensesMarket Holidays and Trading Hours datafor over1,000exchanges and trading venues around the world.
This library allows clients to easily integrate market holidays and trading hours data into existing applications.
This packages downlods all available data from TradingHours.com and then allows you to work with the data locally.About the DataWe support over 1,000 exchanges and trading venues, including all major currencies.See all supported markets.Our comprehensive data covers:Market holidaysTrading hoursHalf-days / Irregular schedulesNon-settlement datesCurrency holidaysDetailed trading phasesHow is data collected?Our global research team collects and verifies trading hours and market holidays using exclusively primary sources.
Manual and automated checks ensure the highest degree of accuracy and reliability.Once data is collected, we then continually monitor for changes to ensure the data is always up-to-date.
Data is updated daily.Learn more »Getting StartedJust installtradinghourswith pip and set your API key.Click here to get your key.pip install tradinghours

export TRADINGHOURS_TOKEN=<your-key-goes-here>Seeadvanced configuration options.AlternativesInstead of using this Python Library, clients can also use the web-basedTrading Hours API.
The web-based API is programming language agnostic.ContentsImporting DataMarketsView Available MarketsGet A Specific MarketMarket HolidaysTrading HoursCurrenciesList CurrenciesCurrency HolidaysAdvancedOptional Advanced ConfigurationDatabase SchemaTime Zone DatabaseModel ConfigurationChange String FormatImporting DataYou just need to run the following command to download and import official data. Remember that you need to have a validTRADINGHOURS_TOKENenvironment variable.$tradinghoursimportDownloading..... (0.824s)Ingesting.......................... (12.066s)You can then check current data status with the following subcommand:$tradinghoursstatus--extendedCollecting timestamps.... (0.213s)TradingHours Data Status:Remote Timestamp:   Thu Oct 26 02:08:17 2023Local Timestamp:    Thu Oct 26 03:12:40 2023Reading local data.... (0.426s)Extended Information:Currencies count:   30Markets count:      1012MarketsView Available MarketsfromtradinghoursimportMarketformarketinMarket.list_all()[:3]:print(market)>>>Market:AE.ADXAbuDhabiSecuritiesExchangeAsia/DubaiMarket:AE.DFMDubaiFinancialMarketAsia/DubaiMarket:AE.DGCXDubaiGold&CommoditiesExchangeAsia/DubaiGet A Specific MarketfromtradinghoursimportMarket# Get by either FinID or MICmarket=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())>>>{'fin_id':'US.NYSE','exchange_name':'New York Stock Exchange','market_name':'Canonical','security_group':None,'mic':'XNYS','acronym':'NYSE','asset_type':'Securities','memo':'Canonical','permanently_closed':None,'timezone':'America/New_York','weekend_definition':'Sat-Sun','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 still retrieve the
older market object for historical analysis by using thefollow=Falseparameter.fromtradinghoursimportMarket# AR.BCBA is permanently closed and replaced by AR.BYMAmarket=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.BYMAreplacedbyNoneonNoneAR.BCBAreplacedbyAR.BYMAon2017-04-17Market HolidaysfromtradinghoursimportMarketmarket=Market.get('US.NYSE')holidays=market.list_holidays("2024-01-01","2024-12-31")forholidayinholidays[:3]:print(holiday)>>>MarketHoliday:US.NYSE2024-01-01NewYear's DayMarketHoliday:US.NYSE2024-01-15BirthdayofMartinLutherKing,JrMarketHoliday:US.NYSE2024-02-19Washington's BirthdayTrading HoursPhasesTo get open and closing times for a particular date range, use theMarket.generate_phasesmethod.
This will return a generator yieldingtradinghours.models.Phaseobjects, representing specific datetimes. These are based on the "general schedule" of a market (see next section) but consider the impact of holidays and potential changes in the schedule.fromtradinghoursimportMarketmarket=Market.get('XNYS')forphaseinlist(market.generate_phases("2023-09-01","2023-09-30"))[:3]:print(phase)>>>Phase:2023-09-0104:00:00-04:00-2023-09-0109:30:00-04:00Pre-TradingSessionPhase:2023-09-0106:30:00-04:00-2023-09-0109:30:00-04:00Pre-OpenPhase:2023-09-0109:30:00-04:00-2023-09-0109:30:00-04:00CallAuctionSchedulesTo get the "general schedule" that phases are based on, useMarket.list_schedules(). This will provide a list oftradinghours.models.Scheduleobjects, representing the schedule without consideration of holidays. The schedule will include 'Regular,' 'Partial,' and potentially other irregular schedules. Interpreting the general schedule objects can be difficult. In most cases, you will want to use theMarket.generate_phasesmethod above.US.NYSEis one of the simplest examples for schedules:fromtradinghoursimportMarketmarket=Market.get('XNYS')forscheduleinmarket.list_schedules():print(schedule)>>>Schedule:US.NYSE(Partial)06:30:00-09:30:00Mon-FriPre-TradingSessionSchedule:US.NYSE(Partial)09:30:00-13:00:00Mon-FriPrimaryTradingSessionSchedule:US.NYSE(Partial)13:00:00-13:30:00Mon-FriPost-TradingSessionSchedule:US.NYSE(Regular)04:00:00-09:30:00Mon-FriPre-TradingSessionSchedule:US.NYSE(Regular)06:30:00-09:30:00Mon-FriPre-OpenSchedule:US.NYSE(Regular)09:30:00-09:30:00Mon-FriCallAuctionSchedule:US.NYSE(Regular)09:30:00-16:00:00Mon-FriPrimaryTradingSessionSchedule:US.NYSE(Regular)15:50:00-16:00:00Mon-FriPre-CloseSchedule:US.NYSE(Regular)16:00:00-20:00:00Mon-FriPost-TradingSessionUS.MGEXis a more complex example, which has multiple irregular schedules and overnight trading sessions. (More on these fields in the next paragraph)fromtradinghoursimportMarketmarket=Market.get('US.MGEX')forscheduleinmarket.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+1Sun-ThuPrimaryTradingSessionSchedule:US.MGEX(Thanksgiving2022)08:00:00-08:30:00WedPre-OpenSchedule:US.MGEX(Thanksgiving2022)08:30:00-12:15:00FriPrimaryTradingSessionSchedule:US.MGEX(Thanksgiving2022)08:30:00-13:30:00WedPrimaryTradingSessionSchedule:US.MGEX(Thanksgiving2022)14:30:00-16:00:00WedPost-TradingSessionSchedule:US.MGEX(Thanksgiving2022)16:45:00-08:30:00+2WedPre-OpenThe string representation created byprint(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'sdownloadendpoint describedhere.fromtradinghoursimportMarketschedule=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':datetime.time(16,45),# start time of the phase'end':datetime.time(8,30),# 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 seasonAs mentioned previously, it can be very error-prone to interpret these schedules yourself, so we recommend sticking to thegenerate_phasesmethod as much as possible.CurrenciesList CurrenciesfromtradinghoursimportCurrencyforcurrencyinCurrency.list_all()[:3]:print(currency)>>>Currency:AUDAustralianDollarCurrency:BRLBrazilianRealCurrency:CADCanadianDollarCurrency HolidaysfromtradinghoursimportCurrencycurrency=Currency.get('AUD')forholidayincurrency.list_holidays("2023-06-01","2023-12-31")[:3]:print(holiday)>>>CurrencyHoliday:AUD2023-06-12King's BirthdayCurrencyHoliday:AUD2023-10-02LaborDayCurrencyHoliday:AUD2023-12-25ChristmasDayAdvancedOptional Advanced ConfigurationBy default, the library uses local file storage. Optionally you can
configure the library to use an SQL store. You can adjust settings
using atradinghours.inifile on the current working directory.Here is a sample configuration file using file system storage:[api]token=YOUR-TOKEN-HERE[data]use_db=Falselocal_dir=/srv/tradinghours/localremote_dir=/srv/tradinghours/remoteAnd here you can see one using a local SQL Alchemy database. Note that
you can use any validDatabase URL:[api]token=YOUR-TOKEN-HERE[data]use_db=Truedb_url=sqlite:///tradinghours.dbDatabase SchemaIn case you would like to directly access the tables for the SQL mode, you
can see that they all follow a very simple structure with keys for stored data
and the data with actual JSON. The library uses this simple structure that
should be compatible with nearly all database engines around.Here is the DDL for the tables currently in use:CREATETABLEthstore_currencies(idINTEGERNOTNULL,slugVARCHAR,"key"VARCHAR,dataJSON,PRIMARYKEY(id));CREATETABLEthstore_currency_holidays(idINTEGERNOTNULL,slugVARCHAR,"key"VARCHAR,dataJSON,PRIMARYKEY(id));CREATETABLEthstore_holidays(idINTEGERNOTNULL,slugVARCHAR,"key"VARCHAR,dataJSON,PRIMARYKEY(id));CREATETABLEthstore_markets(idINTEGERNOTNULL,slugVARCHAR,"key"VARCHAR,dataJSON,PRIMARYKEY(id));CREATETABLEthstore_mic_mapping(idINTEGERNOTNULL,slugVARCHAR,"key"VARCHAR,dataJSON,PRIMARYKEY(id));CREATETABLEthstore_schedules(idINTEGERNOTNULL,slugVARCHAR,"key"VARCHAR,dataJSON,PRIMARYKEY(id));CREATETABLEthstore_season_definitions(idINTEGERNOTNULL,slugVARCHAR,"key"VARCHAR,dataJSON,PRIMARYKEY(id));Time Zone DatabaseThis package employszoneinfofor timezone management, utilizing the IANA Time Zone Database,
which is routinely updated. In certain environments, it's essential to update thetzdatapackage accordingly.tradinghoursautomatically checks yourtzdataversion against PyPI via HTTP request, issuing a warning
if an update is needed.To updatetzdatarun this command:pip install tzdata --upgradeTo disable this verification and prevent the request, add this section to your tradinghours.ini file:[control]check_tzdata=FalseModel ConfigurationChange String FormatfromtradinghoursimportCurrencyCurrency.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/BerlinCurrency:EUREuro