diff --git a/data_sources_default.json b/data_sources_default.json index d8beebc491c..3fc8b6aab2d 100644 --- a/data_sources_default.json +++ b/data_sources_default.json @@ -2,6 +2,7 @@ "stocks": { "search": ["FinanceDatabase"], "quote": ["YahooFinance"], + "tob": ["CBOE"], "candle": [], "codes": ["Polygon"], "news": ["Feedparser", "NewsApi"], diff --git a/openbb_terminal/api.py b/openbb_terminal/api.py index 6c01ddcde57..71bb3d82400 100644 --- a/openbb_terminal/api.py +++ b/openbb_terminal/api.py @@ -1889,6 +1889,7 @@ functions = { }, "stocks.search": {"model": "openbb_terminal.stocks.stocks_helper.search"}, "stocks.quote": {"model": "openbb_terminal.stocks.stocks_helper.quote"}, + "stocks.tob": {"model": "openbb_terminal.stocks.cboe_model.get_top_of_book"}, "stocks.candle": {"model": "openbb_terminal.stocks.stocks_helper.display_candle"}, "crypto.load": { "model": "openbb_terminal.cryptocurrency.cryptocurrency_helpers.load" diff --git a/openbb_terminal/miscellaneous/i18n/en.yml b/openbb_terminal/miscellaneous/i18n/en.yml index 8702e740bc6..70c548d312f 100644 --- a/openbb_terminal/miscellaneous/i18n/en.yml +++ b/openbb_terminal/miscellaneous/i18n/en.yml @@ -94,6 +94,7 @@ en: stocks/load: load a specific stock ticker and additional info for analysis stocks/_ticker: Stock stocks/quote: view the current price for a specific stock ticker + stocks/tob: view top of book for loaded ticker (US exchanges only) stocks/QUOTE: Current quote for stock ticker stocks/QUOTE_ticker: Stock ticker stocks/candle: view a candle chart for a specific stock ticker diff --git a/openbb_terminal/stocks/cboe_model.py b/openbb_terminal/stocks/cboe_model.py new file mode 100644 index 00000000000..6c86a5ee9c6 --- /dev/null +++ b/openbb_terminal/stocks/cboe_model.py @@ -0,0 +1,59 @@ +"""CBOE Model Functions""" +__docformat__ = "numpy" + +from typing import Tuple + +import pandas as pd +import requests + +from openbb_terminal.rich_config import console + + +def get_top_of_book( + symbol: str, exchange: str = "BZX" +) -> Tuple[pd.DataFrame, pd.DataFrame]: + """Get top of book bid and ask for ticker on exchange [CBOE.com] + + Parameters + ---------- + symbol: str + Ticker to get + exchange: str + Exchange to look at. Can be `BZX`,`EDGX`, `BYX`, `EDGA` + + Returns + ------- + pd.DatatFrame + Dataframe of Bids + pd.DataFrame + Dataframe of asks + + """ + if exchange not in ["BZX", "EDGX", "BYX", "EDGA"]: + console.print(f"[red]Exchange not valid: {exchange}[/red]") + return pd.DataFrame(), pd.DataFrame() + # exchange need to be lower case. Not sure why + url = f"https://www.cboe.com/json/{exchange.lower()}/book/{symbol}" + + r = requests.get( + url, + headers={ + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)" + " Chrome/106.0.0.0 Safari/537.36", + "referer": "https://www.cboe.com/us/equities/market_statistics/book_viewer/", + }, + ) + if r.status_code != 200: + console.print(f"[red]Request failed with code {r.status_code}[/red]") + return pd.DataFrame(), pd.DataFrame() + r_json = r.json() + if r_json["data"]["company"] == "unknown symbol": + console.print(f"[red]Unknown symbol: {symbol}[/red]") + return pd.DataFrame(), pd.DataFrame() + bids = pd.DataFrame(r_json["data"]["bids"], columns=["Size", "Price"]) + asks = pd.DataFrame(r_json["data"]["asks"], columns=["Size", "Price"]) + if bids.empty or asks.empty: + console.print( + "[red]No bid/ask data. Note this is real time so there is no data after market close.[/red]" + ) + return bids, asks diff --git a/openbb_terminal/stocks/cboe_view.py b/openbb_terminal/stocks/cboe_view.py new file mode 100644 index 00000000000..c7c4008ddd2 --- /dev/null +++ b/openbb_terminal/stocks/cboe_view.py @@ -0,0 +1,34 @@ +"""CBOE View""" +__docformat__ = "numpy" + +import pandas as pd +from openbb_terminal.stocks import cboe_model +from openbb_terminal.helper_funcs import print_rich_table +from openbb_terminal.rich_config import console + + +def display_top_of_book(symbol: str, exchange: str = "BZX"): + """Display top of book data [CBOE] + + Parameters + ---------- + symbol: str + Symbol to get data for + exchange: str + Exchange to get data for + """ + bids, asks = cboe_model.get_top_of_book(symbol, exchange) + + if not bids.empty and not asks.empty: + asks = asks[["Price", "Size"]] + bids.columns = ["Bid " + col for col in bids.columns] + asks.columns = ["Ask " + col for col in asks.columns] + merged = pd.concat([bids, asks], axis=1) + print_rich_table( + merged, + title=f"{symbol} Top of Book", + show_index=False, + headers=merged.columns, + ) + + console.print() diff --git a/openbb_terminal/stocks/stocks_controller.py b/openbb_terminal/stocks/stocks_controller.py index fb7295c934e..8025ca0de98 100644 --- a/openbb_terminal/stocks/stocks_controller.py +++ b/openbb_terminal/stocks/stocks_controller.py @@ -16,6 +16,7 @@ from openbb_terminal import feature_flags as obbff from openbb_terminal.common import feedparser_view, newsapi_view from openbb_terminal.common.quantitative_analysis import qa_view from openbb_terminal.decorators import log_start_end +from openbb_terminal.stocks import cboe_view from openbb_terminal.helper_funcs import ( EXPORT_ONLY_RAW_DATA_ALLOWED, @@ -46,6 +47,7 @@ class StocksController(StockBaseController): "search", "load", "quote", + "tob", "candle", "news", "resources", @@ -77,6 +79,7 @@ class StocksController(StockBaseController): country = financedatabase.show_options("equities", "countries") sector = financedatabase.show_options("equities", "sectors") industry = financedatabase.show_options("equities", "industries") + TOB_EXCHANGES = ["BZX", "EDGX", "BYX", "EDGA"] def __init__(self, queue: List[str] = None): """Constructor""" @@ -114,6 +117,10 @@ class StocksController(StockBaseController): "--ticker": None, "-t": "--ticker", } + choices["tob"] = { + "--exchange": {c: {} for c in self.TOB_EXCHANGES}, + "-e": "--exchange", + } choices["search"] = { "--query": None, "-q": "--query", @@ -179,6 +186,7 @@ class StocksController(StockBaseController): mt.add_raw(self.add_info) mt.add_raw("\n") mt.add_cmd("quote", self.ticker) + mt.add_cmd("tob", self.ticker) mt.add_cmd("candle", self.ticker) mt.add_cmd("codes", self.ticker) mt.add_cmd("news", self.ticker) @@ -286,6 +294,41 @@ class StocksController(StockBaseController): export=ns_parser.export, ) + @log_start_end(log=logger) + def call_tob(self, other_args: List[str]): + """Process quote command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="quote", + description="Get top of book for loaded ticker from selected exchange", + ) + parser.add_argument( + "-t", + "--ticker", + action="store", + dest="s_ticker", + required="-h" not in other_args and not self.ticker, + help="Ticker to get data for", + ) + parser.add_argument( + "-e", + "--exchange", + default="BZX", + choices=self.TOB_EXCHANGES, + type=str, + dest="exchange", + ) + + if not self.ticker: + if other_args and "-" not in other_args[0][0]: + other_args.insert(0, "-t") + ns_parser = self.parse_known_args_and_warn(parser, other_args) + + if ns_parser: + ticker = ns_parser.s_ticker if ns_parser.s_ticker else self.ticker + cboe_view.display_top_of_book(ticker, ns_parser.exchange) + @log_start_end(log=logger) def call_quote(self, other_args: List[str]): """Process quote command""" diff --git a/scripts/test_stocks.openbb b/scripts/test_stocks.openbb new file mode 100644 index 00000000000..66f1a7d247d --- /dev/null +++ b/scripts/test_stocks.openbb @@ -0,0 +1,9 @@ +stocks +search apple +load aapl -s 2022-10-01 +tob +tob -e EDGX +candle +codes +news +exit diff --git a/tests/openbb_terminal/stocks/cassettes/test_cboe_model/test_top_of_book.yaml b/tests/openbb_terminal/stocks/cassettes/test_cboe_model/test_top_of_book.yaml new file mode 100644 index 00000000000..e36bcccb8a1 --- /dev/null +++ b/tests/openbb_terminal/stocks/cassettes/test_cboe_model/test_top_of_book.yaml @@ -0,0 +1,58 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, + like Gecko) Chrome/106.0.0.0 Safari/537.36 + referer: + - https://www.cboe.com/us/equities/market_statistics/book_viewer/ + method: GET + uri: https://www.cboe.com/json/bzx/book/AAPL + response: + body: + string: !!binary | + H4sIAAAAAAAAA72TT2uDMBiHv0p5zy4kMf5JbqXsMOjWHnaTMlLNWqlWMbajjH33vbFatPQ22EHI + k/fJL6+JfoM9pamxFlTbnIwHjSkqnYEKKKUeZLrVoL7BXsptVYCC+Xy9BA/0KW3z6gjqUxcWV6VV + WevjxQnr9fJ59vK2mC1Wr2jWjTmDYoITKV1iVZujY58w5nif7/aOBQlCx0X1hehLEvAOtW1dmRK/ + ayjd6+POgHriJOzyzlVxKnGCB8KXMccNmsw0dtW8Nzoz1q1lEqOxZ3tATBKX2wWKjZeIAYIxhAg3 + LRpDvNl4sM2zLunmc1SCARiCHADHfj/m0oHgV+iC2r7HJAHGFI8Uo3hkzD39G7thXyEx5W4GU/7N + vkrO96c+DeKpT+XE53Lk0+ttPvIHPZ7qwn+s3/zozuf37URTPxj7EYkl/6MfYlkMdji2QxKLazfu + hvPS2FaXNf4a/eG5ndo8PXy0lxo/XICfn1+23IgKhgMAAA== + headers: + Accept-Ranges: + - bytes + CF-Cache-Status: + - EXPIRED + CF-RAY: + - 75a156ac88625716-IAD + Cache-Control: + - no-cache=False, must-revalidate=False, max-age=5 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '376' + Content-Type: + - application/json + Date: + - Fri, 14 Oct 2022 15:27:21 GMT + Last-Modified: + - Fri, 14 Oct 2022 15:27:18 GMT + Link: + - ; rel="canonical" + Server: + - cloudflare + Vary: + - Host,Accept-Encoding + status: + code: 200 + message: OK +version: 1 diff --git a/tests/openbb_terminal/stocks/cassettes/test_cboe_view/test_top_of_book.yaml b/tests/openbb_terminal/stocks/cassettes/test_cboe_view/test_top_of_book.yaml new file mode 100644 index 00000000000..a1fdc71df2f --- /dev/null +++ b/tests/openbb_terminal/stocks/cassettes/test_cboe_view/test_top_of_book.yaml @@ -0,0 +1,58 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, + like Gecko) Chrome/106.0.0.0 Safari/537.36 + referer: + - https://www.cboe.com/us/equities/market_statistics/book_viewer/ + method: GET + uri: https://www.cboe.com/json/bzx/book/AAPL + response: + body: + string: !!binary | + H4sIAAAAAAAAA5WTTWuDQBCG/0qYsxV3dVfdWwg9FNImh94klK1uE4lfuJoSQv57ZzcKJoSCF/GZ + eXxnXJML6D5NldYgurZXDrSqqGUGgnme50AmOwniAvpcftcFCFgut2twQPZpl9cViB9ZaHwqrctG + VmcjbLfr18Xbx2qx2ryj2bTqBIIE1I1jk1g3qjLsu4QYPuT7g+HAZdxwUf8i+rHLqEWpO9P2XN8u + lB5ktVcgXqjLbd6pLvoSC5Rx5tEAB7SZavWm/WxlprQdRSgjuLM+IiaJybWBZOckfASKQDwe3shH + CsZWMAW22znwnWc2KhyqNEYlGiFC8EcIJ0MoRxjHU5vUDVsmCRAiaCy8CA8NT8Zch7c2t0PPjZmt + YM5zn4b3fsT5E58Fs/SAz9LnbjPx+b3POXvww3/8EM80muWT8HGfuT6/99nU5y73ifXNh85LpTtZ + NvgfufUJBVNOj1/ducFfMMD1+gcFcAyvjwMAAA== + headers: + Accept-Ranges: + - bytes + CF-Cache-Status: + - EXPIRED + CF-RAY: + - 75a1596f3f605830-IAD + Cache-Control: + - no-cache=False, must-revalidate=False, max-age=5 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '370' + Content-Type: + - application/json + Date: + - Fri, 14 Oct 2022 15:29:14 GMT + Last-Modified: + - Fri, 14 Oct 2022 15:29:13 GMT + Link: + - ; rel="canonical" + Server: + - cloudflare + Vary: + - Host,Accept-Encoding + status: + code: 200 + message: OK +version: 1 diff --git a/tests/openbb_terminal/stocks/cassettes/test_cboe_view/test_top_of_book_bad_ticker.yaml b/tests/openbb_terminal/stocks/cassettes/test_cboe_view/test_top_of_book_bad_ticker.yaml new file mode 100644 index 00000000000..4262b607bde --- /dev/null +++ b/tests/openbb_terminal/stocks/cassettes/test_cboe_view/test_top_of_book_bad_ticker.yaml @@ -0,0 +1,55 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, + like Gecko) Chrome/106.0.0.0 Safari/537.36 + referer: + - https://www.cboe.com/us/equities/market_statistics/book_viewer/ + method: GET + uri: https://www.cboe.com/json/bzx/book/JBEUCEBFY + response: + body: + string: !!binary | + H4sIAAAAAAAAA0yNzQrCMBCEX6XsOWC1txyVevCqHkSkrMmCpU1Sko1aiu9u+oN4m5nd+WYAT61D + DTIXoJER5AChN3fXgoTDtjzvyu3+AgK4NhQYTZfy9VoWG1kU2fm4KjEweZs+lDMd2j7do22se9ls + AQl4ujYamkac1+TDJDE0SVxvAu61XhTXqqm479IzjKseNS2nh7dolnysVspFyxNp7P/ZufULPgJC + VIpCArGPlCwjx3CiN8+42e+cHvGbPP9Fs5UZfL4AAAD//wMAf/v13SoBAAA= + headers: + CF-Cache-Status: + - MISS + CF-RAY: + - 75a15e4c5aa35e17-IAD + Cache-Control: + - must-revalidate=False, no-cache=False, max-age=5 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 14 Oct 2022 15:32:33 GMT + Last-Modified: + - Fri, 14 Oct 2022 15:32:33 GMT + Link: + - ; rel="canonical" + Server: + - cloudflare + Transfer-Encoding: + - chunked + Vary: + - Host,Accept-Encoding + X-Frame-Options: + - SAMEORIGIN + status: + code: 200 + message: OK +version: 1 diff --git a/tests/openbb_terminal/stocks/test_cboe_model.py b/tests/openbb_terminal/stocks/test_cboe_model.py new file mode 100644 index 00000000000..7bbbaa0b454 --- /dev/null +++ b/tests/openbb_terminal/stocks/test_cboe_model.py @@ -0,0 +1,7 @@ +import pytest +from openbb_terminal.stocks import cboe_model + + +@pytest.mark.vcr +def test_top_of_book(): + cboe_model.get_top_of_book("AAPL") diff --git a/tests/openbb_terminal/stocks/test_cboe_view.py b/tests/openbb_terminal/stocks/test_cboe_view.py new file mode 100644 index 00000000000..1cb8b94dd1b --- /dev/null +++ b/tests/openbb_terminal/stocks/test_cboe_view.py @@ -0,0 +1,14 @@ +import pytest +from openbb_terminal.stocks import cboe_view + + +@pytest.mark.vcr +@pytest.mark.record_stdout +def test_top_of_book(): + cboe_view.display_top_of_book("AAPL") + + +@pytest.mark.vcr +@pytest.mark.record_stdout +def test_top_of_book_bad_ticker(): + cboe_view.display_top_of_book("JBEUCEBFY") diff --git a/tests/openbb_terminal/stocks/test_stocks_controller.py b/tests/openbb_terminal/stocks/test_stocks_controller.py index 25b272da5c7..6f59019b3a0 100644 --- a/tests/openbb_terminal/stocks/test_stocks_controller.py +++ b/tests/openbb_terminal/stocks/test_stocks_controller.py @@ -271,6 +271,13 @@ def test_call_func_expect_queue(expected_queue, func, queue): [], dict(), ), + ( + "call_tob", + ["--ticker=AAPL"], + "cboe_view.display_top_of_book", + [], + dict(), + ), ( "call_candle", [ diff --git a/tests/openbb_terminal/stocks/txt/test_cboe_view/test_top_of_book.txt b/tests/openbb_terminal/stocks/txt/test_cboe_view/test_top_of_book.txt new file mode 100644 index 00000000000..d012948e643 --- /dev/null +++ b/tests/openbb_terminal/stocks/txt/test_cboe_view/test_top_of_book.txt @@ -0,0 +1,7 @@ + Bid Size Bid Price Ask Price Ask Size +0 700 140.29 140.31 200 +1 800 140.28 140.32 600 +2 300 140.27 140.33 1067 +3 600 140.26 140.34 400 +4 200 140.25 140.35 400 + diff --git a/tests/openbb_terminal/stocks/txt/test_cboe_view/test_top_of_book_bad_ticker.txt b/tests/openbb_terminal/stocks/txt/test_cboe_view/test_top_of_book_bad_ticker.txt new file mode 100644 index 00000000000..cbf65ed1a41 --- /dev/null +++ b/tests/openbb_terminal/stocks/txt/test_cboe_view/test_top_of_book_bad_ticker.txt @@ -0,0 +1,2 @@ +[red]Unknown symbol: JBEUCEBFY[/red] + 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 e2231ac47d3..7f5b5f72ded 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 @@ -8,6 +8,7 @@ Exchange: Currency: [unvl] quote view the current price for a specific stock ticker[/unvl] [YahooFinance] +[unvl] tob view top of book for loaded ticker (US exchanges only)[/unvl] [CBOE] [unvl] candle view a candle chart for a specific stock ticker[/unvl] [unvl] codes FIGI, SIK and SIC codes codes[/unvl] [Polygon] [unvl] news latest news of the company[/unvl] [Feedparser, NewsApi] diff --git a/website/content/terminal/stocks/tob/_index.md b/website/content/terminal/stocks/tob/_index.md new file mode 100644 index 00000000000..836c1037f31 --- /dev/null +++ b/website/content/terminal/stocks/tob/_index.md @@ -0,0 +1,12 @@ +``` +usage: quote [-t S_TICKER] [-e {BZX,EDGX,BYX,EDGA}] [-h] +``` +Get top of book for loaded ticker from selected exchange +``` +optional arguments: + -t S_TICKER, --ticker S_TICKER + Ticker to get data for (default: None) + -e {BZX,EDGX,BYX,EDGA}, --exchange {BZX,EDGX,BYX,EDGA} + -h, --help show this help message (default: False) +``` +For more information and examples, use 'about quote' to access the related guide. diff --git a/website/data/menu/main.yml b/website/data/menu/main.yml index cce42447724..4d9fc5d73d5 100644 --- a/website/data/menu/main.yml +++ b/website/data/menu/main.yml @@ -1507,6 +1507,8 @@ main: ref: /terminal/common/qa/unitroot - name: quote ref: /terminal/stocks/quote + - name: tob + ref: /terminal/stocks/tob - name: research ref: /terminal/stocks/res sub: