Trading hours stock feature (#1697)

This commit is contained in:
Szymon Błaszczyński
2022-05-11 20:51:03 +02:00
committed by GitHub
parent adec66fe93
commit 33a041e5bf
19 changed files with 1718 additions and 5 deletions

View File

@@ -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"""

View File

@@ -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)

View File

@@ -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

View File

@@ -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("")

File diff suppressed because it is too large Load Diff

View File

@@ -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")

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -1,5 +0,0 @@
---
geekdocCollapseSection: true
---
{{< toc-tree >}}

View File

@@ -0,0 +1 @@
Check where the stock market is currently open. Never miss a chance for your next trade.

View File

@@ -0,0 +1,3 @@
usage: `all`
All markets

View File

@@ -0,0 +1,3 @@
usage: `closed`
Closed markets

View File

@@ -0,0 +1,3 @@
usage: `exchange IOB`
Exchange info

View File

@@ -0,0 +1,3 @@
usage: `open`
Open markets

View File

@@ -0,0 +1,3 @@
usage: `symbol [-n --name SYMBOL]`
Load symbol for open market checkup.

View File

@@ -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"