diff --git a/openbb_terminal/miscellaneous/data_sources_default.json b/openbb_terminal/miscellaneous/data_sources_default.json index 03b940412d1..ce503e9048b 100644 --- a/openbb_terminal/miscellaneous/data_sources_default.json +++ b/openbb_terminal/miscellaneous/data_sources_default.json @@ -12,7 +12,8 @@ "open": ["Bursa"], "closed": ["Bursa"], "all": ["Bursa"], - "exchange": ["Bursa"] + "exchange": ["Bursa"], + "holidays":["PandasMarketCalendars"] }, "options": { "unu": ["FDScanner"], diff --git a/openbb_terminal/miscellaneous/i18n/en.yml b/openbb_terminal/miscellaneous/i18n/en.yml index 9e3d873f8c4..a1003975072 100644 --- a/openbb_terminal/miscellaneous/i18n/en.yml +++ b/openbb_terminal/miscellaneous/i18n/en.yml @@ -164,6 +164,7 @@ en: stocks/th/closed: show closed markets stocks/th/all: show all markets stocks/th/exchange: show one exchange + stocks/th/holidays: show all dates on which there is a market holiday stocks/disc/pipo: past IPOs dates stocks/disc/fipo: future IPOs dates stocks/disc/gainers: show latest top gainers diff --git a/openbb_terminal/stocks/tradinghours/pandas_market_cal_model.py b/openbb_terminal/stocks/tradinghours/pandas_market_cal_model.py new file mode 100644 index 00000000000..981e145ccf8 --- /dev/null +++ b/openbb_terminal/stocks/tradinghours/pandas_market_cal_model.py @@ -0,0 +1,58 @@ +import logging +import pandas_market_calendars as mcal + +import pandas as pd +from openbb_terminal.decorators import log_start_end + +logger = logging.getLogger(__name__) + + +@log_start_end(log=logger) +def get_all_holiday_exchange_short_names() -> pd.DataFrame: + """Get all holiday exchanges short names. + + Returns + ------- + pd.DataFrame + All available exchanges with holiday data (short names) + """ + exchange_short_names = mcal.calendar_registry.get_calendar_names() + + df = pd.DataFrame(exchange_short_names, columns=["short_name"]) + + return df + + +@log_start_end(log=logger) +def get_exchange_holidays(exchange_symbol: str, year: int) -> pd.DataFrame: + + """Get all short name of each exchange that we hold holiday calendar for. + + Parameters + ---------- + symbol : str + Exchange symbol + year : int + Calendar year + + Returns + ------- + pd.DataFrame + All available exchanges with holiday data (short names) + """ + + cal = mcal.get_calendar(exchange_symbol) + holidays = pd.DataFrame(cal.holidays().holidays) + + start_date = str(year) + "-01-01" + end_date = str(year) + "-12-31" + holidays.columns = ["Holiday Date"] + + mask = (holidays["Holiday Date"] >= start_date) & ( + holidays["Holiday Date"] <= end_date + ) + exchange_holidays = holidays.loc[mask].copy() + + exchange_holidays["Holiday Date"] = exchange_holidays["Holiday Date"].astype(str) + + return exchange_holidays diff --git a/openbb_terminal/stocks/tradinghours/pandas_market_cal_view.py b/openbb_terminal/stocks/tradinghours/pandas_market_cal_view.py new file mode 100644 index 00000000000..9ad362a4403 --- /dev/null +++ b/openbb_terminal/stocks/tradinghours/pandas_market_cal_view.py @@ -0,0 +1,55 @@ +import logging +import pandas as pd + +from openbb_terminal.decorators import log_start_end +from openbb_terminal.stocks.tradinghours import pandas_market_cal_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 get_all_holiday_exchange_short_names() -> pd.DataFrame: + """Get all holiday exchanges short names. + + Parameters + ---------- + + Returns + ------- + pd.DataFrame + All available exchange with holiday data short names + """ + + return pandas_market_cal_model.get_all_holiday_exchange_short_names() + + +@log_start_end(log=logger) +def display_exchange_holidays(exchange_symbol: str, year: int): + """Display current exchange holiday calendar. + + Parameters + ---------- + symbol : str + Exchange symbol + year : int + Calendar year + """ + + exchange = pandas_market_cal_model.get_exchange_holidays(exchange_symbol, year) + + if len(exchange) == 0 or exchange.empty: + console.print( + "[red]" + + "No exchange data loaded.\n" + + "Make sure you picked a valid exchange symbol and year." + + "[/red]\n" + ) + return + + print_rich_table( + exchange, + show_index=False, + title=f"[bold]{exchange_symbol}[/bold]", + ) diff --git a/openbb_terminal/stocks/tradinghours/tradinghours_controller.py b/openbb_terminal/stocks/tradinghours/tradinghours_controller.py index a7ccc2a2328..973901d922b 100644 --- a/openbb_terminal/stocks/tradinghours/tradinghours_controller.py +++ b/openbb_terminal/stocks/tradinghours/tradinghours_controller.py @@ -6,7 +6,7 @@ import logging import os from typing import List - +from datetime import date import pandas as pd from openbb_terminal.custom_prompt_toolkit import NestedCompleter @@ -25,16 +25,21 @@ from openbb_terminal.stocks.tradinghours.tradinghours_helper import ( get_exchanges_short_names, get_fd_equities_list, ) - +from openbb_terminal.stocks.tradinghours.pandas_market_cal_view import ( + get_all_holiday_exchange_short_names, + display_exchange_holidays, +) logger = logging.getLogger(__name__) +# pylint: disable=R0902 + class TradingHoursController(BaseController): """Trading Hours Controller class.""" - CHOICES_COMMANDS = ["symbol", "open", "closed", "all", "exchange"] + CHOICES_COMMANDS = ["symbol", "open", "closed", "all", "exchange", "holidays"] PATH = "/stocks/th/" FILE_PATH = os.path.join(os.path.dirname(__file__), "README.md") CHOICES_GENERATION = True @@ -50,6 +55,9 @@ class TradingHoursController(BaseController): 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.all_holiday_exchange_short_names = sorted( + list(get_all_holiday_exchange_short_names()["short_name"].values) + ) self.symbol = None self.symbol_name = None @@ -75,7 +83,6 @@ class TradingHoursController(BaseController): if session and obbff.USE_PROMPT_TOOLKIT: choices: dict = self.choices_default - self.completer = NestedCompleter.from_nested_dict(choices) def print_help(self): @@ -92,6 +99,7 @@ class TradingHoursController(BaseController): mt.add_cmd("closed") mt.add_cmd("all") mt.add_cmd("exchange") + mt.add_cmd("holidays") mt.add_raw("\n") mt.add_cmd("symbol") mt.add_raw("\n") @@ -231,3 +239,56 @@ class TradingHoursController(BaseController): ns_parser = self.parse_known_args_and_warn(parser, other_args) if ns_parser: bursa_view.display_all() + + @log_start_end(log=logger) + def call_holidays(self, other_args: List[str]): + """Process 'holidays' command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="exchange", + description="Select the exchange and year you want see holidays for", + ) + parser.add_argument( + "-n", + "--name", + help="Exchange short name", + dest="exchange", + required=True, + type=str.upper, + choices=self.all_holiday_exchange_short_names, + metavar="LSE", + ) + + parser.add_argument( + "-y", + "--year", + help="Year", + nargs="?", + type=int, + const=date.today().year, + dest="year", + metavar="year", + ) + + 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") + other_args.insert(2, "-y") + + ns_parser = self.parse_known_args_and_warn(parser, other_args) + if ns_parser: + if ns_parser.exchange: + if ns_parser.year: + display_exchange_holidays(ns_parser.exchange, ns_parser.year) + else: + display_exchange_holidays(ns_parser.exchange, date.today().year) + else: + logger.error("Select the exchange and year you want holiday calendar for.") + console.print( + "[red]Select the exchange and year you want holiday calendar for.[/red]\n" + ) diff --git a/website/content/terminal/stocks/th/holidays/_index.md b/website/content/terminal/stocks/th/holidays/_index.md new file mode 100644 index 00000000000..70b8cdef549 --- /dev/null +++ b/website/content/terminal/stocks/th/holidays/_index.md @@ -0,0 +1,3 @@ +usage: `holidays LSE 2022` + +Exchange holiday info \ No newline at end of file diff --git a/website/old_content/terminal/stocks/th/_index.md b/website/old_content/terminal/stocks/th/_index.md index 8651677f0b1..cf1900b31a7 100644 --- a/website/old_content/terminal/stocks/th/_index.md +++ b/website/old_content/terminal/stocks/th/_index.md @@ -20,13 +20,14 @@ Choose from: closed, all or exchange. +holidays. Below, examples are given what these commands display, note that the tables are purposely cut off to keep the introduction compact. ```` (๐Ÿฆ‹) /stocks/th/ $ open - Open markets + Open markets โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ โ”ƒ name โ”ƒ short_name โ”ƒ โ”กโ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ @@ -49,7 +50,7 @@ introduction compact. (๐Ÿฆ‹) /stocks/th/ $ closed - Closed markets + Closed markets โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ โ”ƒ name โ”ƒ short_name โ”ƒ โ”กโ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ @@ -74,7 +75,7 @@ introduction compact. (๐Ÿฆ‹) /stocks/th/ $ all - World markets + World markets โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ โ”ƒ name โ”ƒ short_name โ”ƒ open โ”ƒ โ”กโ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”ฉ @@ -120,10 +121,10 @@ Market open: False Selected symbol Symbol: FB2.L -Name: LEVERAGE SHARES PUBLIC LIMITED +Name: LEVERAGE SHARES PUBLIC LIMITED Market open: False -(๐Ÿฆ‹) /stocks/th/ $ exchange BO +(๐Ÿฆ‹) /stocks/th/ $ exchange BO โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ โ”ƒ BSE โ”ƒ โ”กโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ @@ -151,8 +152,35 @@ Market open: False โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ timezone โ”‚ Asia/Kolkata โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ flag โ”‚ ๐Ÿ‡ฎ๐Ÿ‡ณ +โ”‚ flag โ”‚ ๐Ÿ‡ฎ๐Ÿ‡ณ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ open โ”‚ False โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + + +(๐Ÿฆ‹) /stocks/th/ $ holidays BATS 2024 + BATS +โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ +โ”ƒ โ”ƒ Holiday Date โ”ƒ +โ”กโ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ +โ”‚ 1805 โ”‚ 2024-01-01 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1806 โ”‚ 2024-01-15 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1807 โ”‚ 2024-02-19 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1808 โ”‚ 2024-03-29 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1809 โ”‚ 2024-05-27 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1810 โ”‚ 2024-07-04 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1811 โ”‚ 2024-09-02 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1812 โ”‚ 2024-11-28 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1813 โ”‚ 2024-12-25 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + ````