diff --git a/openbb_terminal/stocks/stocks_controller.py b/openbb_terminal/stocks/stocks_controller.py index 00386733baf..27ecc1c1308 100644 --- a/openbb_terminal/stocks/stocks_controller.py +++ b/openbb_terminal/stocks/stocks_controller.py @@ -64,6 +64,7 @@ class StocksController(StockBaseController): "dd", "ca", "options", + "th", ] PATH = "/stocks/" @@ -121,6 +122,7 @@ Stock: [/param]{stock_text} codes FIGI, SIK and SIC codes codes[/cmds] [src][Polygon.io][/src] [menu] +> th trading hours, \t\t\t check open markets > options options menu, \t\t\t e.g.: chains, open interest, greeks, parity > disc discover trending stocks, \t e.g.: map, sectors, high short interest > sia sector and industry analysis, \t e.g.: companies per sector, quick ratio per industry and country @@ -514,6 +516,15 @@ Stock: [/param]{stock_text} self.queue = self.load_class(OptionsController, self.ticker, self.queue) + @log_start_end(log=logger) + def call_th(self, _): + """Process th command""" + from openbb_terminal.stocks.tradinghours.tradinghours_controller import ( + TradingHoursController, + ) + + self.queue = self.load_class(TradingHoursController, self.queue) + @log_start_end(log=logger) def call_res(self, _): """Process res command""" diff --git a/openbb_terminal/stocks/tradinghours/README.md b/openbb_terminal/stocks/tradinghours/README.md new file mode 100644 index 00000000000..a4984eec282 --- /dev/null +++ b/openbb_terminal/stocks/tradinghours/README.md @@ -0,0 +1,21 @@ +# Trading Hours + +initial code by **Neosb** Szymon Błaszczyński @museyoucoulduse [github.com/buahaha](https://github.com/buahaha) + +## Usage + +1. `tradinghours` +2. `symbol MSFT` + +or + +1. `tradinghours` + - `open` + - `closed` + - `all` + +___ + +## Reference data + +[Exchanges in Yahoo Finance](https://help.yahoo.com/kb/SLN2310.html) diff --git a/openbb_terminal/stocks/tradinghours/__init__.py b/openbb_terminal/stocks/tradinghours/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/openbb_terminal/stocks/tradinghours/bursa_model.py b/openbb_terminal/stocks/tradinghours/bursa_model.py new file mode 100644 index 00000000000..67261b37a30 --- /dev/null +++ b/openbb_terminal/stocks/tradinghours/bursa_model.py @@ -0,0 +1,264 @@ +"""Stocks Trading Hours Model.""" + +from datetime import datetime + +import logging +import os + +import pandas as pd +import pytz + +from openbb_terminal.decorators import log_start_end + +logger = logging.getLogger(__name__) + + +@log_start_end(log=logger) +def get_bursa(symbol: str) -> pd.DataFrame: + """Get current exchange open hours. + + Parameters + ---------- + symbol : str + Exchange symbol + + Returns + ------- + pd.DataFrame + Exchange info + """ + bursa = all_bursa() + + symbol = symbol.upper() + if symbol in bursa["short_name"].values: + df = pd.DataFrame(bursa.loc[bursa["short_name"] == symbol]).transpose() + is_open = check_if_open(bursa, symbol) + df = df.append( + pd.DataFrame([is_open], index=["open"], columns=df.columns.values) + ) + return df + if symbol in bursa.index: + df = pd.DataFrame(bursa.loc[symbol]) + is_open = check_if_open(bursa, symbol) + df = df.append( + pd.DataFrame([is_open], index=["open"], columns=df.columns.values) + ) + return df + return pd.DataFrame() + + +@log_start_end(log=logger) +def get_open() -> pd.DataFrame: + """Get open exchanges. + + Parameters + ---------- + + Returns + ------- + pd.DataFrame + Currently open exchanges + """ + bursa = all_bursa() + is_open_list = [] + for exchange in bursa.index: + is_open = check_if_open(bursa, exchange) + is_open_list.append(is_open) + bursa["open"] = is_open_list + bursa = bursa.loc[bursa["open"]] + return bursa[["name", "short_name"]] + + +@log_start_end(log=logger) +def get_closed() -> pd.DataFrame: + """Get closed exchanges. + + Parameters + ---------- + + Returns + ------- + pd.DataFrame + Currently closed exchanges + """ + bursa = all_bursa() + is_open_list = [] + for exchange in bursa.index: + is_open = check_if_open(bursa, exchange) + is_open_list.append(is_open) + bursa["open"] = is_open_list + bursa = bursa.loc[~bursa["open"]] + return bursa[["name", "short_name"]] + + +@log_start_end(log=logger) +def get_all() -> pd.DataFrame: + """Get all exchanges. + + Parameters + ---------- + + Returns + ------- + pd.DataFrame + All available exchanges + """ + bursa = all_bursa() + is_open_list = [] + for exchange in bursa.index: + is_open = check_if_open(bursa, exchange) + is_open_list.append(is_open) + bursa["open"] = is_open_list + return bursa[["name", "short_name", "open"]] + + +@log_start_end(log=logger) +def get_all_exchange_short_names() -> pd.DataFrame: + """Get all exchanges short names. + + Parameters + ---------- + + Returns + ------- + pd.DataFrame + All available exchanges short names + """ + bursa = all_bursa() + is_open_list = [] + for exchange in bursa.index: + is_open = check_if_open(bursa, exchange) + is_open_list.append(is_open) + bursa["open"] = is_open_list + return bursa[["short_name"]] + + +@log_start_end(log=logger) +def all_bursa(): + """Get all exchanges from dictionary + + Parameters + __________ + + Returns + _______ + pd.DataFrame + All exchanges + """ + path = os.path.join(os.path.dirname(__file__), "data/bursa_open_hours.json") + bursa = pd.read_json(path) # , orient="index") + return bursa + + +def check_if_open(bursa, exchange): + """Check if market open helper function + + Parameters + __________ + bursa + pd.DataFrame of all exchanges + exchange + bursa pd.DataFrame index value for exchande + + Returns + _______ + bool + If market is open + """ + exchange = exchange.upper() + if exchange in bursa.index.values: + tz = bursa.loc[exchange]["timezone"] + exchange_df = bursa.loc[exchange] + elif exchange in bursa["short_name"].values: + tz = bursa.loc[bursa["short_name"] == exchange]["timezone"].values[0] + exchange_df = bursa.loc[bursa["short_name"] == exchange] + exchange_df = exchange_df.iloc[0].transpose() + utcmoment_naive = datetime.utcnow() + utcmoment = utcmoment_naive.replace(tzinfo=pytz.utc) + localDatetime = utcmoment.astimezone(pytz.timezone(tz)) + market_open = datetime.strptime(exchange_df["market_open"], "%H:%M:%S") + market_close = datetime.strptime(exchange_df["market_close"], "%H:%M:%S") + try: + lunchbreak_start = datetime.strptime( + exchange_df["lunchbreak_start"], "%H:%M:%S" + ) + lunchbreak_end = datetime.strptime(exchange_df["lunchbreak_end"], "%H:%M:%S") + except Exception: + lunchbreak_end = None + lunchbreak_start = None + + if localDatetime.weekday() >= 5: + result = False + elif ( + localDatetime.hour > market_open.hour and localDatetime.hour < market_close.hour + ): + if lunchbreak_start is not None and lunchbreak_end is not None: + if ( + localDatetime.hour > lunchbreak_start.hour + and localDatetime.hour < lunchbreak_end.hour + ): + result = False + elif ( + localDatetime.hour > lunchbreak_start.hour + and localDatetime.hour == lunchbreak_end.hour + and localDatetime.minute < lunchbreak_end.minute + ): + result = False + elif ( + localDatetime.hour == lunchbreak_start.hour + and localDatetime.minute >= lunchbreak_start.minute + ): + result = False + else: + result = True + elif ( + localDatetime.hour > market_open.hour + and localDatetime.hour == market_close.hour + and localDatetime.minute < market_close.minute + ): + if lunchbreak_start is not None and lunchbreak_end is not None: + if ( + localDatetime.hour > lunchbreak_start.hour + and localDatetime.hour < lunchbreak_end.hour + ): + result = False + elif ( + localDatetime.hour > lunchbreak_start.hour + and localDatetime.hour == lunchbreak_end.hour + and localDatetime.minute < lunchbreak_end.minute + ): + result = False + elif ( + localDatetime.hour == lunchbreak_start.hour + and localDatetime.minute >= lunchbreak_start.minute + ): + result = False + else: + result = True + elif ( + localDatetime.hour == market_open.hour + and localDatetime.minute >= market_open.minute + ): + if lunchbreak_start is not None and lunchbreak_end is not None: + if ( + localDatetime.hour > lunchbreak_start.hour + and localDatetime.hour < lunchbreak_end.hour + ): + result = False + elif ( + localDatetime.hour > lunchbreak_start.hour + and localDatetime.hour == lunchbreak_end.hour + and localDatetime.minute < lunchbreak_end.minute + ): + result = False + elif ( + localDatetime.hour == lunchbreak_start.hour + and localDatetime.minute >= lunchbreak_start.minute + ): + result = False + else: + result = True + else: + result = False + + return result diff --git a/openbb_terminal/stocks/tradinghours/bursa_view.py b/openbb_terminal/stocks/tradinghours/bursa_view.py new file mode 100644 index 00000000000..a7469758314 --- /dev/null +++ b/openbb_terminal/stocks/tradinghours/bursa_view.py @@ -0,0 +1,104 @@ +"""Stocks Trading Hours View.""" +__docformat__ = "numpy" + +import logging + +from openbb_terminal.decorators import log_start_end +from openbb_terminal.stocks.tradinghours import bursa_model +from openbb_terminal.helper_funcs import print_rich_table +from openbb_terminal.rich_config import console + +logger = logging.getLogger(__name__) + + +@log_start_end(log=logger) +def display_exchange(symbol: str): + """Display current exchange trading hours. + + Parameters + ---------- + symbol : str + Exchange symbol + """ + exchange = bursa_model.get_bursa(symbol) + + if len(exchange) == 0 or exchange.empty: + console.print( + "\n[red]" + + "No exchange data loaded.\n" + + "Make sure you picked proper exchange symbol." + + "[/red]\n" + ) + return + + exchange_name = exchange.loc["name"] + + print_rich_table( + exchange, + show_index=True, + title=f"[bold]{exchange_name}[/bold]", + ) + console.print("") + + +@log_start_end(log=logger) +def display_open(): + """Display open exchanges. + + Parameters + ---------- + """ + exchanges = bursa_model.get_open() + + if exchanges.empty: + console.print("\n[red]" + "No exchange data loaded.\n" + "[/red]\n") + return + + print_rich_table( + exchanges, + show_index=True, + title="[bold]Open markets[/bold]", + ) + console.print("") + + +@log_start_end(log=logger) +def display_closed(): + """Display closed exchanges. + + Parameters + ---------- + """ + exchanges = bursa_model.get_closed() + + if exchanges.empty: + console.print("\n[red]" + "No exchange data loaded.\n" + "[/red]\n") + return + + print_rich_table( + exchanges, + show_index=True, + title="[bold]Closed markets[/bold]", + ) + console.print("") + + +@log_start_end(log=logger) +def display_all(): + """Display all exchanges. + + Parameters + ---------- + """ + exchanges = bursa_model.get_all() + + if exchanges.empty: + console.print("\n[red]" + "No exchange data loaded.\n" + "[/red]\n") + return + + print_rich_table( + exchanges, + show_index=True, + title="[bold]World markets[/bold]", + ) + console.print("") diff --git a/openbb_terminal/stocks/tradinghours/data/__init__.py b/openbb_terminal/stocks/tradinghours/data/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/openbb_terminal/stocks/tradinghours/data/bursa_open_hours.json b/openbb_terminal/stocks/tradinghours/data/bursa_open_hours.json new file mode 100644 index 00000000000..04e1ec9e17d --- /dev/null +++ b/openbb_terminal/stocks/tradinghours/data/bursa_open_hours.json @@ -0,0 +1,1029 @@ +{ + "name": { + "SHZ": "Shenzen Stock Exchange", + "KSC": "Korea Exchange", + "KOE": "Korea Exchange", + "HKG": "Hong Kong Stock Exchange", + "KLS": "Bursa Malaysia", + "FRA": "Frankfurt Stock Exchange", + "STU": "B\u00f6rse Stuttgart", + "ENX": "Euronext", + "TWO": "Taipei Exchange", + "HAM": "Hamburger B\u00f6rse", + "MUN": "B\u00f6rse M\u00fcnchen", + "DUS": "B\u00f6rse D\u00fcsseldorf", + "TW": "Taiwan Stock Exchange", + "PAR": "Euronext Paris", + "GER": "Deutsche B\u00f6rse Xetra", + "IOB": "London Stock Exchange", + "LSE": "London Stock Exchange", + "MEX": "Bolsa Mexicana de Valores", + "HAN": "B\u00f6rsen Hamburg-Hannover", + "SAU": "Tadwul", + "JPX": "Japan Exchange Group", + "ASX": "Australian Securities Exchange", + "SAP": "Sapporo Securities Exchange", + "FKA": "Frankfurt Stock Exchange", + "SES": "Singapore Exchange", + "TLO": "Borsa Italiana", + "VIE": "Wiener B\u00f6rse", + "BSE": "BSE", + "OSL": "Oslo B\u00f8rs", + "NSI": "National Stock Exchange of India", + "STO": "Nasdaq Nordic Stockholm", + "MCE": "Bolsa de Madrid", + "MIL": "Borsa Italiana", + "BUD": "Budapest Stock Exchange", + "SHH": "Shanghai Stock Exchange", + "SET": "Stock Exchange of Thailand", + "NYQ": "New York Stock Exchange", + "VAN": "TSX Venture Exchange", + "SAO": "A Bolsa do Brasil", + "ISE": "Euronext Dublin", + "AMS": "Euronext Amsterdam", + "ATH": "Athens Stock Exchange", + "PNK": "OTC Markets Group", + "AQS": "Aquis Exchange", + "CPH": "Nasdaq Nordic Copenhagen", + "TOR": "Toronto Stock Exchange", + "NGM": "NasdaqGM", + "NCM": "Nasdaq", + "NMS": "NasdaqGS", + "CSE": "Colombo Stock Exchange", + "JKT": "Indonesia Stock Exchange", + "HEL": "Nasdaq Nordic Helsinki", + "EBS": "SIX Swiss Exchange", + "ASE": "NYSE American", + "BUE": "Bolsas y Mercados Argentinos", + "SGO": "Bolsa de Santiago", + "CNQ": "Canadian Securities Exchange: CSE", + "NZE": "NZX", + "CCS": "Bolsa de Valores de Caracas", + "JNB": "JSE", + "BRU": "Euronext Brussels", + "TLV": "Tel Aviv Stock Exchange", + "DOH": "Qatar Stock Exchange", + "MCX": "MOEX", + "NEO": "NEO Exchange", + "NYS": "NYS", + "IST": "Borsa Istanbul", + "NAS": "NASDAQ", + "LIS": "Euronext Lisbon", + "LIT": "Nasdaq OMX Vilnius", + "PRA": "Prague Stock Exchange", + "TAL": "Nasdaq OMX Tallinn", + "ICE": "Nasdaq Nordic Iceland", + "RIS": "Nasdaq OMX Riga", + "CAI": "The Egyptian Exchange", + "PCX": "NYSE Arca", + "SAT": "Nasdaq OMX Stockholm" + }, + "short_name": { + "SHZ": "SHZE", + "KSC": "KRX", + "KOE": "KRX", + "HKG": "HKEX", + "KLS": "KLSE", + "FRA": "F", + "STU": "SG", + "ENX": "NX", + "TWO": "TWO", + "HAM": "HM", + "MUN": "MU", + "DUS": "DU", + "TW": "TWSE", + "PAR": "PA", + "GER": "DE", + "IOB": "IL", + "LSE": "L", + "MEX": "MX", + "HAN": "HA", + "SAU": "SR", + "JPX": "T", + "ASX": "AX", + "SAP": "S", + "FKA": "F", + "SES": "SI", + "TLO": "TI", + "VIE": "VI", + "BSE": "BO", + "OSL": "OL", + "NSI": "NS", + "STO": "ST", + "MCE": "MC", + "MIL": "MI", + "BUD": "BD", + "SHH": "SS", + "SET": "BK", + "NYQ": "NYSE", + "VAN": "V", + "SAO": "SA", + "ISE": "IR", + "AMS": "AS", + "ATH": "AT", + "PNK": "OTC", + "AQS": "AQ", + "CPH": "CO", + "TOR": "TO", + "NGM": "NASGM", + "NCM": "NASDQ", + "NMS": "NASGS", + "CSE": "CM", + "JKT": "JK", + "HEL": "HE", + "EBS": "SW", + "ASE": "AMEX", + "BUE": "BA", + "SGO": "SN", + "CNQ": "CN", + "NZE": "NZ", + "CCS": "CR", + "JNB": "JO", + "BRU": "BR", + "TLV": "TA", + "DOH": "QA", + "MCX": "ME", + "NEO": "NE", + "NYS": "NYS", + "IST": "IS", + "NAS": "NAS", + "LIS": "LS", + "LIT": "VS", + "PRA": "PR", + "TAL": "TL", + "ICE": "IC", + "RIS": "RG", + "CAI": "CA", + "PCX": "ARCA", + "SAT": "ST" + }, + "website": { + "SHZ": "http:\/\/www.szse.cn\/English\/services\/trading\/tradOverview\/", + "KSC": "http:\/\/global.krx.co.kr\/contents\/GLB\/06\/0603\/0603010300\/GLB0603010300.jsp", + "KOE": "http:\/\/global.krx.co.kr\/contents\/GLB\/06\/0603\/0603010300\/GLB0603010300.jsp", + "HKG": "https:\/\/www.hkex.com.hk\/Services\/Trading-hours-and-Severe-Weather-Arrangements\/Trading-Hours\/Securities-Market?sc_lang=en", + "KLS": "https:\/\/www.bursamalaysia.com\/trade\/trading_resources\/equities\/trading_sessions", + "FRA": "https:\/\/www.xetra.com\/xetra-en\/trading\/trading-calendar-and-trading-hours", + "STU": "https:\/\/www.boerse-stuttgart.de\/-\/media\/files\/geschaeftspartner\/regelwerke\/en\/exchange-rules\/20220101-exchange-rules-clean.ashx?la=en&hash=0F73CA59A4AC13AF6C734CDB808098D1", + "ENX": "https:\/\/www.euronext.com\/en\/trade\/trading-hours-holidays", + "TWO": "https:\/\/www.tpex.org.tw\/web\/service\/sotck_info\/feature\/feature.php?l=en-us", + "HAM": "http:\/\/www.hamburger-boerse.de\/en\/the-stock-exchange\/markets.html", + "MUN": "https:\/\/www.boerse-muenchen.de\/boersenplatz", + "DUS": "", + "TW": "https:\/\/www.twse.com.tw\/en\/page\/products\/trading\/information13.html", + "PAR": "https:\/\/www.euronext.com\/en\/trade\/trading-hours-holidays", + "GER": "https:\/\/www.xetra.com\/xetra-en\/trading\/trading-calendar-and-trading-hours", + "IOB": "https:\/\/www.londonstockexchange.com\/trade\/equity-trading", + "LSE": "https:\/\/www.londonstockexchange.com\/trade\/equity-trading", + "MEX": null, + "HAN": "https:\/\/www.boersenag.de\/Aktienhandel", + "SAU": "https:\/\/www.saudiexchange.sa\/wps\/portal\/tadawul\/knowledge-center\/about\/trading-times?locale=en", + "JPX": "https:\/\/www.jpx.co.jp\/english\/derivatives\/rules\/trading-hours\/index.html", + "ASX": "https:\/\/www2.asx.com.au\/markets\/market-resources\/trading-hours-calendar\/cash-market-trading-hours", + "SAP": "https:\/\/www.sse.or.jp\/torihiki\/kabushiki", + "FKA": "https:\/\/www.xetra.com\/xetra-en\/trading\/trading-calendar-and-trading-hours", + "SES": "http:\/\/rulebook.sgx.com\/rulebook\/regulatory-notice-821-trading-hours-market-phases-application-market-phases-and-principles", + "TLO": "https:\/\/www.borsaitaliana.it\/borsaitaliana\/calendario-e-orari-di-negoziazione\/tradinghours-2021.en_pdf.htm", + "VIE": "https:\/\/www.wienerborse.at\/en\/trading\/trading-information\/trading-hours\/", + "BSE": "https:\/\/www.bseindia.com\/static\/markets\/equity\/EQReports\/tra_trading.aspx", + "OSL": "https:\/\/www.euronext.com\/en\/trade\/trading-hours-holidays", + "NSI": "https:\/\/www.nseindia.com\/resources\/exchange-communication-holidays", + "STO": "http:\/\/www.nasdaqomxnordic.com\/tradinghours", + "MCE": "https:\/\/www.bolsamadrid.es\/ing\/Inversores\/Agenda\/HorarioMercado.aspx", + "MIL": "https:\/\/www.borsaitaliana.it\/borsaitaliana\/calendario-e-orari-di-negoziazione\/tradinghours-2021.en_pdf.htm", + "BUD": "https:\/\/www.bse.hu\/Products-and-Services\/Trading-information\/Trading-hours", + "SHH": "http:\/\/english.sse.com.cn\/start\/trading\/schedule\/", + "SET": "https:\/\/www.set.or.th\/en\/products\/trading\/equity\/tradingsystem_p2.html", + "NYQ": "https:\/\/www.nyse.com\/markets\/hours-calendars", + "VAN": "https:\/\/www.tsx.com\/trading\/calendars-and-trading-hours\/trading-hours", + "SAO": "https:\/\/www.b3.com.br\/en_us\/solutions\/platforms\/puma-trading-system\/for-members-and-traders\/trading-hours\/equities\/", + "ISE": "https:\/\/www.euronext.com\/en\/trade\/trading-hours-holidays", + "AMS": "https:\/\/www.euronext.com\/en\/trade\/trading-hours-holidays", + "ATH": "https:\/\/www.athexgroup.gr\/documents\/10180\/4974340\/OASIS+Markets+Schedule_+Oct+2021+ver+1.pdf\/f8613e1f-5b6e-4eb0-a891-05a5b9ad1492", + "PNK": "https:\/\/www.otcmarkets.com\/learn\/faqs", + "AQS": "https:\/\/www.aquis.eu\/trading\/trading-calendar-hours", + "CPH": "http:\/\/www.nasdaqomxnordic.com\/tradinghours", + "TOR": "https:\/\/www.tsx.com\/trading\/calendars-and-trading-hours\/trading-hours", + "NGM": "https:\/\/www.nasdaq.com\/stock-market-trading-hours-for-nasdaq", + "NCM": "https:\/\/www.nasdaq.com\/stock-market-trading-hours-for-nasdaq", + "NMS": "https:\/\/www.nasdaq.com\/stock-market-trading-hours-for-nasdaq", + "CSE": "https:\/\/www.cse.lk\/pages\/trading-sessions\/trading-sessions.component.html", + "JKT": "https:\/\/www.idx.co.id\/en-us\/investhub\/trading-hours\/", + "HEL": "http:\/\/www.nasdaqomxnordic.com\/tradinghours", + "EBS": "https:\/\/www.six-group.com\/en\/products-services\/the-swiss-stock-exchange\/trading\/trading-provisions\/trading-hours.html", + "ASE": "https:\/\/www.nyse.com\/markets\/nyse-arca\/market-info", + "BUE": "https:\/\/www.byma.com.ar\/servicios\/horarios\/", + "SGO": "https:\/\/www.bolsadesantiago.com\/mercado_horarios_feriados", + "CNQ": "https:\/\/www.thecse.com\/en\/trading\/trading-rules-and-links\/trading-rules-and-regulations", + "NZE": "https:\/\/www.nzx.com\/services\/nzx-trading\/hours-boards", + "CCS": "https:\/\/www.bolsadecaracas.com\/nosotros\/#horario", + "JNB": "https:\/\/www.jse.co.za\/sites\/default\/files\/media\/documents\/market-regulation\/Equities%20Directives.pdf", + "BRU": "https:\/\/www.euronext.com\/en\/trade\/trading-hours-holidays", + "TLV": "https:\/\/info.tase.co.il\/eng\/trading\/trading_schedule\/pages\/trading_schedule.aspx", + "DOH": "https:\/\/www.qe.com.qa\/web\/venture-market\/trading-hours", + "MCX": "https:\/\/www.moex.com\/en\/", + "NEO": "https:\/\/www.neo.inc\/en\/resources?item=trading-hours", + "NYS": "", + "IST": "https:\/\/www.borsaistanbul.com\/en\/sayfa\/3637\/trading-hours", + "NAS": "https:\/\/www.nasdaq.com\/stock-market-trading-hours-for-nasdaq", + "LIS": "https:\/\/www.euronext.com\/en\/trade\/trading-hours-holidays", + "LIT": "https:\/\/nasdaqbaltic.com\/market-information\/trading-day\/", + "PRA": "https:\/\/www.pse.cz\/en\/trading\/trading-information\/trading-hours", + "TAL": "https:\/\/nasdaqbaltic.com\/market-information\/trading-day\/", + "ICE": "http:\/\/www.nasdaqomxnordic.com\/tradinghours", + "RIS": "https:\/\/nasdaqbaltic.com\/market-information\/trading-day\/", + "CAI": "https:\/\/www.egx.com.eg\/en\/Trading_Hours.aspx", + "PCX": "https:\/\/www.nyse.com\/markets\/hours-calendars", + "SAT": "http:\/\/www.nasdaqomxnordic.com\/tradinghours" + }, + "market_open": { + "SHZ": "09:30:00", + "KSC": "09:00:00", + "KOE": "09:00:00", + "HKG": "09:30:00", + "KLS": "09:00:00", + "FRA": "08:00:00", + "STU": "08:00:00", + "ENX": "09:00:00", + "TWO": "09:00:00", + "HAM": "08:00:00", + "MUN": "08:00:00", + "DUS": "08:00:00", + "TW": "09:00:00", + "PAR": "09:00:00", + "GER": "09:00:00", + "IOB": "08:00:00", + "LSE": "08:00:00", + "MEX": "08:30:00", + "HAN": "09:00:00", + "SAU": "10:00:00", + "JPX": "09:00:00", + "ASX": "10:00:00", + "SAP": "09:00:00", + "FKA": "08:00:00", + "SES": "09:00:00", + "TLO": "09:00:00", + "VIE": "09:00:00", + "BSE": "09:15:00", + "OSL": "09:00:00", + "NSI": "09:15:00", + "STO": "09:00:00", + "MCE": "09:00:00", + "MIL": "09:00:00", + "BUD": "09:00:00", + "SHH": "09:30:00", + "SET": "10:00:00", + "NYQ": "09:30:00", + "VAN": "09:30:00", + "SAO": "10:00:00", + "ISE": "09:00:00", + "AMS": "09:00:00", + "ATH": "10:30:00", + "PNK": "06:00:00", + "AQS": "08:00:00", + "CPH": "09:00:00", + "TOR": "09:30:00", + "NGM": "09:30:00", + "NCM": "09:30:00", + "NMS": "09:30:00", + "CSE": "09:30:00", + "JKT": "09:00:00", + "HEL": "09:00:00", + "EBS": "09:00:00", + "ASE": "09:30:00", + "BUE": "11:00:00", + "SGO": "09:30:00", + "CNQ": "08:00:00", + "NZE": "10:00:00", + "CCS": "09:00:00", + "JNB": "09:00:00", + "BRU": "09:00:00", + "TLV": "09:59:00", + "DOH": "09:30:00", + "MCX": "01:01:00", + "NEO": "09:30:00", + "NYS": "09:30:00", + "IST": "10:00:00", + "NAS": "09:30:00", + "LIS": "09:00:00", + "LIT": "10:00:00", + "PRA": "09:00:00", + "TAL": "10:00:00", + "ICE": "07:30:00", + "RIS": "10:00:00", + "CAI": "10:00:00", + "PCX": "09:30:00", + "SAT": "09:00:00" + }, + "market_close": { + "SHZ": "14:57:00", + "KSC": "15:35:00", + "KOE": "15:35:00", + "HKG": "16:00:00", + "KLS": "16:45:00", + "FRA": "22:00:00", + "STU": "22:00:00", + "ENX": "17:30:00", + "TWO": "13:30:00", + "HAM": "20:00:00", + "MUN": "22:00:00", + "DUS": "22:00:00", + "TW": "13:25:00", + "PAR": "17:30:00", + "GER": "17:30:00", + "IOB": "16:30:00", + "LSE": "16:30:00", + "MEX": "15:00:00", + "HAN": "17:30:00", + "SAU": "15:20:00", + "JPX": "15:00:00", + "ASX": "16:00:00", + "SAP": "15:30:00", + "FKA": "22:00:00", + "SES": "17:00:00", + "TLO": "17:30:00", + "VIE": "17:30:00", + "BSE": "15:30:00", + "OSL": "17:30:00", + "NSI": "15:30:00", + "STO": "17:30:00", + "MCE": "17:30:00", + "MIL": "17:30:00", + "BUD": "17:00:00", + "SHH": "14:57:00", + "SET": "16:30:00", + "NYQ": "16:00:00", + "VAN": "16:00:00", + "SAO": "16:55:00", + "ISE": "17:30:00", + "AMS": "17:30:00", + "ATH": "17:00:00", + "PNK": "17:00:00", + "AQS": "16:30:00", + "CPH": "17:00:00", + "TOR": "16:00:00", + "NGM": "16:00:00", + "NCM": "16:00:00", + "NMS": "16:00:00", + "CSE": "14:30:00", + "JKT": "15:00:00", + "HEL": "17:30:00", + "EBS": "17:30:00", + "ASE": "16:00:00", + "BUE": "16:57:00", + "SGO": "16:00:00", + "CNQ": "18:00:00", + "NZE": "16:45:00", + "CCS": "13:00:00", + "JNB": "16:50:00", + "BRU": "17:30:00", + "TLV": "17:14:00", + "DOH": "13:15:00", + "MCX": "02:02:00", + "NEO": "16:00:00", + "NYS": "16:00:00", + "IST": "18:00:00", + "NAS": "16:00:00", + "LIS": "17:30:00", + "LIT": "15:55:00", + "PRA": "16:10:00", + "TAL": "15:55:00", + "ICE": "13:30:00", + "RIS": "15:55:00", + "CAI": "14:15:00", + "PCX": "16:00:00", + "SAT": "17:30:00" + }, + "lunchbreak_start": { + "SHZ": "11:30:00", + "KSC": null, + "KOE": null, + "HKG": null, + "KLS": "12:30:00", + "FRA": null, + "STU": null, + "ENX": null, + "TWO": null, + "HAM": null, + "MUN": null, + "DUS": null, + "TW": null, + "PAR": null, + "GER": null, + "IOB": null, + "LSE": null, + "MEX": null, + "HAN": null, + "SAU": null, + "JPX": "11:30:00", + "ASX": null, + "SAP": "11:30:00", + "FKA": null, + "SES": "12:00:00", + "TLO": null, + "VIE": null, + "BSE": null, + "OSL": null, + "NSI": null, + "STO": null, + "MCE": null, + "MIL": null, + "BUD": null, + "SHH": null, + "SET": "12:30:00", + "NYQ": null, + "VAN": null, + "SAO": null, + "ISE": null, + "AMS": null, + "ATH": null, + "PNK": null, + "AQS": null, + "CPH": null, + "TOR": null, + "NGM": null, + "NCM": null, + "NMS": null, + "CSE": null, + "JKT": "11:30:00", + "HEL": null, + "EBS": null, + "ASE": null, + "BUE": null, + "SGO": null, + "CNQ": null, + "NZE": null, + "CCS": null, + "JNB": null, + "BRU": null, + "TLV": null, + "DOH": null, + "MCX": null, + "NEO": null, + "NYS": null, + "IST": null, + "NAS": null, + "LIS": null, + "LIT": null, + "PRA": null, + "TAL": null, + "ICE": null, + "RIS": null, + "CAI": null, + "PCX": null, + "SAT": null + }, + "lunchbreak_end": { + "SHZ": "13:00:00", + "KSC": null, + "KOE": null, + "HKG": null, + "KLS": "14:00:00", + "FRA": null, + "STU": null, + "ENX": null, + "TWO": null, + "HAM": null, + "MUN": null, + "DUS": null, + "TW": null, + "PAR": null, + "GER": null, + "IOB": null, + "LSE": null, + "MEX": null, + "HAN": null, + "SAU": null, + "JPX": "12:30:00", + "ASX": null, + "SAP": "12:30:00", + "FKA": null, + "SES": "12:58:00", + "TLO": null, + "VIE": null, + "BSE": null, + "OSL": null, + "NSI": null, + "STO": null, + "MCE": null, + "MIL": null, + "BUD": null, + "SHH": null, + "SET": "14:00:00", + "NYQ": null, + "VAN": null, + "SAO": null, + "ISE": null, + "AMS": null, + "ATH": null, + "PNK": null, + "AQS": null, + "CPH": null, + "TOR": null, + "NGM": null, + "NCM": null, + "NMS": null, + "CSE": null, + "JKT": "13:30:00", + "HEL": null, + "EBS": null, + "ASE": null, + "BUE": null, + "SGO": null, + "CNQ": null, + "NZE": null, + "CCS": null, + "JNB": null, + "BRU": null, + "TLV": null, + "DOH": null, + "MCX": null, + "NEO": null, + "NYS": null, + "IST": null, + "NAS": null, + "LIS": null, + "LIT": null, + "PRA": null, + "TAL": null, + "ICE": null, + "RIS": null, + "CAI": null, + "PCX": null, + "SAT": null + }, + "opening_auction_start": { + "SHZ": "09:15:00", + "KSC": "08:30:00", + "KOE": "08:30:00", + "HKG": "09:00:00", + "KLS": "08:30:00", + "FRA": null, + "STU": null, + "ENX": null, + "TWO": null, + "HAM": null, + "MUN": null, + "DUS": null, + "TW": "08:30:00", + "PAR": null, + "GER": null, + "IOB": "07:50:00", + "LSE": "07:50:00", + "MEX": null, + "HAN": null, + "SAU": null, + "JPX": null, + "ASX": "07:00:00", + "SAP": null, + "FKA": null, + "SES": "08:30:00", + "TLO": "08:00:00", + "VIE": "08:00:00", + "BSE": null, + "OSL": null, + "NSI": "09:00:00", + "STO": null, + "MCE": "08:30:00", + "MIL": "08:00:00", + "BUD": "08:15:00", + "SHH": "09:15:00", + "SET": "09:30:00", + "NYQ": null, + "VAN": "07:00:00", + "SAO": "09:45:00", + "ISE": null, + "AMS": null, + "ATH": null, + "PNK": null, + "AQS": null, + "CPH": null, + "TOR": "07:00:00", + "NGM": null, + "NCM": null, + "NMS": null, + "CSE": "09:00:00", + "JKT": "08:45:00", + "HEL": null, + "EBS": null, + "ASE": null, + "BUE": null, + "SGO": null, + "CNQ": null, + "NZE": "08:30:00", + "CCS": "08:30:00", + "JNB": "08:30:00", + "BRU": null, + "TLV": null, + "DOH": "09:00:00", + "MCX": null, + "NEO": "07:00:00", + "NYS": null, + "IST": "09:40:00", + "NAS": null, + "LIS": null, + "LIT": null, + "PRA": "08:50:00", + "TAL": null, + "ICE": null, + "RIS": null, + "CAI": null, + "PCX": "04:00:00", + "SAT": null + }, + "opening_auction_end": { + "SHZ": "09:25:00", + "KSC": "09:00:00", + "KOE": "09:00:00", + "HKG": "09:30:00", + "KLS": "09:00:00", + "FRA": null, + "STU": null, + "ENX": null, + "TWO": null, + "HAM": null, + "MUN": null, + "DUS": null, + "TW": "09:00:00", + "PAR": null, + "GER": null, + "IOB": "08:00:00", + "LSE": "08:00:00", + "MEX": null, + "HAN": null, + "SAU": null, + "JPX": null, + "ASX": "10:00:00", + "SAP": null, + "FKA": null, + "SES": "09:00:00", + "TLO": "09:00:00", + "VIE": "09:00:00", + "BSE": null, + "OSL": null, + "NSI": "09:15:00", + "STO": null, + "MCE": "09:00:00", + "MIL": "09:00:00", + "BUD": "09:00:00", + "SHH": "09:25:00", + "SET": "10:00:00", + "NYQ": null, + "VAN": "09:30:00", + "SAO": "10:00:00", + "ISE": null, + "AMS": null, + "ATH": null, + "PNK": null, + "AQS": null, + "CPH": null, + "TOR": "09:30:00", + "NGM": null, + "NCM": null, + "NMS": null, + "CSE": "09:30:00", + "JKT": "09:00:00", + "HEL": null, + "EBS": null, + "ASE": null, + "BUE": null, + "SGO": null, + "CNQ": null, + "NZE": "10:00:00", + "CCS": "09:00:00", + "JNB": "09:00:00", + "BRU": null, + "TLV": null, + "DOH": "09:30:00", + "MCX": null, + "NEO": "09:30:00", + "NYS": null, + "IST": "10:00:00", + "NAS": null, + "LIS": null, + "LIT": null, + "PRA": "09:00:00", + "TAL": null, + "ICE": null, + "RIS": null, + "CAI": "14:15:00", + "PCX": "09:30:00", + "SAT": null + }, + "closing_auction_start": { + "SHZ": "14:57:00", + "KSC": "15:35:00", + "KOE": "15:35:00", + "HKG": "16:00:00", + "KLS": "16:45:00", + "FRA": null, + "STU": null, + "ENX": null, + "TWO": null, + "HAM": null, + "MUN": null, + "DUS": null, + "TW": "13:25:00", + "PAR": null, + "GER": null, + "IOB": "16:30:00", + "LSE": "16:30:00", + "MEX": null, + "HAN": null, + "SAU": null, + "JPX": null, + "ASX": "16:10:00", + "SAP": null, + "FKA": null, + "SES": "17:00:00", + "TLO": "17:30:00", + "VIE": "17:30:00", + "BSE": null, + "OSL": null, + "NSI": "15:40:00", + "STO": null, + "MCE": "17:30:00", + "MIL": "17:30:00", + "BUD": "17:00:00", + "SHH": "14:57:00", + "SET": "16:30:00", + "NYQ": null, + "VAN": "16:10:00", + "SAO": "16:55:00", + "ISE": null, + "AMS": null, + "ATH": null, + "PNK": null, + "AQS": null, + "CPH": null, + "TOR": "16:10:00", + "NGM": null, + "NCM": null, + "NMS": null, + "CSE": null, + "JKT": "15:01:00", + "HEL": null, + "EBS": null, + "ASE": null, + "BUE": null, + "SGO": null, + "CNQ": null, + "NZE": "16:45:00", + "CCS": "13:00:00", + "JNB": "16:50:00", + "BRU": null, + "TLV": null, + "DOH": null, + "MCX": null, + "NEO": "16:05:00", + "NYS": null, + "IST": "18:00:00", + "NAS": null, + "LIS": null, + "LIT": null, + "PRA": "16:10:00", + "TAL": null, + "ICE": null, + "RIS": null, + "CAI": "14:25:00", + "PCX": "16:00:00", + "SAT": null + }, + "closing_auction_end": { + "SHZ": "15:00:00", + "KSC": "15:45:00", + "KOE": "15:45:00", + "HKG": "16:08:00", + "KLS": "17:00:00", + "FRA": null, + "STU": null, + "ENX": null, + "TWO": null, + "HAM": null, + "MUN": null, + "DUS": null, + "TW": "13:30:00", + "PAR": null, + "GER": null, + "IOB": "16:35:00", + "LSE": "16:35:00", + "MEX": null, + "HAN": null, + "SAU": null, + "JPX": null, + "ASX": "16:12:00", + "SAP": null, + "FKA": null, + "SES": "17:16:00", + "TLO": "17:42:00", + "VIE": "17:50:00", + "BSE": null, + "OSL": null, + "NSI": "16:00:00", + "STO": null, + "MCE": "17:35:00", + "MIL": "17:42:00", + "BUD": "17:20:00", + "SHH": "15:00:00", + "SET": "17:00:00", + "NYQ": null, + "VAN": "17:00:00", + "SAO": "18:00:00", + "ISE": null, + "AMS": null, + "ATH": null, + "PNK": null, + "AQS": null, + "CPH": null, + "TOR": "17:00:00", + "NGM": null, + "NCM": null, + "NMS": null, + "CSE": null, + "JKT": "15:15:00", + "HEL": null, + "EBS": null, + "ASE": null, + "BUE": null, + "SGO": null, + "CNQ": null, + "NZE": "17:00:00", + "CCS": "13:30:00", + "JNB": "17:00:00", + "BRU": null, + "TLV": null, + "DOH": null, + "MCX": null, + "NEO": "17:00:00", + "NYS": null, + "IST": "18:05:00", + "NAS": null, + "LIS": null, + "LIT": null, + "PRA": "16:15:00", + "TAL": null, + "ICE": null, + "RIS": null, + "CAI": null, + "PCX": "20:00:00", + "SAT": null + }, + "timezone": { + "SHZ": "Asia\/Shanghai", + "KSC": "Asia\/Seoul", + "KOE": "Asia\/Seoul", + "HKG": "Asia\/Hong_Kong", + "KLS": "Asia\/Kuala_Lumpur", + "FRA": "Europe\/Berlin", + "STU": "Europe\/Berlin", + "ENX": "Europe\/Amsterdam", + "TWO": "Asia\/Taipei", + "HAM": "Europe\/Berlin", + "MUN": "Europe\/Berlin", + "DUS": "Europe\/Berlin", + "TW": "Asia\/Taipei", + "PAR": "Europe\/Paris", + "GER": "Europe\/Berlin", + "IOB": "Europe\/London", + "LSE": "Europe\/London", + "MEX": "Mexico\/General", + "HAN": "Europe\/Berlin", + "SAU": "Asia\/Riyadh", + "JPX": "Asia\/Tokyo", + "ASX": "Australia\/Sydney", + "SAP": "Asia\/Tokyo", + "FKA": "Europe\/Berlin", + "SES": "Asia\/Singapore", + "TLO": "Europe\/Rome", + "VIE": "Europe\/Vienna", + "BSE": "Asia\/Kolkata", + "OSL": "Europe\/Oslo", + "NSI": "Asia\/Kolkata", + "STO": "Europe\/Stockholm", + "MCE": "Europe\/Madrid", + "MIL": "Europe\/Rome", + "BUD": "Europe\/Budapest", + "SHH": "Asia\/Shanghai", + "SET": "Asia\/Bangkok", + "NYQ": "US\/Eastern", + "VAN": "Canada\/Eastern", + "SAO": "Brazil\/East", + "ISE": "Europe\/Dublin", + "AMS": "Europe\/Amsterdam", + "ATH": "Europe\/Athens", + "PNK": "US\/Eastern", + "AQS": "Europe\/London", + "CPH": "Europe\/Copenhagen", + "TOR": "Canada\/Eastern", + "NGM": "US\/Eastern", + "NCM": "US\/Eastern", + "NMS": "US\/Eastern", + "CSE": "Asia\/Colombo", + "JKT": "Asia\/Jakarta", + "HEL": "Europe\/Helsinki", + "EBS": "Europe\/Zurich", + "ASE": "US\/Eastern", + "BUE": "America\/Argentina\/Buenos_Aires", + "SGO": "Chile\/Continental", + "CNQ": "Canada\/Eastern", + "NZE": "Pacific\/Auckland", + "CCS": "America\/Caracas", + "JNB": "Africa\/Johannesburg", + "BRU": "Europe\/Brussels", + "TLV": "Asia\/Tel_Aviv", + "DOH": "Asia\/Qatar", + "MCX": "Europe\/Moscow", + "NEO": "Canada\/Eastern", + "NYS": "US\/Eastern", + "IST": "Europe\/Istanbul", + "NAS": "US\/Eastern", + "LIS": "Europe\/Lisbon", + "LIT": "Europe\/Vilnius", + "PRA": "Europe\/Prague", + "TAL": "Europe\/Tallinn", + "ICE": "Iceland", + "RIS": "Europe\/Riga", + "CAI": "Egypt", + "PCX": "US\/Eastern", + "SAT": "Europe\/Stockholm" + }, + "flag": { + "SHZ": "\ud83c\udde8\ud83c\uddf3", + "KSC": "\ud83c\uddf0\ud83c\uddf7", + "KOE": "\ud83c\uddf0\ud83c\uddf7", + "HKG": "\ud83c\udded\ud83c\uddf0", + "KLS": "\ud83c\uddf2\ud83c\uddfe", + "FRA": "\ud83c\udde9\ud83c\uddea", + "STU": "\ud83c\udde9\ud83c\uddea", + "ENX": "\ud83c\uddf3\ud83c\uddf1", + "TWO": "\ud83c\uddf9\ud83c\uddfc", + "HAM": "\ud83c\udde9\ud83c\uddea", + "MUN": "\ud83c\udde9\ud83c\uddea", + "DUS": "\ud83c\udde9\ud83c\uddea", + "TW": "\ud83c\uddf9\ud83c\uddfc", + "PAR": "\ud83c\uddeb\ud83c\uddf7", + "GER": "\ud83c\udde9\ud83c\uddea", + "IOB": "\ud83c\uddec\ud83c\udde7", + "LSE": "\ud83c\uddec\ud83c\udde7", + "MEX": "\ud83c\uddf2\ud83c\uddfd", + "HAN": "\ud83c\udde9\ud83c\uddea", + "SAU": "\ud83c\uddf8\ud83c\udde6", + "JPX": "\ud83c\uddef\ud83c\uddf5", + "ASX": "\ud83c\udde6\ud83c\uddfa", + "SAP": "\ud83c\uddef\ud83c\uddf5", + "FKA": "\ud83c\udde9\ud83c\uddea", + "SES": "\ud83c\uddf8\ud83c\uddec", + "TLO": "\ud83c\uddee\ud83c\uddf9", + "VIE": "\ud83c\udde6\ud83c\uddf9", + "BSE": "\ud83c\uddee\ud83c\uddf3", + "OSL": "\ud83c\uddf3\ud83c\uddf4", + "NSI": "\ud83c\uddee\ud83c\uddf3", + "STO": "\ud83c\uddf8\ud83c\uddea", + "MCE": "\ud83c\uddea\ud83c\uddf8", + "MIL": "\ud83c\uddee\ud83c\uddf9", + "BUD": "\ud83c\udded\ud83c\uddfa", + "SHH": "\ud83c\udde8\ud83c\uddf3", + "SET": "\ud83c\uddf9\ud83c\udded", + "NYQ": "\ud83c\uddfa\ud83c\uddf8", + "VAN": "\ud83c\udde8\ud83c\udde6", + "SAO": "\ud83c\udde7\ud83c\uddf7", + "ISE": "\ud83c\uddee\ud83c\uddea", + "AMS": "\ud83c\uddf3\ud83c\uddf1", + "ATH": "\ud83c\uddec\ud83c\uddf7", + "PNK": "\ud83c\uddfa\ud83c\uddf8", + "AQS": "\ud83c\uddec\ud83c\udde7", + "CPH": "\ud83c\udde9\ud83c\uddf0", + "TOR": "\ud83c\udde8\ud83c\udde6", + "NGM": "\ud83c\uddfa\ud83c\uddf8", + "NCM": "\ud83c\uddfa\ud83c\uddf8", + "NMS": "\ud83c\uddfa\ud83c\uddf8", + "CSE": "\ud83c\uddf1\ud83c\uddf0", + "JKT": "\ud83c\uddee\ud83c\udde9", + "HEL": "\ud83c\uddeb\ud83c\uddee", + "EBS": "\ud83c\udde8\ud83c\udded", + "ASE": "\ud83c\uddfa\ud83c\uddf8", + "BUE": "\ud83c\udde6\ud83c\uddf7", + "SGO": "\ud83c\udde8\ud83c\uddf1", + "CNQ": "\ud83c\udde8\ud83c\udde6", + "NZE": "\ud83c\uddf3\ud83c\uddff", + "CCS": "\ud83c\uddfb\ud83c\uddea", + "JNB": "\ud83c\uddff\ud83c\udde6", + "BRU": "\ud83c\udde7\ud83c\uddea", + "TLV": "\ud83c\uddee\ud83c\uddf1", + "DOH": "\ud83c\uddf6\ud83c\udde6", + "MCX": "\ud83c\uddf7\ud83c\uddfa", + "NEO": "\ud83c\udde8\ud83c\udde6", + "NYS": "\ud83c\uddfa\ud83c\uddf8", + "IST": "\ud83c\uddf9\ud83c\uddf7", + "NAS": "\ud83c\uddfa\ud83c\uddf8", + "LIS": "\ud83c\uddf5\ud83c\uddf9", + "LIT": "\ud83c\uddf1\ud83c\uddf9", + "PRA": "\ud83c\udde8\ud83c\uddff", + "TAL": "\ud83c\uddea\ud83c\uddea", + "ICE": "\ud83c\uddee\ud83c\uddf8", + "RIS": "\ud83c\uddf1\ud83c\uddfb", + "CAI": "\ud83c\uddea\ud83c\uddec", + "PCX": "\ud83c\uddfa\ud83c\uddf8", + "SAT": "\ud83c\uddf8\ud83c\uddea" + } +} \ No newline at end of file diff --git a/openbb_terminal/stocks/tradinghours/tradinghours_controller.py b/openbb_terminal/stocks/tradinghours/tradinghours_controller.py new file mode 100644 index 00000000000..f084ddff5d9 --- /dev/null +++ b/openbb_terminal/stocks/tradinghours/tradinghours_controller.py @@ -0,0 +1,225 @@ +"""Trading Hours Controller.""" +__docformat__ = "numpy" + +import argparse +import logging +import os + +from typing import List + +import pandas as pd +from prompt_toolkit.completion import NestedCompleter + +from openbb_terminal import feature_flags as obbff +from openbb_terminal.decorators import log_start_end +from openbb_terminal.menu import session +from openbb_terminal.helper_funcs import ( + get_user_timezone_or_invalid, + parse_known_args_and_warn, +) +from openbb_terminal.rich_config import console +from openbb_terminal.parent_classes import BaseController +from openbb_terminal.stocks.tradinghours import bursa_view +from openbb_terminal.stocks.tradinghours.bursa_model import get_open +from openbb_terminal.stocks.tradinghours.tradinghours_helper import ( + get_exchanges_short_names, + get_fd_equities_list, +) + + +logger = logging.getLogger(__name__) + + +class TradingHoursController(BaseController): + + """Trading Hours Controller class.""" + + CHOICES_COMMANDS = ["symbol", "open", "closed", "all", "exchange"] + PATH = "/stocks/th/" + FILE_PATH = os.path.join(os.path.dirname(__file__), "README.md") + + def __init__(self, queue: List[str] = None): + """Construct Data.""" + super().__init__(queue) + + self.equities = get_fd_equities_list() + self.equity_tickers = sorted(list(self.equities.keys())) + short_names_df = get_exchanges_short_names() + short_names_index = short_names_df.index.values + short_names = short_names_df["short_name"].values + all_short_names = list(short_names) + list(short_names_index) + self.all_exchange_short_names = sorted(list(all_short_names)) + + self.exchange = None + self.symbol = None + self.symbol_name = None + self.symbol_market_open = False + self.source = "yf" + self.data = pd.DataFrame() + self.timezone = get_user_timezone_or_invalid() + + if session and obbff.USE_PROMPT_TOOLKIT: + choices: dict = {c: {} for c in self.controller_choices} + choices["exchange"] = {c: None for c in self.all_exchange_short_names} + choices["exchange"]["-n"] = {c: None for c in self.all_exchange_short_names} + choices["exchange"]["--name"] = { + c: None for c in self.all_exchange_short_names + } + self.completer = NestedCompleter.from_nested_dict(choices) + + def print_help(self): + if self.symbol is not None: + if self.symbol_market_open: + exchange_opened = "OPENED" + else: + exchange_opened = "CLOSED" + else: + exchange_opened = "" + + help_text = f"""[cmds] + symbol select the symbol[/cmds] + +[param]Symbol name:[/param] {self.symbol_name or ""} +[param]Symbol:[/param] {self.symbol or ""} +[param]Exchange open:[/param] {exchange_opened} +[cmds] + open show open markets + closed show closed markets + all show all markets + exchange show one exchange[/cmds] +""" + console.print(text=help_text, menu="Trading Hours") + + @log_start_end(log=logger) + def call_symbol(self, other_args: List[str]): + """Process 'symbol' command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="symbol", + description="Select the symbol you wish to trade", + ) + parser.add_argument( + "-n", + "--name", + help="Symbol", + type=str.upper, + dest="symbol", + ) + + if ( + other_args + and "-n" not in other_args[0] + and "--name" not in other_args[0] + and "-h" not in other_args + ): + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if ns_parser: + self.symbol = ns_parser.symbol + self.symbol_name = self.equities[self.symbol]["short_name"] # + self.exchange = self.equities[self.symbol]["exchange"] + open_ex = get_open() + if self.exchange in open_ex.index: + self.symbol_market_open = True + else: + self.symbol_market_open = False + # add currency + console.print( + f"\nSelected symbol\nSymbol: {self.symbol}\n" + f"Name: {self.symbol_name}\n" + f"Market open: {self.symbol_market_open}\n" + ) + + @log_start_end(log=logger) + def call_exchange(self, other_args: List[str]): + """Process 'exchange' command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="exchange", + description="Select the exchange you want see open hours for", + ) + parser.add_argument( + "-n", + "--name", + help="Exchange short name", + type=str.upper, + dest="exchange", + ) + + if ( + other_args + and "-n" not in other_args[0] + and "--name" not in other_args[0] + and "-h" not in other_args + ): + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if ns_parser and ns_parser.exchange: + bursa_view.display_exchange(ns_parser.exchange) + else: + logger.error("Select the exchange you want to know about.") + console.print("[red]Select the exchange you want to know about.[/red]\n") + + @log_start_end(log=logger) + def call_open(self, other_args: List[str]): + """Process 'symbol' command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="exchange", + description="Show currently open exchanges", + ) + + if other_args and "-h" not in other_args: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if ns_parser: + bursa_view.display_open() + else: + logger.error("No open exchanges right now.") + console.print("[red]No open exchanges right now.[/red]\n") + + @log_start_end(log=logger) + def call_closed(self, other_args: List[str]): + """Process 'symbol' command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="exchange", + description="Show currently closed exchanges", + ) + + if other_args and "-h" not in other_args: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if ns_parser: + bursa_view.display_closed() + else: + logger.error("No closed exchanges right now.") + console.print("[red]No closed exchanges right now.[/red]\n") + + @log_start_end(log=logger) + def call_all(self, other_args: List[str]): + """Process 'symbol' command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="exchange", + description="Show all exchanges", + ) + + if other_args and "-h" not in other_args: + other_args.insert(0, "-n") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if ns_parser: + bursa_view.display_all() + else: + logger.error("No exchanges right now.") + console.print("[red]No exchanges right now.[/red]\n") diff --git a/openbb_terminal/stocks/tradinghours/tradinghours_helper.py b/openbb_terminal/stocks/tradinghours/tradinghours_helper.py new file mode 100644 index 00000000000..82ba65276a3 --- /dev/null +++ b/openbb_terminal/stocks/tradinghours/tradinghours_helper.py @@ -0,0 +1,25 @@ +from typing import List +import logging + +import financedatabase as fd + +from openbb_terminal.decorators import log_start_end +from openbb_terminal.stocks.tradinghours.bursa_model import get_all_exchange_short_names + +logger = logging.getLogger(__name__) + + +@log_start_end(log=logger) +def get_fd_equities_list() -> List: + """Load FD list of equity symbols.""" + equities = fd.select_equities(exclude_exchanges=False) + + return equities + + +@log_start_end(log=logger) +def get_exchanges_short_names() -> List: + """Load FD list of equity symbols.""" + shorts = get_all_exchange_short_names() + + return shorts diff --git a/terminal.py b/terminal.py index 7fa2ee2a7e2..399dca08b55 100644 --- a/terminal.py +++ b/terminal.py @@ -271,6 +271,14 @@ class TerminalController(BaseController): if tz_ns_parser.timezone: replace_user_timezone(tz_ns_parser.timezone.replace("-", "/")) + def call_tradinghours(self, _): + """Process tradinghours command""" + from openbb_terminal.tradinghours.tradinghours_controller import ( + TradingHoursController, + ) + + self.queue = self.load_class(TradingHoursController, self.queue) + def call_export(self, other_args: List[str]): """Process export command""" if other_args or self.queue: diff --git a/tests/openbb_terminal/stocks/txt/test_stocks_controller/test_print_help.txt b/tests/openbb_terminal/stocks/txt/test_stocks_controller/test_print_help.txt index 9f18af200a5..3c8d23cac91 100644 --- a/tests/openbb_terminal/stocks/txt/test_stocks_controller/test_print_help.txt +++ b/tests/openbb_terminal/stocks/txt/test_stocks_controller/test_print_help.txt @@ -17,6 +17,7 @@ Company: codes FIGI, SIK and SIC codes codes [Polygon.io] +> th trading hours, check open markets > options options menu, e.g.: chains, open interest, greeks, parity > disc discover trending stocks, e.g.: map, sectors, high short interest > sia sector and industry analysis, e.g.: companies per sector, quick ratio per industry and country diff --git a/website/content/terminal/stocks/_index.md b/website/content/terminal/stocks/_index.md deleted file mode 100755 index 093e02e215d..00000000000 --- a/website/content/terminal/stocks/_index.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -geekdocCollapseSection: true ---- - -{{< toc-tree >}} diff --git a/website/content/terminal/stocks/tradinghours/_index.md b/website/content/terminal/stocks/tradinghours/_index.md new file mode 100755 index 00000000000..8cbe312d214 --- /dev/null +++ b/website/content/terminal/stocks/tradinghours/_index.md @@ -0,0 +1 @@ +Check where the stock market is currently open. Never miss a chance for your next trade. \ No newline at end of file diff --git a/website/content/terminal/stocks/tradinghours/all/_index.md b/website/content/terminal/stocks/tradinghours/all/_index.md new file mode 100644 index 00000000000..d58172ad576 --- /dev/null +++ b/website/content/terminal/stocks/tradinghours/all/_index.md @@ -0,0 +1,3 @@ +usage: `all` + +All markets \ No newline at end of file diff --git a/website/content/terminal/stocks/tradinghours/closed/_index.md b/website/content/terminal/stocks/tradinghours/closed/_index.md new file mode 100644 index 00000000000..e9261f8b291 --- /dev/null +++ b/website/content/terminal/stocks/tradinghours/closed/_index.md @@ -0,0 +1,3 @@ +usage: `closed` + +Closed markets \ No newline at end of file diff --git a/website/content/terminal/stocks/tradinghours/exchange/_index.md b/website/content/terminal/stocks/tradinghours/exchange/_index.md new file mode 100644 index 00000000000..c62960bded7 --- /dev/null +++ b/website/content/terminal/stocks/tradinghours/exchange/_index.md @@ -0,0 +1,3 @@ +usage: `exchange IOB` + +Exchange info \ No newline at end of file diff --git a/website/content/terminal/stocks/tradinghours/open/_index.md b/website/content/terminal/stocks/tradinghours/open/_index.md new file mode 100644 index 00000000000..e1f83cc9a17 --- /dev/null +++ b/website/content/terminal/stocks/tradinghours/open/_index.md @@ -0,0 +1,3 @@ +usage: `open` + +Open markets \ No newline at end of file diff --git a/website/content/terminal/stocks/tradinghours/symbol/_index.md b/website/content/terminal/stocks/tradinghours/symbol/_index.md new file mode 100644 index 00000000000..c24b61c0e56 --- /dev/null +++ b/website/content/terminal/stocks/tradinghours/symbol/_index.md @@ -0,0 +1,3 @@ +usage: `symbol [-n --name SYMBOL]` + +Load symbol for open market checkup. \ No newline at end of file diff --git a/website/data/menu/main.yml b/website/data/menu/main.yml index 372aa6634b5..f92c0a3771d 100755 --- a/website/data/menu/main.yml +++ b/website/data/menu/main.yml @@ -466,6 +466,19 @@ main: ref: "/terminal/stocks/options/pricing/show" - name: rnval ref: "/terminal/stocks/options/pricing/rnval" + - name: "Trading Hours" + ref: "/tradinghours" + sub: + - name: "Symbol" + ref: "/terminal/stocks/tradinghours/symbol" + - name: "Open" + ref: "/terminal/stocks/tradinghours/open" + - name: "Closed"stocks/ + ref: "/terminal/stocks/tradinghours/closed" + - name: "All"stocks/ + ref: "/terminal/stocks/tradinghours/all" + - name: "Exchange"stocks/ + ref: "/terminal/stocks/tradinghours/exchange" - name: technical analysis ref: "/terminal/common/technical_analysis" sub: @@ -1607,3 +1620,4 @@ main: ref: "/bots/slack" - name: "GroupMe" ref: "/bots/groupme" + \ No newline at end of file