├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── config.py.example ├── eodhd ├── APIs │ ├── BaseAPI.py │ ├── BondsFundamentalsAPI.py │ ├── BulkEodSplitsDividendsAPI.py │ ├── EarningTrendsAPI.py │ ├── EconomicEventsDataAPI.py │ ├── EodHistoricalStockMarketDataAPI.py │ ├── FinancialNewsAPI.py │ ├── FundamentalDataAPI.py │ ├── HistoricalDividendsAPI.py │ ├── HistoricalMarketCapitalizationAPI.py │ ├── HistoricalSplitsAPI.py │ ├── InsiderTransactionsAPI.py │ ├── IntradayDataAPI.py │ ├── ListOfExchangesAPI.py │ ├── LiveStockPricesAPI.py │ ├── MacroIndicatorsAPI.py │ ├── OptionsDataAPI.py │ ├── StockMarketScreenerAPI.py │ ├── StockMarketTickDataAPI.py │ ├── TechnicalIndicatorAPI.py │ ├── TradingHours_StockMarketHolidays_SymbolsChangeHistory.py │ ├── UpcomingEarningsAPI.py │ ├── UpcomingIPOsAPI.py │ ├── UpcomingSplitsAPI.py │ └── __init__.py ├── __init__.py ├── apiclient.py ├── eodhdgraphs.py └── websocketclient.py ├── example_api.py ├── example_graph.py ├── example_scanner.py ├── example_websockets.py ├── requirements.txt ├── setup.py └── tests ├── __init__.py └── test_websocketclient.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # VS code 132 | .vscode 133 | 134 | config.py 135 | *.csv 136 | 137 | # Sandbox file for testing 138 | sandbox.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michael Whittle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EodHistoricalData/EODHD-APIs-Python-Financial-Library/8f65ea9a9d3ba5321605bfbe823803fd8bdb6bab/MANIFEST.in -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Official EODHD APIs Python Library 2 | [Full documentation](https://eodhd.com/financial-apis/python-financial-libraries-and-code-samples/) 3 | 4 | This is the official Python library developed by [EODHD](https://eodhd.com) for accessing various financial data via API in your code. If you have any feedback or questions, you can reach out to our support team, available 24/7 via live chat on [our website](https://eodhd.com). 5 | 6 | To access our APIs, you need to register on our site (it’s free) and obtain an API key. Access to some data requires a subscription to one of [our paid plans](https://eodhd.com/pricing). 7 | 8 | ## List of supported data: 9 | 10 | * End of the Day Historical Stock Market Data 11 | * Live (Delayed) Stock Prices and Macroeconomic Data 12 | * Bonds Fundamentals 13 | * Intraday Historical Data 14 | * Historical Dividends 15 | * Historical Splits 16 | * Bulk API for EOD, Splits and Dividends 17 | * Calendar. Upcoming Earnings, Trends, IPOs and Splits 18 | * Economic Events 19 | * Stock Market and Financial News 20 | * List of supported Exchanges 21 | * Insider Transactions 22 | * Macro Indicators 23 | * Exchange API. Trading Hours, Stock Market Holidays, Symbols Change History 24 | * Stock Market Screener 25 | * Technical Indicator 26 | * Historical Market Capitalization 27 | * Fundamental Data: Stocks, ETFs, Mutual Funds, Indices and Cryptocurrencies 28 | 29 | All functions are described in our [documentation](https://eodhd.com/financial-apis/python-financial-libraries-and-code-samples/). 30 | 31 | 32 | ## Installation 33 | 34 | In short: 35 | 36 | python3 -m pip install eodhd -U 37 | 38 | If you have any difficulties, go to our full [documentation](https://eodhd.com/financial-apis/python-financial-libraries-and-code-samples/) with step by step instructions. 39 | 40 | ## Sample code and examples 41 | 42 | The files below contain examples of available functions: 43 | 44 | * example_api.py: describes the functions that can be used in the APIClient class. 45 | * example_scanner.py: describes the functions that can be used in the ScannerClient class. 46 | * example_websockets.py: describes the functions that can be used in the WebSocketClient class. 47 | 48 | 49 | New features will be added to the example files. The most relevant functions can be viewed directly in the code files. 50 | -------------------------------------------------------------------------------- /config.py.example: -------------------------------------------------------------------------------- 1 | """EODHD API Key""" 2 | 3 | API_KEY = "YOUR_API_KEY" 4 | -------------------------------------------------------------------------------- /eodhd/APIs/BaseAPI.py: -------------------------------------------------------------------------------- 1 | from json.decoder import JSONDecodeError 2 | import sys 3 | from requests import get as requests_get 4 | from requests import ConnectionError as requests_ConnectionError 5 | from requests import Timeout as requests_Timeout 6 | from requests.exceptions import HTTPError as requests_HTTPError 7 | from rich.console import Console 8 | 9 | class BaseAPI: 10 | 11 | def __init__(self) -> None: 12 | self._api_url = "https://eodhd.com/api" 13 | self.console = Console() 14 | 15 | 16 | def _rest_get_method(self, api_key: str, endpoint: str = "", uri: str = "", querystring: str = ""): 17 | """Generic REST GET""" 18 | 19 | if endpoint.strip() == "": 20 | raise ValueError("endpoint is empty!") 21 | 22 | try: 23 | resp = requests_get(f"{self._api_url}/{endpoint}/{uri}?api_token={api_key}&fmt=json{querystring}") 24 | 25 | if resp.status_code != 200: 26 | try: 27 | if "message" in resp.json(): 28 | resp_message = resp.json()["message"] 29 | elif "errors" in resp.json(): 30 | self.console.log(resp.json()) 31 | sys.exit(1) 32 | else: 33 | resp_message = "" 34 | 35 | message = f"({resp.status_code}) {self._api_url} - {resp_message}" 36 | self.console.log(message) 37 | 38 | except JSONDecodeError as err: 39 | self.console.log(err) 40 | 41 | try: 42 | resp.raise_for_status() 43 | 44 | return resp.json() 45 | 46 | except ValueError as err: 47 | self.console.log(err) 48 | 49 | except requests_ConnectionError as err: 50 | self.console.log(err) 51 | except requests_HTTPError as err: 52 | self.console.log(err) 53 | except requests_Timeout as err: 54 | self.console.log(err) 55 | return {} 56 | -------------------------------------------------------------------------------- /eodhd/APIs/BondsFundamentalsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class BondsFundamentalsAPI(BaseAPI): 4 | 5 | def get_bonds_fundamentals_data(self, api_token: str, isin): 6 | 7 | endpoint = 'bond-fundamentals' 8 | uri = f'{isin}' 9 | 10 | query_string = '' 11 | if isin is None or isin == '': 12 | raise ValueError('isin cannot be empty') 13 | 14 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string, uri = uri) 15 | -------------------------------------------------------------------------------- /eodhd/APIs/BulkEodSplitsDividendsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class BulkEodSplitsDividendsDataAPI(BaseAPI): 4 | 5 | def get_eod_splits_dividends_data(self, api_token: str, country = 'US', type = None, date = None, 6 | symbols = None, filter = None): 7 | 8 | endpoint = 'eod-bulk-last-day' 9 | uri = f'{country}' 10 | 11 | query_string = '' 12 | 13 | if type is not None: 14 | query_string += '&type=' + str(type) 15 | if date is not None: 16 | query_string += '&date=' + str(date) 17 | if symbols: 18 | query_string += '&symbols=' + str(symbols) 19 | if filter is not None: 20 | query_string += '&filter=' + str(filter) 21 | 22 | 23 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string, uri = uri) 24 | -------------------------------------------------------------------------------- /eodhd/APIs/EarningTrendsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class EarningTrendsAPI(BaseAPI): 4 | 5 | def get_earning_trends_data(self, api_token: str, symbols): 6 | 7 | endpoint = 'calendar/trends' 8 | 9 | query_string = '' 10 | 11 | query_string += '&symbols=' + str(symbols) 12 | 13 | 14 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) -------------------------------------------------------------------------------- /eodhd/APIs/EconomicEventsDataAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class EconomicEventsDataAPI(BaseAPI): 4 | 5 | def get_economic_events_data(self, api_token: str, date_from: str = None, date_to: str = None, 6 | country: str = None, comparison: str = None, offset: int = None, limit: int = None): 7 | 8 | endpoint = 'economic-events' 9 | 10 | query_string = '' 11 | 12 | if date_to is not None: 13 | query_string += "&to=" + date_to 14 | if date_from is not None: 15 | query_string += "&from=" + date_from 16 | if country is not None: 17 | query_string += "&country=" + country 18 | if comparison is not None: 19 | query_string += "&comparison=" + comparison 20 | if offset is not None: 21 | query_string += "&offset=" + str(offset) 22 | if limit is not None: 23 | query_string += "&limit=" + str(limit) 24 | 25 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) 26 | -------------------------------------------------------------------------------- /eodhd/APIs/EodHistoricalStockMarketDataAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class EodHistoricalStockMarketDataAPI(BaseAPI): 4 | 5 | def get_eod_historical_stock_market_data(self, api_token: str, symbol: str, period: str, 6 | from_date: str = None, to_date: str = None, order = None): 7 | 8 | possible_periods = ['d', 'w', 'm'] 9 | 10 | endpoint = 'eod' 11 | 12 | if symbol.strip() == "" or symbol is None: 13 | raise ValueError("Ticker is empty. You need to add ticker to args") 14 | if period not in possible_periods: 15 | raise ValueError("Interval must be in ['d', 'w', 'm'] values") 16 | 17 | uri = f'{symbol}' 18 | query_string = '' 19 | 20 | query_string += '&period=' + str(period) 21 | 22 | if from_date is not None: 23 | query_string += '&from=' + str(from_date) 24 | if to_date is not None: 25 | query_string += '&to=' + str(to_date) 26 | if order is not None: 27 | query_string += '&order=' + str(order) 28 | 29 | 30 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string, uri = uri) -------------------------------------------------------------------------------- /eodhd/APIs/FinancialNewsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class FinancialNewsAPI(BaseAPI): 4 | 5 | possible_tags = ['balance sheet', 'capital employed', 'class action', 'company announcement', 6 | 'consensus eps estimate', 'consensus estimate', 'credit rating', 7 | 'discounted cash flow', 'dividend payments', 'earnings estimate', 8 | 'earnings growth', 'earnings per share', 'earnings release', 'earnings report', 9 | 'earnings results', 'earnings surprise', 'estimate revisions', 'european regulatory news', 10 | 'financial results', 'fourth quarter', 'free cash flow', 'future cash flows', 11 | 'growth rate', 'initial public offering', 'insider ownership', 'insider transactions', 12 | 'institutional investors', 'institutional ownership', 'intrinsic value', 13 | 'market research reports', 'net income', 'operating income', 'present value', 14 | 'press releases', 'price target', 'quarterly earnings', 'quarterly results', 15 | 'ratings', 'research analysis and reports', 'return on equity', 'revenue estimates', 16 | 'revenue growth', 'roce', 'roe', 'share price', 'shareholder rights', 'shareholder', 17 | 'shares outstanding', 'strong buy', 'total revenue', 'zacks investment research', 'zacks rank'] 18 | 19 | def financial_news(self, api_token: str, s = None, t = None, from_date = None, to_date = None, limit = None, offset = None): 20 | 21 | endpoint = 'news' 22 | 23 | query_string = '' 24 | 25 | if t is None and s is None: 26 | raise ValueError("s or t is empty. You need to add s or t to args") 27 | if t is not None and s is not None: 28 | query_string += '&s=' + str(s) 29 | elif s is not None and t is None: 30 | query_string += '&s=' + str(s) 31 | else: 32 | if t in self.possible_tags: 33 | query_string += '&t=' + str(t) 34 | else: 35 | raise ValueError("Incorrect value was fullfiled for s or t") 36 | if limit is not None: 37 | query_string += '&limit=' + str(limit) 38 | if offset is not None: 39 | query_string += '&offset=' + str(offset) 40 | if from_date is not None: 41 | query_string += '&from=' + str(from_date) 42 | if to_date is not None: 43 | query_string += '&to=' + str(to_date) 44 | 45 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) 46 | 47 | def get_sentiment(self, api_token: str, s: str, from_date: str = None, to_date: str = None): 48 | 49 | endpoint = 'sentiments' 50 | 51 | query_string = '' 52 | 53 | if s is None: 54 | raise ValueError("s argument is empty. You need to add s to args") 55 | 56 | query_string += '&s=' + str(s) 57 | 58 | if from_date is not None: 59 | query_string += '&from=' + str(from_date) 60 | if to_date is not None: 61 | query_string += '&to=' + str(to_date) 62 | 63 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) 64 | -------------------------------------------------------------------------------- /eodhd/APIs/FundamentalDataAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class FundamentalDataAPI(BaseAPI): 4 | 5 | def get_fundamentals_data(self, api_token: str, ticker: str): 6 | 7 | endpoint = 'fundamentals' 8 | 9 | if ticker.strip() == "" or ticker is None: 10 | raise ValueError("Ticker is empty. You need to add ticker to args") 11 | 12 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, uri = ticker) 13 | -------------------------------------------------------------------------------- /eodhd/APIs/HistoricalDividendsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class HistoricalDividendsAPI(BaseAPI): 4 | 5 | def get_historical_dividends_data(self, api_token: str, ticker: str, date_from: str = None, date_to: str = None): 6 | 7 | endpoint = 'div' 8 | 9 | if ticker.strip() == "" or ticker is None: 10 | raise ValueError("Ticker is empty. You need to add ticker to args") 11 | 12 | query_string = '' 13 | 14 | if date_to is not None: 15 | query_string += "&to=" + date_to 16 | if date_from is not None: 17 | query_string += "&from=" + date_from 18 | 19 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, uri = ticker, querystring = query_string) 20 | -------------------------------------------------------------------------------- /eodhd/APIs/HistoricalMarketCapitalizationAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class HistoricalMarketCapitalization(BaseAPI): 4 | 5 | def get_historical_market_capitalization_data(self, api_token: str, ticker, from_date: str = None, to_date: str = None): 6 | 7 | endpoint = 'historical-market-cap' 8 | uri = f'{ticker}' 9 | 10 | query_string = '' 11 | 12 | if from_date is not None: 13 | query_string += '&from=' + str(from_date) 14 | if to_date is not None: 15 | query_string += '&to=' + str(to_date) 16 | 17 | 18 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string, uri = uri) 19 | -------------------------------------------------------------------------------- /eodhd/APIs/HistoricalSplitsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class HistoricalSplitsAPI(BaseAPI): 4 | 5 | def get_historical_splits_data(self, api_token: str, ticker: str, date_from: str = None, date_to: str = None): 6 | 7 | endpoint = 'splits' 8 | 9 | if ticker.strip() == "" or ticker is None: 10 | raise ValueError("Ticker is empty. You need to add ticker to args") 11 | 12 | query_string = '' 13 | 14 | if date_to is not None: 15 | query_string += "&to=" + date_to 16 | if date_from is not None: 17 | query_string += "&from=" + date_from 18 | 19 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, uri = ticker, querystring = query_string) 20 | -------------------------------------------------------------------------------- /eodhd/APIs/InsiderTransactionsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class InsiderTransactionsAPI(BaseAPI): 4 | 5 | def get_insider_transactions_data(self, api_token: str, date_from: str = None, date_to: str = None, 6 | code: str = None, limit: int = None): 7 | 8 | endpoint = 'insider-transactions' 9 | 10 | query_string = '' 11 | 12 | if date_to is not None: 13 | query_string += "&to=" + date_to 14 | if date_from is not None: 15 | query_string += "&from=" + date_from 16 | if code is not None: 17 | query_string += "&code=" + code 18 | if limit is not None: 19 | query_string += "&limit=" + str(limit) 20 | 21 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) 22 | -------------------------------------------------------------------------------- /eodhd/APIs/IntradayDataAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class IntradayDataAPI(BaseAPI): 4 | 5 | def get_intraday_historical_data(self, api_token: str, symbol: str, interval: str, 6 | from_unix_time: str = None, to_unix_time: str = None): 7 | 8 | possible_intervals = ['5m', '1h', '1m'] 9 | 10 | endpoint = 'intraday' 11 | 12 | if symbol.strip() == "" or symbol is None: 13 | raise ValueError("Ticker is empty. You need to add ticker to args") 14 | if interval not in possible_intervals: 15 | raise ValueError("Interval must be in ['5m', '1h', '1m'] values") 16 | 17 | uri = f'{symbol}' 18 | query_string = '' 19 | 20 | query_string += '&interval=' + str(interval) 21 | 22 | if from_unix_time is not None: 23 | query_string += '&from=' + str(from_unix_time) 24 | if to_unix_time is not None: 25 | query_string += '&to=' + str(to_unix_time) 26 | 27 | 28 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string, uri = uri) -------------------------------------------------------------------------------- /eodhd/APIs/ListOfExchangesAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class ListOfExchangesAPI(BaseAPI): 4 | 5 | def get_list_of_exchanges(self, api_token: str): 6 | 7 | endpoint = 'exchanges-list' 8 | 9 | 10 | return self._rest_get_method(api_key = api_token, endpoint = endpoint) 11 | 12 | def get_list_of_tickers(self, api_token: str, code, delisted = 1): 13 | 14 | endpoint = 'exchange-symbol-list' 15 | uri = f'{code}' 16 | 17 | query_string = '' 18 | 19 | query_string += '&delisted=' + str(delisted) 20 | 21 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string, uri = uri) -------------------------------------------------------------------------------- /eodhd/APIs/LiveStockPricesAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class LiveStockPricesAPI(BaseAPI): 4 | 5 | def get_live_stock_prices(self, api_token: str, ticker: str, s:str): 6 | 7 | endpoint = 'real-time' 8 | query_string = '' 9 | 10 | if ticker.strip() == "" or ticker is None: 11 | raise ValueError("Ticker is empty. You need to add ticker to args") 12 | 13 | if s is not None: 14 | query_string += '&s=' + str(s) 15 | 16 | 17 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, uri = ticker, querystring=query_string) 18 | 19 | -------------------------------------------------------------------------------- /eodhd/APIs/MacroIndicatorsAPI.py: -------------------------------------------------------------------------------- 1 | 2 | from .BaseAPI import BaseAPI 3 | 4 | class MacroIndicatorsAPI(BaseAPI): 5 | 6 | def get_macro_indicators_data(self, api_token: str, country, indicator = None): 7 | 8 | endpoint = 'macro-indicator' 9 | uri = f'{country}' 10 | 11 | query_string = '' 12 | 13 | if indicator is not None: 14 | query_string += '&indicator=' + str(indicator) 15 | 16 | 17 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string, uri = uri) -------------------------------------------------------------------------------- /eodhd/APIs/OptionsDataAPI.py: -------------------------------------------------------------------------------- 1 | 2 | from .BaseAPI import BaseAPI 3 | 4 | class OptionsDataAPI(BaseAPI): 5 | 6 | def get_options_data(self, api_token: str, ticker, date_to = None, date_from = None, 7 | trade_date_to = None, trade_date_from = None, contract_name = None): 8 | 9 | endpoint = 'options' 10 | if ticker.strip() == "" or ticker is None: 11 | raise ValueError("Ticker is empty. You need to add ticker to args") 12 | 13 | uri = f'{ticker}' 14 | 15 | query_string = '' 16 | 17 | if date_to is not None: 18 | query_string += '&to=' + str(date_to) 19 | if date_from is not None: 20 | query_string += '&from=' + str(date_from) 21 | if trade_date_from is not None: 22 | query_string += '&trade_date_from=' + str(trade_date_from) 23 | if trade_date_to is not None: 24 | query_string += '&trade_date_to=' + str(trade_date_to) 25 | if contract_name is not None: 26 | query_string += '&contract_name=' + str(contract_name) 27 | 28 | 29 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string, uri = uri) -------------------------------------------------------------------------------- /eodhd/APIs/StockMarketScreenerAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class StockMarketScreenerAPI(BaseAPI): 4 | 5 | def stock_market_screener(self, api_token: str, sort = None, filters = None, limit = None, signals = None, offset = None): 6 | 7 | endpoint = 'screener' 8 | 9 | query_string = '' 10 | 11 | if sort is not None: 12 | query_string += '&sort=' + str(sort) 13 | if filters is not None: 14 | query_string += '&filters=' + str(filters).replace('\'', '\"') 15 | if limit is not None: 16 | query_string += '&limit=' + str(limit) 17 | if signals is not None: 18 | query_string += '&signals=' + str(signals) 19 | if offset is not None: 20 | query_string += '&offset=' + str(offset) 21 | 22 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) -------------------------------------------------------------------------------- /eodhd/APIs/StockMarketTickDataAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class StockMarketTickDataAPI(BaseAPI): 4 | 5 | def get_stock_market_tick_data(self, api_token: str, symbol: str, from_timestamp: str, to_timestamp: str, limit: int): 6 | 7 | endpoint = 'ticks' 8 | 9 | query_string = '' 10 | 11 | if symbol is None: 12 | raise ValueError("symbol is empty. Need to add symbol to args") 13 | if from_timestamp is None: 14 | raise ValueError("from_timestamp is empty. Need to add from_timestamp to args") 15 | if to_timestamp is None: 16 | raise ValueError("to_timestamp is empty. Need to add to_timestamp to args") 17 | 18 | query_string += '&s=' + str(symbol) 19 | query_string += '&from=' + str(from_timestamp) 20 | query_string += '&to=' + str(to_timestamp) 21 | 22 | if limit is not None: 23 | query_string += '&limit=' + str(limit) 24 | 25 | 26 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) -------------------------------------------------------------------------------- /eodhd/APIs/TechnicalIndicatorAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | 4 | 5 | class TechnicalIndicatorAPI(BaseAPI): 6 | 7 | possible_functions = ['avgvol', 'avgvolccy', 'sma', 'ema', 'wma', 'volatility', 'stochastic', 8 | 'rsi', 'stddev', 'stochrsi', 'slope', 'dmi', 'adx', 'macd', 'atr', 9 | 'cci', 'sar', 'bbands', 'format_amibroker', 'splitadjusted'] 10 | 11 | def get_technical_indicator_data(self, api_token: str, ticker: str, function: str, period: int = 50, 12 | date_from: str = None, date_to: str = None, order: str = 'a', 13 | splitadjusted_only: str = '0', agg_period = None, 14 | fast_kperiod = None, slow_kperiod = None, slow_dperiod = None, 15 | fast_dperiod = None, fast_period = None, slow_period = None, 16 | signal_period = None, acceleration = None, maximum = None): 17 | endpoint = 'technical/' 18 | 19 | if ticker.strip() == "" or ticker is None: 20 | raise ValueError("Ticker is empty. You need to add ticker to args") 21 | 22 | if function.strip() == "" or function is None: 23 | raise ValueError("Function is empty. You need to add function to args") 24 | 25 | if function not in self.possible_functions: 26 | raise ValueError("Incorrect value for fanction parameter") 27 | 28 | query_string = f'&order={order}&splitadjusted_only={splitadjusted_only}&period={period}&function={function}' 29 | 30 | if date_to is not None: 31 | query_string += "&to=" + str(date_to) 32 | if date_from is not None: 33 | query_string += "&from=" + str(date_from) 34 | 35 | if function == 'splitadjusted': 36 | possible_agg_period = ['d', 'w', 'm'] 37 | if agg_period is not None: 38 | if agg_period not in possible_agg_period: 39 | raise ValueError("agg_period must be in ['d', 'w', 'm']") 40 | query_string += "&agg_period=" + str(agg_period) 41 | 42 | if function == 'stochastic': 43 | if fast_kperiod is not None: 44 | query_string += "&fast_kperiod=" + str(fast_kperiod) 45 | if slow_kperiod is not None: 46 | query_string += "&slow_kperiod=" + str(slow_kperiod) 47 | if slow_dperiod is not None: 48 | query_string += "&slow_dperiod=" + str(slow_dperiod) 49 | 50 | if function == 'stochrsi': 51 | if fast_kperiod is not None: 52 | query_string += "&fast_kperiod=" + str(fast_kperiod) 53 | if fast_dperiod is not None: 54 | query_string += "&fast_dperiod=" + str(fast_dperiod) 55 | 56 | if function == 'macd': 57 | if fast_period is not None: 58 | query_string += "&fast_period=" + str(fast_period) 59 | if slow_period is not None: 60 | query_string += "&slow_period=" + str(slow_period) 61 | if signal_period is not None: 62 | query_string += "&signal_period=" + str(signal_period) 63 | 64 | if function == 'sar': 65 | if acceleration is not None: 66 | query_string += "&acceleration=" + str(acceleration) 67 | if maximum is not None: 68 | query_string += "&maximum=" + str(maximum) 69 | 70 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, uri = ticker, querystring = query_string) 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /eodhd/APIs/TradingHours_StockMarketHolidays_SymbolsChangeHistory.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class TradingHours_StockMarketHolidays_SymbolsChangeHistoryAPI(BaseAPI): 4 | 5 | def get_details_trading_hours_stock_market_holidays(self, api_token: str, code, from_date = None, to_date = None): 6 | 7 | endpoint = 'exchange-details' 8 | uri = f'{code}' 9 | 10 | query_string = '' 11 | 12 | if from_date is not None: 13 | query_string += '&from=' + str(from_date) 14 | if to_date is not None: 15 | query_string += '&to=' + str(to_date) 16 | 17 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, uri = uri, querystring = query_string) 18 | 19 | def symbol_change_history(self, api_token: str, from_date = None, to_date = None): 20 | 21 | endpoint = 'symbol-change-history' 22 | 23 | query_string = '' 24 | 25 | if from_date is not None: 26 | query_string += '&from=' + str(from_date) 27 | if to_date is not None: 28 | query_string += '&to=' + str(to_date) 29 | 30 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) -------------------------------------------------------------------------------- /eodhd/APIs/UpcomingEarningsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class UpcomgingEarningsAPI(BaseAPI): 4 | 5 | def get_upcoming_earnings_data(self, api_token: str, from_date = None, to_date = None, symbols = None): 6 | 7 | endpoint = 'calendar/earnings' 8 | 9 | query_string = '' 10 | 11 | if from_date is not None: 12 | query_string += '&from=' + str(from_date) 13 | if to_date is not None: 14 | query_string += '&to=' + str(to_date) 15 | if symbols: 16 | query_string += '&symbols=' + str(symbols) 17 | 18 | 19 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) -------------------------------------------------------------------------------- /eodhd/APIs/UpcomingIPOsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class UpcomingIPOsAPI(BaseAPI): 4 | 5 | def get_upcoming_IPOs_data(self, api_token: str, from_date = None, to_date = None): 6 | 7 | endpoint = 'calendar/ipos' 8 | 9 | query_string = '' 10 | 11 | if from_date is not None: 12 | query_string += '&from=' + str(from_date) 13 | if to_date is not None: 14 | query_string += '&to=' + str(to_date) 15 | 16 | 17 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) -------------------------------------------------------------------------------- /eodhd/APIs/UpcomingSplitsAPI.py: -------------------------------------------------------------------------------- 1 | from .BaseAPI import BaseAPI 2 | 3 | class UpcomingSplitsAPI(BaseAPI): 4 | 5 | def get_upcoming_splits_data(self, api_token: str, from_date = None, to_date = None): 6 | 7 | endpoint = 'calendar/splits' 8 | 9 | query_string = '' 10 | 11 | if from_date is not None: 12 | query_string += '&from=' + str(from_date) 13 | if to_date is not None: 14 | query_string += '&to=' + str(to_date) 15 | 16 | 17 | return self._rest_get_method(api_key = api_token, endpoint = endpoint, querystring = query_string) -------------------------------------------------------------------------------- /eodhd/APIs/__init__.py: -------------------------------------------------------------------------------- 1 | from .HistoricalDividendsAPI import HistoricalDividendsAPI 2 | from .HistoricalSplitsAPI import HistoricalSplitsAPI 3 | from .TechnicalIndicatorAPI import TechnicalIndicatorAPI 4 | from .LiveStockPricesAPI import LiveStockPricesAPI 5 | from .EconomicEventsDataAPI import EconomicEventsDataAPI 6 | from .InsiderTransactionsAPI import InsiderTransactionsAPI 7 | from .FundamentalDataAPI import FundamentalDataAPI 8 | from .BulkEodSplitsDividendsAPI import BulkEodSplitsDividendsDataAPI 9 | from .UpcomingEarningsAPI import UpcomgingEarningsAPI 10 | from .EarningTrendsAPI import EarningTrendsAPI 11 | from .UpcomingIPOsAPI import UpcomingIPOsAPI 12 | from .UpcomingSplitsAPI import UpcomingSplitsAPI 13 | from .MacroIndicatorsAPI import MacroIndicatorsAPI 14 | from .BondsFundamentalsAPI import BondsFundamentalsAPI 15 | from .ListOfExchangesAPI import ListOfExchangesAPI 16 | from .TradingHours_StockMarketHolidays_SymbolsChangeHistory import TradingHours_StockMarketHolidays_SymbolsChangeHistoryAPI 17 | from .StockMarketScreenerAPI import StockMarketScreenerAPI 18 | from .FinancialNewsAPI import FinancialNewsAPI 19 | from .OptionsDataAPI import OptionsDataAPI 20 | from .IntradayDataAPI import IntradayDataAPI 21 | from .EodHistoricalStockMarketDataAPI import EodHistoricalStockMarketDataAPI 22 | from .StockMarketTickDataAPI import StockMarketTickDataAPI 23 | from .HistoricalMarketCapitalizationAPI import HistoricalMarketCapitalization -------------------------------------------------------------------------------- /eodhd/__init__.py: -------------------------------------------------------------------------------- 1 | """ __init__.py """ 2 | 3 | from eodhd.apiclient import APIClient 4 | from eodhd.apiclient import ScannerClient 5 | from eodhd.eodhdgraphs import EODHDGraphs 6 | from eodhd.websocketclient import WebSocketClient 7 | from eodhd import APIs 8 | 9 | 10 | # Version of eodhd package 11 | __version__ = "1.0.31" 12 | -------------------------------------------------------------------------------- /eodhd/apiclient.py: -------------------------------------------------------------------------------- 1 | """apiclient.py""" 2 | 3 | from json.decoder import JSONDecodeError 4 | import sys 5 | from enum import Enum 6 | from datetime import datetime 7 | from datetime import timedelta 8 | from re import compile as re_compile 9 | import pandas as pd 10 | import numpy as np 11 | from requests import get as requests_get 12 | from requests import ConnectionError as requests_ConnectionError 13 | from requests import Timeout as requests_Timeout 14 | from requests.exceptions import HTTPError as requests_HTTPError 15 | from rich.console import Console 16 | from rich.progress import track 17 | 18 | from eodhd.APIs import HistoricalDividendsAPI 19 | from eodhd.APIs import HistoricalSplitsAPI 20 | from eodhd.APIs import TechnicalIndicatorAPI 21 | from eodhd.APIs import LiveStockPricesAPI 22 | from eodhd.APIs import EconomicEventsDataAPI 23 | from eodhd.APIs import InsiderTransactionsAPI 24 | from eodhd.APIs import FundamentalDataAPI 25 | from eodhd.APIs import BulkEodSplitsDividendsDataAPI 26 | from eodhd.APIs import UpcomgingEarningsAPI 27 | from eodhd.APIs import EarningTrendsAPI 28 | from eodhd.APIs import UpcomingIPOsAPI 29 | from eodhd.APIs import UpcomingSplitsAPI 30 | from eodhd.APIs import MacroIndicatorsAPI 31 | from eodhd.APIs import BondsFundamentalsAPI 32 | from eodhd.APIs import ListOfExchangesAPI 33 | from eodhd.APIs import TradingHours_StockMarketHolidays_SymbolsChangeHistoryAPI 34 | from eodhd.APIs import StockMarketScreenerAPI 35 | from eodhd.APIs import FinancialNewsAPI 36 | from eodhd.APIs import OptionsDataAPI 37 | from eodhd.APIs import IntradayDataAPI 38 | from eodhd.APIs import EodHistoricalStockMarketDataAPI 39 | from eodhd.APIs import StockMarketTickDataAPI 40 | from eodhd.APIs import HistoricalMarketCapitalization 41 | 42 | # minimal traceback 43 | sys.tracebacklimit = 1 44 | 45 | 46 | class Interval(Enum): 47 | """Enum: infraday""" 48 | 49 | MINUTE = "1m" 50 | FIVEMINUTES = "5m" 51 | HOUR = "1h" 52 | ONEDAY = "d" 53 | WEEK = "w" 54 | MONTH = "m" 55 | 56 | 57 | class DateUtils: 58 | """Utility class""" 59 | 60 | @staticmethod 61 | def str2datetime(_datetime: str): 62 | """Convert yyyy-mm-dd hh:mm:ss to datetime""" 63 | 64 | # Validate string datetime 65 | prog = re_compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$") 66 | if not prog.match(_datetime): 67 | raise ValueError("Incorrect datetime format: yyyy-mm-dd hh:mm:ss") 68 | 69 | return datetime.strptime(_datetime, "%Y-%m-%d %H:%M:%S") 70 | 71 | @staticmethod 72 | def str2epoch(_datetime: str): 73 | """Convert yyyy-mm-dd hh:mm:ss to datetime""" 74 | 75 | # Validate string datetime 76 | prog = re_compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$") 77 | if not prog.match(_datetime): 78 | raise ValueError("Incorrect datetime format: yyyy-mm-dd hh:mm:ss") 79 | 80 | return int(datetime.strptime(_datetime, "%Y-%m-%d %H:%M:%S").timestamp()) 81 | 82 | @staticmethod 83 | def previous_day_last_second(): 84 | """Returns the last second of the previous day""" 85 | 86 | yesterday = datetime.today() - timedelta(days=1) 87 | return str(yesterday.date()) + " 23:59:59" 88 | 89 | @staticmethod 90 | def previous_day_last_minute(): 91 | """Returns the last minute of the previous day""" 92 | 93 | yesterday = datetime.today() - timedelta(days=1) 94 | return str(yesterday.date()) + " 23:59:00" 95 | 96 | 97 | class APIClient: 98 | """API class""" 99 | 100 | def __init__(self, api_key: str) -> None: 101 | # Validate API key 102 | prog = re_compile(r"^[A-z0-9.]{16,32}$") 103 | if api_key != "demo" and not prog.match(api_key): 104 | raise ValueError("API key is invalid") 105 | 106 | self._api_key = api_key 107 | self._api_url = "https://eodhd.com/api" 108 | 109 | self.console = Console() 110 | 111 | def _rest_get(self, endpoint: str = "", uri: str = "", querystring: str = "") -> pd.DataFrame(): 112 | """Generic REST GET""" 113 | 114 | if endpoint.strip() == "": 115 | raise ValueError("endpoint is empty!") 116 | 117 | try: 118 | resp = requests_get(f"{self._api_url}/{endpoint}/{uri}?api_token={self._api_key}&fmt=json{querystring}") 119 | 120 | if resp.status_code != 200: 121 | try: 122 | if "message" in resp.json(): 123 | resp_message = resp.json()["message"] 124 | elif "errors" in resp.json(): 125 | self.console.log(resp.json()) 126 | sys.exit(1) 127 | else: 128 | resp_message = "" 129 | 130 | message = f"({resp.status_code}) {self._api_url} - {resp_message}" 131 | self.console.log(message) 132 | 133 | except JSONDecodeError as err: 134 | self.console.log(err) 135 | 136 | try: 137 | resp.raise_for_status() 138 | 139 | if isinstance(resp.json(), list): 140 | return pd.DataFrame.from_dict(resp.json()) 141 | else: 142 | return pd.DataFrame(resp.json(), index=[0]) 143 | 144 | except ValueError as err: 145 | self.console.log(err) 146 | 147 | except requests_ConnectionError as err: 148 | self.console.log(err) 149 | except requests_HTTPError as err: 150 | self.console.log(err) 151 | except requests_Timeout as err: 152 | self.console.log(err) 153 | return pd.DataFrame() 154 | 155 | def get_exchanges(self) -> pd.DataFrame: 156 | """Get supported exchanges""" 157 | 158 | return self._rest_get("exchanges-list") 159 | 160 | def get_exchange_symbols(self, uri: str = "", delisted=False) -> pd.DataFrame: 161 | """Get supported exchange symbols""" 162 | 163 | try: 164 | if uri.strip() == "": 165 | raise ValueError("endpoint uri is empty!") 166 | 167 | if delisted: 168 | return self._rest_get("exchange-symbol-list", uri, "&delisted=1") 169 | 170 | return self._rest_get("exchange-symbol-list", uri) 171 | except ValueError as err: 172 | self.console.log(err) 173 | return pd.DataFrame() 174 | 175 | def get_historical_data( 176 | self, 177 | symbol: str, 178 | interval: str = Interval, 179 | iso8601_start: str = "", 180 | iso8601_end: str = "", 181 | results: int = 300, 182 | ) -> pd.DataFrame: 183 | """Initiates a REST API call""" 184 | 185 | # validate symbol 186 | prog = re_compile(r"^[A-z0-9-$\.+]{1,48}$") 187 | if not prog.match(symbol): 188 | raise ValueError(f"Symbol is invalid: {symbol}") 189 | 190 | # replace "." with "-" in markets 191 | if symbol.count(".") == 2: 192 | symbol = symbol.replace(".", "-", 1) 193 | 194 | # validate interval 195 | try: 196 | Interval(interval) 197 | except ValueError as err: 198 | self.console.log(err) 199 | return pd.DataFrame() 200 | 201 | # init dataframe 202 | df_data = pd.DataFrame() 203 | 204 | if interval == "d" or interval == "w" or interval == "m": 205 | # api expects epoch time 206 | 207 | re_date_only = re_compile(r"^\d{4}\-\d{2}-\d{2}$") 208 | re_iso8601 = re_compile(r"^\d{4}\-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$") 209 | 210 | if iso8601_end == "" or ((iso8601_end != "" and not re_iso8601.match(iso8601_end)) and (iso8601_end != "" and not re_date_only.match(iso8601_end))): 211 | date_to = datetime.today().date() 212 | else: 213 | try: 214 | if re_date_only.match(iso8601_end): 215 | date_to = datetime.strptime(iso8601_end, "%Y-%m-%d").date() 216 | elif re_iso8601.match(iso8601_end): 217 | date_to = datetime.strptime(iso8601_end, "%Y-%m-%dT%H:%M:%S").date() 218 | else: 219 | date_to = datetime.today().date() 220 | except ValueError: 221 | self.console.log("invalid end date (yyyy-mm-ddThh-mm-ss OR yyyy-mm-dd):", iso8601_end) 222 | sys.exit() 223 | 224 | if iso8601_start == "" or ((iso8601_start != "" and not re_iso8601.match(iso8601_start)) and (iso8601_start != "" and not re_date_only.match(iso8601_start))): 225 | if interval == "1m": 226 | date_from = date_to - timedelta(minutes=(results - 1)) 227 | elif interval == "5m": 228 | date_from = date_to - timedelta(minutes=((results * 5) - 1)) 229 | elif interval == "1h": 230 | date_from = date_to - timedelta(hours=(results - 1)) 231 | else: 232 | date_from = date_to - timedelta(days=(results - 1)) 233 | else: 234 | try: 235 | if re_date_only.match(iso8601_start): 236 | date_from = datetime.strptime(iso8601_start, "%Y-%m-%d").date() 237 | elif re_iso8601.match(iso8601_start): 238 | date_from = datetime.strptime(iso8601_start, "%Y-%m-%dT%H:%M:%S").date() 239 | else: 240 | date_from = datetime.today().date() 241 | except ValueError: 242 | self.console.log("invalid start date (yyyy-mm-ddThh-mm-ss OR yyyy-mm-dd):", iso8601_start) 243 | sys.exit() 244 | 245 | df_data = self._rest_get("eod", symbol, f"&period={interval}&from={str(date_from)}&to={str(date_to)}") 246 | 247 | if len(df_data) == 0: 248 | columns_eod = [ 249 | "symbol", 250 | "interval", 251 | "open", 252 | "high", 253 | "low", 254 | "close", 255 | "adjusted_close", 256 | "volume", 257 | ] 258 | return pd.DataFrame(columns=columns_eod) 259 | 260 | if iso8601_start == "" and iso8601_end == "": 261 | df_data = df_data.tail(results) 262 | elif iso8601_start != "" and iso8601_end == "": 263 | df_data = df_data.head(results) 264 | 265 | df_data["symbol"] = symbol 266 | df_data["interval"] = interval 267 | 268 | # convert dataframe to a time series 269 | if interval == "d" or interval == "w" or interval == "m": 270 | tsidx = pd.DatetimeIndex(pd.to_datetime(df_data["date"]).dt.strftime("%Y-%m-%d")) 271 | df_data.set_index(tsidx, inplace=True) 272 | df_data = df_data.drop(columns=["date"]) 273 | else: 274 | tsidx = pd.DatetimeIndex(pd.to_datetime(df_data["datetime"]).dt.strftime("%Y-%m-%d %H:%M:%S")) 275 | df_data.set_index(tsidx, inplace=True) 276 | df_data = df_data.drop(columns=["datetime"]) 277 | 278 | df_data.columns = [ 279 | "open", 280 | "high", 281 | "low", 282 | "close", 283 | "adjusted_close", 284 | "volume", 285 | "symbol", 286 | "interval", 287 | ] 288 | 289 | # set object type to display large floats 290 | df_data.fillna(0, inplace=True) 291 | df_data["open"] = df_data["open"].astype(object) 292 | df_data["high"] = df_data["high"].astype(object) 293 | df_data["low"] = df_data["low"].astype(object) 294 | df_data["close"] = df_data["close"].astype(object) 295 | df_data["adjusted_close"] = df_data["adjusted_close"].astype(object) 296 | df_data["volume"] = df_data["volume"].astype(object) 297 | 298 | return df_data[ 299 | [ 300 | "symbol", 301 | "interval", 302 | "open", 303 | "high", 304 | "low", 305 | "close", 306 | "adjusted_close", 307 | "volume", 308 | ] 309 | ] 310 | 311 | elif interval == "1m" or interval == "5m" or interval == "1h": 312 | # api expects date in yyyy-mm-dd format 313 | 314 | re_date_only = re_compile(r"^\d{4}\-\d{2}-\d{2}$") 315 | re_iso8601 = re_compile(r"^\d{4}\-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$") 316 | re_epoch = re_compile(r"^\d{10}$") 317 | 318 | if iso8601_end == "" or ((iso8601_end != "" and not re_iso8601.match(iso8601_end)) and (iso8601_end != "" and not re_date_only.match(iso8601_end))): 319 | date_to = str(int(datetime.today().timestamp())) 320 | else: 321 | try: 322 | if re_date_only.match(iso8601_end): 323 | date_to = str(int(datetime.strptime(iso8601_end, "%Y-%m-%d").timestamp())) 324 | elif re_iso8601.match(iso8601_end): 325 | date_to = str(int(datetime.strptime(iso8601_end, "%Y-%m-%dT%H:%M:%S").timestamp())) 326 | elif re_epoch.match(iso8601_end): 327 | date_to = str(iso8601_end) 328 | else: 329 | date_to = str(int(datetime.today().timestamp())) 330 | except ValueError: 331 | self.console.log("invalid end date (yyyy-mm-ddThh-mm-ss OR yyyy-mm-dd OR nnnnnnnnnn):", iso8601_end) 332 | sys.exit() 333 | 334 | LIMIT_FOR_1M = 120 # Limit for 1m interval 335 | if iso8601_start == "" or ((iso8601_start != "" and not re_iso8601.match(iso8601_start)) and (iso8601_start != "" and not re_date_only.match(iso8601_start))): 336 | if interval == "d": 337 | date_from = str(int((datetime.fromtimestamp(int(date_to)) - timedelta(days=(results - 1))).timestamp())) 338 | elif interval == "w": 339 | date_from = str(int((datetime.fromtimestamp(int(date_to)) - timedelta(weeks=((results - 1) - 1))).timestamp())) 340 | elif interval == "m": 341 | date_from = str(int((datetime.fromtimestamp(int(date_to)) - timedelta(months=(results - 1))).timestamp())) 342 | else: 343 | date_from = str(int((datetime.fromtimestamp(int(date_to)) - timedelta(days=(LIMIT_FOR_1M))).timestamp())) 344 | else: 345 | try: 346 | if re_date_only.match(iso8601_start): 347 | date_from = str(int(datetime.strptime(iso8601_start, "%Y-%m-%d").timestamp())) 348 | elif re_iso8601.match(iso8601_start): 349 | date_from = str(int(datetime.strptime(iso8601_start, "%Y-%m-%dT%H:%M:%S").timestamp())) 350 | else: 351 | date_from = str(int(datetime.today().timestamp())) 352 | except ValueError: 353 | self.console.log("invalid start date (yyyy-mm-ddThh-mm-ss OR yyyy-mm-dd OR nnnnnnnnnn):", iso8601_start) 354 | sys.exit() 355 | 356 | df_data = self._rest_get("intraday", symbol, f"&interval={interval}&from={str(date_from)}&to={str(date_to)}") 357 | 358 | if len(df_data) == 0: 359 | columns_eod = [ 360 | "symbol", 361 | "interval", 362 | "open", 363 | "high", 364 | "low", 365 | "close", 366 | "adjusted_close", 367 | "volume", 368 | ] 369 | return pd.DataFrame(columns=columns_eod) 370 | 371 | if iso8601_start == "" and iso8601_end == "": 372 | df_data = df_data.tail(results) 373 | elif iso8601_start != "" and iso8601_end == "": 374 | df_data = df_data.head(results) 375 | 376 | df_data["symbol"] = symbol 377 | df_data["interval"] = interval 378 | 379 | # convert dataframe to a time series 380 | if interval == "d" or interval == "w" or interval == "m": 381 | tsidx = pd.DatetimeIndex(pd.to_datetime(df_data["date"]).dt.strftime("%Y-%m-%d")) 382 | df_data.set_index(tsidx, inplace=True) 383 | df_data = df_data.drop(columns=["date"]) 384 | else: 385 | tsidx = pd.DatetimeIndex(pd.to_datetime(df_data["datetime"]).dt.strftime("%Y-%m-%d %H:%M:%S")) 386 | df_data.set_index(tsidx, inplace=True) 387 | df_data = df_data.drop(columns=["datetime"]) 388 | 389 | df_data.columns = [ 390 | "epoch", 391 | "gmtoffset", 392 | "open", 393 | "high", 394 | "low", 395 | "close", 396 | "volume", 397 | "symbol", 398 | "interval", 399 | ] 400 | 401 | # set object type to display large floats 402 | df_data.fillna(0, inplace=True) 403 | df_data["gmtoffset"] = df_data["gmtoffset"].astype(object) 404 | df_data["epoch"] = df_data["epoch"].astype(object) 405 | df_data["open"] = df_data["open"].astype(object) 406 | df_data["high"] = df_data["high"].astype(object) 407 | df_data["low"] = df_data["low"].astype(object) 408 | df_data["close"] = df_data["close"].astype(object) 409 | df_data["volume"] = df_data["volume"].astype(object) 410 | 411 | return df_data[ 412 | [ 413 | "epoch", 414 | "gmtoffset", 415 | "symbol", 416 | "interval", 417 | "open", 418 | "high", 419 | "low", 420 | "close", 421 | "volume", 422 | ] 423 | ] 424 | 425 | else: 426 | self.console.log("invalid interval (1m, 5m, 1h, 1d, w, m):", iso8601_start) 427 | sys.exit() 428 | 429 | def get_historical_dividends_data(self, ticker, date_to=None, date_from=None) -> list: 430 | """Available args: 431 | ticker (required) - consists of two parts: [SYMBOL_NAME].[EXCHANGE_ID]. Example: AAPL.US 432 | date_from (not required) - date from with format Y-m-d. Example: 2000-01-01 433 | date_to (not required) - date from with format Y-m-d. Example: 2000-01-01 434 | If you skip date_from or date_to then you’ll get the maximum available data for the symbol. 435 | For more information visit: https://eodhistoricaldata.com/financial-apis/api-splits-dividends/ 436 | """ 437 | 438 | api_call = HistoricalDividendsAPI() 439 | return api_call.get_historical_dividends_data(api_token=self._api_key, ticker=ticker, date_from=date_from, date_to=date_to) 440 | 441 | def get_historical_splits_data(self, ticker, date_to=None, date_from=None) -> list: 442 | """Available args: 443 | ticker (required) - consists of two parts: [SYMBOL_NAME].[EXCHANGE_ID]. Example: AAPL.US 444 | date_from (not required) - date from with format Y-m-d. Example: 2000-01-01 445 | date_to (not required) - date from with format Y-m-d. Example: 2000-01-01 446 | If you skip date_from or date_to then you’ll get the maximum available data for the symbol. 447 | For more information visit: https://eodhistoricaldata.com/financial-apis/api-splits-dividends/ 448 | """ 449 | 450 | api_call = HistoricalSplitsAPI() 451 | return api_call.get_historical_splits_data(api_token=self._api_key, ticker=ticker, date_from=date_from, date_to=date_to) 452 | 453 | def get_technical_indicator_data( 454 | self, 455 | ticker: str, 456 | function: str, 457 | period: int = 50, 458 | date_from: str = None, 459 | date_to: str = None, 460 | order: str = "a", 461 | splitadjusted_only: str = "0", 462 | agg_period: str = None, 463 | fast_kperiod: int = None, 464 | slow_kperiod: int = None, 465 | slow_dperiod: int = None, 466 | fast_dperiod: int = None, 467 | fast_period: int = None, 468 | slow_period: int = None, 469 | signal_period: int = None, 470 | acceleration: float = None, 471 | maximum: float = None 472 | ) -> list: 473 | """Available args: 474 | ticker (required) - consists of two parts: [SYMBOL_NAME].[EXCHANGE_ID]. Example: AAPL.US 475 | function (required) – the function that will be applied to data series to get technical indicator data. 476 | All possible values for function parameter: 477 | ['avgvol', 'avgvolccy', 'sma', 'ema', 'wma', 'volatility', 'stochastic', 478 | 'rsi', 'stddev', 'stochrsi', 'slope', 'dmi', 'adx', 'macd', 'atr', 479 | 'cci', 'sar', 'bbands', 'format_amibroker', 'splitadjusted'] 480 | Description for possible functions you get here: 481 | https://eodhistoricaldata.com/financial-apis/technical-indicators-api/ 482 | period - the number of data points used to calculate each moving average value. 483 | Valid range from 2 to 100000 with the default value - 50. 484 | date_from (not required) - date from with format Y-m-d. Example: 2000-01-01 485 | date_to (not required) - date from with format Y-m-d. Example: 2000-01-01 486 | order - use 'a' for ascending dates (from old to new) and 'd' for descending dates (from new to old). 487 | By default, dates are shown in ascending order. 488 | splitadjusted_only - default value is '0'. 489 | By default, we calculate data for some functions by closes adjusted with splits and dividends. 490 | If you need to calculate the data by closes adjusted only with splits, set this parameter to '1'. 491 | Works with the following functions: sma, ema, wma, volatility, rsi, slope, and macd. 492 | 493 | For some functions can be used additional parameters: 494 | 1. For splitadjusted: 495 | agg_period [optional] – aggregation period. Default value – 'd'. Possible values: d – daily, w – weekly, m – monthly. 496 | 2. For stochastic: 497 | fast_kperiod [optional] – Fast K-period, the default value is 14. Valid range from 2 to 100000. 498 | slow_kperiod [optional] – Slow K-period, the default value is 3. Valid range from 2 to 100000. 499 | slow_dperiod [optional] – Slow D-period, the default value is 3. Valid range from 2 to 100000. 500 | 3. For stochrsi: 501 | fast_kperiod [optional] – Fast K-period, the default value is 14. Valid range from 2 to 100000. 502 | fast_dperiod [optional] – Fast D-period, the default value is 14. Valid range from 2 to 100000. 503 | 4. For macd: 504 | fast_period [optional] – the default value is 12. Valid range from 2 to 100000. 505 | slow_period [optional] – the default value is 26. Valid range from 2 to 100000. 506 | signal_period [optional] – the default value is 9. Valid range from 2 to 100000. 507 | 5. For sar: 508 | acceleration [optional] – Acceleration Factor used up to the Maximum value. Default value – 0.02. 509 | maximum [optional] – Acceleration Factor Maximum value. Default value – 0.20. 510 | For those functions use this parameters to set periods. 511 | """ 512 | 513 | api_call = TechnicalIndicatorAPI() 514 | return api_call.get_technical_indicator_data( 515 | api_token=self._api_key, 516 | ticker=ticker, 517 | function=function, 518 | period=period, 519 | date_from=date_from, 520 | date_to=date_to, 521 | order=order, 522 | splitadjusted_only=splitadjusted_only, 523 | agg_period=agg_period, 524 | fast_kperiod=fast_kperiod, 525 | slow_kperiod=slow_kperiod, 526 | slow_dperiod=slow_dperiod, 527 | fast_dperiod=fast_dperiod, 528 | fast_period=fast_period, 529 | slow_period=slow_period, 530 | signal_period=signal_period, 531 | acceleration=acceleration, 532 | maximum=maximum 533 | ) 534 | 535 | def get_live_stock_prices(self, ticker, date_to=None, date_from=None, s=None) -> list: 536 | """Available args: 537 | ticker (required) - consists of two parts: [SYMBOL_NAME].[EXCHANGE_ID]. Example: AAPL.US 538 | s (not required) - add “s=” parameter to your function and you will be able to get data for multiple 539 | tickers at one request, all tickers should be separated with a comma. For example: 540 | api.get_live_stock_prices(ticker = "AAPL.US", s="VTI,EUR.FOREX") 541 | For more information visit: https://eodhistoricaldata.com/financial-apis/live-realtime-stocks-api/ 542 | """ 543 | 544 | api_call = LiveStockPricesAPI() 545 | return api_call.get_live_stock_prices(api_token=self._api_key, ticker=ticker, s=s) 546 | 547 | def get_economic_events_data( 548 | self, 549 | date_from: str = None, 550 | date_to: str = None, 551 | country: str = None, 552 | comparison: str = None, 553 | offset: int = None, 554 | limit: int = None, 555 | ) -> list: 556 | """Available args: 557 | date_from (not required) - date from with format Y-m-d. Example: 2000-01-01 558 | date_to (not required) - date from with format Y-m-d. Example: 2000-01-01 559 | country (not required) - The country code is in ISO 3166 format, has 2 symbols 560 | comparison (not required) - Possible values: mom, qoq, yoy 561 | offset (not required) - Possible values from 0 to 1000. Default value: 0 562 | limit (not required) - Possible values from 0 to 1000. Default value: 50. 563 | For more information visit: https://eodhistoricaldata.com/financial-apis/economic-events-data-api/ 564 | """ 565 | 566 | api_call = EconomicEventsDataAPI() 567 | return api_call.get_economic_events_data( 568 | api_token=self._api_key, 569 | date_from=date_from, 570 | date_to=date_to, 571 | country=country, 572 | comparison=comparison, 573 | offset=offset, 574 | limit=limit, 575 | ) 576 | 577 | def get_insider_transactions_data( 578 | self, 579 | date_from: str = None, 580 | date_to: str = None, 581 | code: str = None, 582 | limit: int = None, 583 | ) -> list: 584 | """Available args: 585 | date_from (not required) - date from with format Y-m-d. Example: 2000-01-01 586 | date_to (not required) - date from with format Y-m-d. Example: 2000-01-01 587 | code (not required) - to get the data only for Apple Inc (AAPL), use AAPL.US or AAPL ticker code. 588 | By default, all possible symbols will be displayed. 589 | limit (not required) - the limit for entries per result, from 1 to 1000. Default value: 100. 590 | For more information visit: https://eodhistoricaldata.com/financial-apis/insider-transactions-api/ 591 | """ 592 | 593 | api_call = InsiderTransactionsAPI() 594 | return api_call.get_insider_transactions_data( 595 | api_token=self._api_key, 596 | date_from=date_from, 597 | date_to=date_to, 598 | code=code, 599 | limit=limit, 600 | ) 601 | 602 | def get_fundamentals_data(self, ticker: str) -> list: 603 | """Available args: 604 | ticker (required) - consists of two parts: [SYMBOL_NAME].[EXCHANGE_ID]. Example: AAPL.US 605 | For more information visit: https://eodhistoricaldata.com/financial-apis/stock-etfs-fundamental-data-feeds/ 606 | """ 607 | 608 | api_call = FundamentalDataAPI() 609 | return api_call.get_fundamentals_data(api_token=self._api_key, ticker=ticker) 610 | 611 | def get_eod_splits_dividends_data(self, country="US", type=None, date=None, symbols=None, filter=None) -> list: 612 | """Available args: 613 | type (not required) - can get splits, empty or dividends. 614 | for splits function returns all splits for US stocks in bulk for a particular day 615 | for dividends function returns all dividends for US stocks in bulk for a particular day 616 | if type will remain empty then returns end-of-day data for US stocks in bulk for a particular day 617 | date (not required) - By default, the data for last trading day will be downloaded, but if you need any specific date 618 | you can add parameter 619 | symbols (not required) - To download last day data for several symbols, for example, 620 | for MSFT and AAPL, you can add the ‘symbols’ parameter. For non-US tickers, 621 | you should use the exchange code, for example, BMW.XETRA or SAP.F 622 | If you want get data for several codes you need to input in the next type of format: AAPL,BMW.XETRA,SAP.F 623 | For more information visit: https://eodhistoricaldata.com/financial-apis/bulk-api-eod-splits-dividends/ 624 | """ 625 | 626 | api_call = BulkEodSplitsDividendsDataAPI() 627 | return api_call.get_eod_splits_dividends_data( 628 | api_token=self._api_key, 629 | country=country, 630 | type=type, 631 | date=date, 632 | symbols=symbols, 633 | filter=filter, 634 | ) 635 | 636 | def get_upcoming_earnings_data(self, from_date=None, to_date=None, symbols=None) -> list: 637 | """Available args: 638 | from_date (not required) - Format: YYYY-MM-DD. The start date for earnings data, if not provided, today will be used. 639 | to_date (not required) - Format: YYYY-MM-DD. The end date for earnings data, if not provided, today + 7 days will be used. 640 | symbols (not required) - OPTIONAL. You can request specific symbols to get historical and upcoming data. 641 | If ‘symbols’ used, then ‘from’ and ‘to’ parameters will be ignored. 642 | You can use one symbol: ‘AAPL.US’ or several symbols separated by a comma: ‘AAPL.US, MS’ 643 | For more information visit: https://eodhistoricaldata.com/financial-apis/calendar-upcoming-earnings-ipos-and-splits/#Upcoming_Earnings_API 644 | """ 645 | 646 | api_call = UpcomgingEarningsAPI() 647 | return api_call.get_upcoming_earnings_data( 648 | api_token=self._api_key, 649 | from_date=from_date, 650 | to_date=to_date, 651 | symbols=symbols, 652 | ) 653 | 654 | def get_earning_trends_data(self, symbols) -> list: 655 | """Available args: 656 | symbols (required) - You can request specific symbols to get historical and upcoming data. 657 | f ‘symbols’ used, then ‘from’ and ‘to’ parameters will be ignored. 658 | ou can use one symbol: ‘AAPL.US’ or several symbols separated by a comma: ‘AAPL.US, MS’ 659 | For more information visit: https://eodhistoricaldata.com/financial-apis/calendar-upcoming-earnings-ipos-and-splits/#Earnings_Trends_API 660 | """ 661 | api_call = EarningTrendsAPI() 662 | return api_call.get_earning_trends_data(api_token=self._api_key, symbols=symbols) 663 | 664 | def get_upcoming_IPOs_data(self, from_date=None, to_date=None) -> list: 665 | """Available args: 666 | from_date (not required) - Format: YYYY-MM-DD. The start date for ipos data, if not provided, today will be used. 667 | to_date (not required) - Format: YYYY-MM-DD. The end date for ipos data, if not provided, today + 7 days will be used. 668 | For more information visit: https://eodhistoricaldata.com/financial-apis/calendar-upcoming-earnings-ipos-and-splits/#Upcoming_Earnings_API 669 | """ 670 | 671 | api_call = UpcomingIPOsAPI() 672 | return api_call.get_upcoming_IPOs_data(api_token=self._api_key, from_date=from_date, to_date=to_date) 673 | 674 | def get_upcoming_splits_data(self, from_date=None, to_date=None) -> list: 675 | """Available args: 676 | from_date (not required) - Format: YYYY-MM-DD. The start date for splits data, if not provided, today will be used. 677 | to_date (not required) - Format: YYYY-MM-DD. The end date for splits data, if not provided, today + 7 days will be used. 678 | For more information visit: https://eodhistoricaldata.com/financial-apis/calendar-upcoming-earnings-ipos-and-splits/#Upcoming_Earnings_API 679 | """ 680 | 681 | api_call = UpcomingSplitsAPI() 682 | return api_call.get_upcoming_splits_data(api_token=self._api_key, from_date=from_date, to_date=to_date) 683 | 684 | def get_macro_indicators_data(self, country, indicator=None) -> list: 685 | """Available args: 686 | country (required) - Defines the country for which the indicator will be shown. 687 | The country should be defined in the Alpha-3 ISO format. Possible values: USA, FRA, DEU… 688 | indicator (not required) - Defines which macroeconomics data indicator will be shown. 689 | See the list of possible indicators below. The default value is ‘gdp_current_usd‘. 690 | All possible indicators will be avaliable on: https://eodhistoricaldata.com/financial-apis/macroeconomics-data-and-macro-indicators-api/ 691 | """ 692 | 693 | api_call = MacroIndicatorsAPI() 694 | return api_call.get_macro_indicators_data(api_token=self._api_key, country=country, indicator=indicator) 695 | 696 | def get_bonds_fundamentals_data(self, isin=None) -> list: 697 | """Available args: 698 | isin - An International Securities Identification Number, in current function isin may be cusip-code. 699 | Other IDs are not supported at the moment. 700 | For more information visit: https://eodhistoricaldata.com/financial-apis/bonds-fundamentals-and-historical-api/ 701 | """ 702 | 703 | api_call = BondsFundamentalsAPI() 704 | return api_call.get_bonds_fundamentals_data(api_token=self._api_key, isin=isin) 705 | 706 | def get_list_of_exchanges(self): 707 | """Available args: 708 | Function returns list of avaliable exchanges 709 | """ 710 | 711 | api_call = ListOfExchangesAPI() 712 | return api_call.get_list_of_exchanges(api_token=self._api_key) 713 | 714 | def get_list_of_tickers(self, code, delisted=0): 715 | """Available args: 716 | delisted (not required) - by default, this API provides only tickers that were active at least a month ago, 717 | to get the list of inactive (delisted) tickers please use the parameter “delisted=1” 718 | code (required) - For US exchanges you can also get all US tickers, 719 | then you should use the ‘US’ exchange code and tickers only for the particular exchange, 720 | the list of possible US exchanges to request:'US', 'NYSE', 'NASDAQ', 'BATS', 'OTCQB', 'PINK', 'OTCQX', 721 | 'OTCMKTS', 'NMFQS', 'NYSE MKT', 'OTCBB', 'OTCGREY', 'BATS', 'OTC' 722 | For more information visit: https://eodhistoricaldata.com/financial-apis/exchanges-api-list-of-tickers-and-trading-hours/ 723 | """ 724 | 725 | api_call = ListOfExchangesAPI() 726 | return api_call.get_list_of_tickers(api_token=self._api_key, delisted=delisted, code=code) 727 | 728 | def get_details_trading_hours_stock_market_holidays(self, code, from_date=None, to_date=None): 729 | """Available args: 730 | Use the exchange code from the API endpoint. 731 | For more information visit: https://eodhistoricaldata.com/financial-apis/exchanges-api-trading-hours-and-stock-market-holidays/ 732 | """ 733 | 734 | api_call = TradingHours_StockMarketHolidays_SymbolsChangeHistoryAPI() 735 | return api_call.get_details_trading_hours_stock_market_holidays(api_token=self._api_key, code=code, from_date=from_date, to_date=to_date) 736 | 737 | def symbol_change_history(self, from_date=None, to_date=None): 738 | """Available args: 739 | from and to (not required) - the format is ‘YYYY-MM-DD’. 740 | If you need data from Jul 22, 2022, to Aug 10, 2022, you should use from=2022-07-22 and to=2022-08-10. 741 | For more information visit: https://eodhistoricaldata.com/financial-apis/exchanges-api-trading-hours-and-stock-market-holidays/ 742 | """ 743 | api_call = TradingHours_StockMarketHolidays_SymbolsChangeHistoryAPI() 744 | return api_call.symbol_change_history(api_token=self._api_key, from_date=from_date, to_date=to_date) 745 | 746 | def stock_market_screener(self, sort=None, filters=None, limit=None, signals=None, offset=None): 747 | """Available args: 748 | filters (not required) - Usage: filters=[[“field1”, “operation1”, value1],[“field2”, “operation2”, value2] , … ]. 749 | Filters out tickers by different fields. 750 | signals (not required) - Usage: signals=signal1,signal2,…,signalN. Filter out tickers by signals, the calculated fields. 751 | sort (not required) - Usage: sort=field_name.(asc|desc). Sorts all fields with type ‘Number’ in ascending/descending order. 752 | limit (not required) - The number of results should be returned with the query. 753 | Default value: 50, minimum value: 1, maximum value: 100. 754 | offset (not required) - The offset of the data. Default value: 0, minimum value: 0, maximum value: 1000. 755 | For example, to get 100 symbols starting from 200 you should use limit=100 and offset=200. 756 | """ 757 | 758 | api_call = StockMarketScreenerAPI() 759 | return api_call.stock_market_screener( 760 | api_token=self._api_key, 761 | filters=filters, 762 | limit=limit, 763 | signals=signals, 764 | offset=offset, 765 | sort=sort, 766 | ) 767 | 768 | def financial_news(self, s=None, t=None, from_date=None, to_date=None, limit=None, offset=None): 769 | """Available args: 770 | s (required if t empty) - The ticker code to get news for. 771 | t (required if s empty) - The tag to get news on a given topic. 772 | limit (not required) - The number of results should be returned with the query. 773 | Default value: 50, minimum value: 1, maximum value: 1000. 774 | offset (not required) - The offset of the data. Default value: 0, minimum value: 0. 775 | For example, to get 100 symbols starting from 200 you should use limit=100 and offset=200. 776 | from and to (not required) - the format is ‘YYYY-MM-DD’. 777 | If you need data from Mar 1, 2021, to Mar 10, 2021, you should use from=2021-03-01 and to=2021-03-10. 778 | """ 779 | api_call = FinancialNewsAPI() 780 | return api_call.financial_news( 781 | api_token=self._api_key, 782 | limit=limit, 783 | from_date=from_date, 784 | to_date=to_date, 785 | offset=offset, 786 | s=s, 787 | t=t, 788 | ) 789 | 790 | def get_options_data( 791 | self, 792 | ticker, 793 | date_to=None, 794 | date_from=None, 795 | trade_date_to=None, 796 | trade_date_from=None, 797 | contract_name=None, 798 | ): 799 | """ 800 | Stock options data for top US stocks from NYSE and NASDAQ, the data for Options starts from April 2018. 801 | Options data is updated daily; however, 802 | the API does not provide a history for options contracts prices or other related data. 803 | That means: for each contract, there is only the current price, bid/ask, etc. 804 | 805 | 1. IMPORTANT! For backward compatibility, you should use the from parameter with any value before the expiration date, 806 | the API recommends '2000-01-01'. 807 | 808 | 2. Note: option greeks and some additional value are available only for options with expiration date Feb 15, 2019, or later. 809 | 810 | Available args: 811 | ticker(string): Required - Could be any supported symbol. No default value. 812 | date_to(DateTime) and date_from(DateTime): Optional - the beginning and end of the desired dates. 813 | trade_date_from(DateTime): Optional - filters OPTIONS by lastTradeDateTime. Default value is blank. 814 | trade_date_to(DateTime): Optional - filters OPTIONS by lastTradeDateTime. Default value is blank. 815 | contract_name(string): Optional - Name of a particular contract. 816 | """ 817 | api_call = OptionsDataAPI() 818 | return api_call.get_options_data( 819 | api_token=self._api_key, 820 | ticker=ticker, 821 | date_to=date_to, 822 | date_from=date_from, 823 | trade_date_to=trade_date_to, 824 | trade_date_from=trade_date_from, 825 | contract_name=contract_name, 826 | ) 827 | 828 | def get_intraday_historical_data( 829 | self, 830 | symbol, 831 | interval='5m', 832 | from_unix_time=None, 833 | to_unix_time=None 834 | ): 835 | """ 836 | IMPORTANT: data for all exchanges is provided in the UTC timezone, with Unix timestamps. 837 | 838 | Available args: 839 | symbol(string): Required - consists of two parts: {SYMBOL_NAME}.{EXCHANGE_ID}, 840 | then you can use, for example, AAPL.MX for Mexican Stock Exchange. or AAPL.US for NASDAQ 841 | interval(string) Optional - the possible intervals: ‘5m’ for 5-minutes, ‘1h’ for 1 hour, and ‘1m’ for 1-minute intervals. 842 | from_unix_time(string) and to_unix_time(string): Optional - Parameters should be passed in UNIX time with UTC timezone, for example, 843 | these values are correct: “from=1627896900&to=1630575300” and correspond to 844 | ‘ 2021-08-02 09:35:00 ‘ and ‘ 2021-09-02 09:35:00 ‘. 845 | The maximum periods between ‘from’ and ‘to’ are 120 days for 1-minute intervals, 846 | 600 days for 5-minute intervals and 847 | 7200 days for 1-hour intervals 848 | (please note, especially with the 1-hour interval, this is the maximum theoretically possible length). 849 | Without ‘from’ and ‘to’ specified, the length of the data obtained is the last 120 days. 850 | 851 | List of supported exchanges: https://eodhd.com/financial-apis/exchanges-api-list-of-tickers-and-trading-hours/ 852 | For more information visit: https://eodhd.com/financial-apis/intraday-historical-data-api/ 853 | """ 854 | api_call = IntradayDataAPI() 855 | return api_call.get_intraday_historical_data( 856 | api_token=self._api_key, 857 | symbol=symbol, 858 | interval=interval, 859 | to_unix_time=to_unix_time, 860 | from_unix_time=from_unix_time 861 | ) 862 | 863 | def get_eod_historical_stock_market_data( 864 | self, 865 | symbol, 866 | period='d', 867 | from_date=None, 868 | to_date=None, 869 | order=None 870 | ): 871 | """ 872 | Available args: 873 | symbol(string): Required - consists of two parts: {SYMBOL_NAME}.{EXCHANGE_ID}, 874 | then you can use, for example, AAPL.MX for Mexican Stock Exchange. or AAPL.US for NASDAQ 875 | period(string) Optional - use 'd' for daily, 'w' for weekly, 'm' for monthly prices. By default, daily prices will be shown. 876 | from_date and to_date - the format is 'YYYY-MM-DD'. 877 | If you need data from Jan 5, 2017, to Feb 10, 2017, you should use from=2017-01-05 and to=2017-02-10. 878 | order(string) Optional - use ‘a’ for ascending dates (from old to new), ‘d’ for descending dates (from new to old). 879 | By default, dates are shown in ascending order. 880 | 881 | List of supported exchanges: https://eodhd.com/financial-apis/exchanges-api-list-of-tickers-and-trading-hours/ 882 | For more information visit: https://eodhd.com/financial-apis/api-for-historical-data-and-volumes/ 883 | """ 884 | api_call = EodHistoricalStockMarketDataAPI() 885 | return api_call.get_eod_historical_stock_market_data( 886 | api_token=self._api_key, 887 | symbol=symbol, 888 | period=period, 889 | to_date=to_date, 890 | from_date=from_date, 891 | order=order 892 | ) 893 | 894 | def get_stock_market_tick_data( 895 | self, 896 | symbol, 897 | from_timestamp, 898 | to_timestamp, 899 | limit=None 900 | ): 901 | """ 902 | Available args: 903 | symbol - for example, AAPL.US, consists of two parts: {SYMBOL_NAME}.{EXCHANGE_ID}. 904 | This API works only for US exchanges for the moment, 905 | then you can use 'AAPL' or 'AAPL.US' to get the data as well for other US tickers. 906 | from_timestamp and to_timestamp - use these parameters to filter data by datetime. 907 | Parameters should be passed in UNIX time with UTC timezone, 908 | for example, these values are correct: “from=1627896900&to=1630575300” and 909 | correspond to ' 2021-08-02 09:35:00 ' and ' 2021-09-02 09:35:00 '. 910 | limit - the maximum number of ticks will be provided. 911 | """ 912 | api_call = StockMarketTickDataAPI() 913 | return api_call.get_stock_market_tick_data( 914 | api_token=self._api_key, 915 | symbol=symbol, 916 | to_timestamp=to_timestamp, 917 | from_timestamp=from_timestamp, 918 | limit=limit 919 | ) 920 | 921 | def get_sentiment( 922 | self, 923 | s, 924 | from_date=None, 925 | to_date=None 926 | ): 927 | """ 928 | Available args: 929 | s [REQUIRED] - parameter to your URL and you will be able to get data for multiple tickers at one request, 930 | all tickers should be separated with a comma. 931 | from_date and to_date [NOT REQUIRED] - the format is ‘YYYY-MM-DD’. 932 | If you need data from Jan 5, 2022 to Feb 10, 2022, you should use from=2022-01-05 and to=2022-02-10. 933 | For more information visit: https://eodhd.com/financial-apis/sentimental-data-financial-api/ 934 | List of supported exchanges: https://eodhd.com/financial-apis/exchanges-api-list-of-tickers-and-trading-hours/ 935 | """ 936 | 937 | api_call = FinancialNewsAPI() 938 | return api_call.get_sentiment( 939 | api_token=self._api_key, 940 | s=s, 941 | from_date=from_date, 942 | to_date=to_date 943 | ) 944 | 945 | def get_historical_market_capitalization_data( 946 | self, 947 | ticker, 948 | from_date=None, 949 | to_date=None 950 | ): 951 | """ 952 | Available args: 953 | ticker [REQUIRED] - is the ticker code and it consists of two parts: {SYMBOL_NAME}.{EXCHANGE_ID}, 954 | you can use a US ticker code with or without the exchange part (AAPL or AAPL.US) 955 | from_date and to_date [NOT REQUIRED] - the format is ‘YYYY-MM-DD’. 956 | If you need data from Jan 5, 2022 to Feb 10, 2022, you should use from=2022-01-05 and to=2022-02-10. 957 | For more information visit: https://eodhd.com/financial-apis/historical-market-capitalization-api/ 958 | """ 959 | 960 | api_call = HistoricalMarketCapitalization() 961 | return api_call.get_historical_market_capitalization_data( 962 | api_token=self._api_key, 963 | ticker=ticker, 964 | from_date=from_date, 965 | to_date=to_date 966 | ) 967 | 968 | class ScannerClient: 969 | """Scanner class""" 970 | 971 | def __init__(self, api_key: str) -> None: 972 | # Validate API key 973 | prog = re_compile(r"^[A-z0-9.]{16,32}$") 974 | if api_key != "demo" and not prog.match(api_key): 975 | raise ValueError("API key is invalid") 976 | 977 | self.api = APIClient(api_key) 978 | 979 | def scan_markets( 980 | self, 981 | market_type: str = "CC", 982 | interval: str = Interval, 983 | quote_currency: str = "USD", 984 | request_limit: int = 5000, 985 | ): 986 | """Scan markets""" 987 | 988 | if request_limit < 0 or request_limit > 100000: 989 | raise ValueError("request limit is out of bounds!") 990 | 991 | df_dataset = pd.DataFrame() 992 | 993 | resp = self.api.get_exchange_symbols(market_type) 994 | symbol_list = resp[resp.Code.str.endswith(f"-{quote_currency}", na=False)].Code.to_numpy() 995 | 996 | if request_limit > 0: 997 | # truncate symbol list to request limit minus symbol list request 998 | symbol_list = symbol_list[0 : request_limit - 1] 999 | 1000 | for symbol in track(symbol_list, description="Processing..."): 1001 | df_data = self.api.get_historical_data(f"{symbol}.{market_type}", interval) 1002 | if len(df_data) >= 200: 1003 | df_data["sma50"] = df_data.adjusted_close.rolling(50, min_periods=1).mean() 1004 | df_data["sma200"] = df_data.adjusted_close.rolling(200, min_periods=1).mean() 1005 | df_data["bull_market"] = df_data.sma50 >= df_data.sma200 1006 | 1007 | df_data["ema12"] = df_data.adjusted_close.ewm(span=12, adjust=False).mean() 1008 | df_data["ema26"] = df_data.adjusted_close.ewm(span=26, adjust=False).mean() 1009 | df_data.loc[df_data["ema12"] > df_data["ema26"], "next_action"] = "sell" 1010 | df_data["next_action"].fillna("buy", inplace=True) 1011 | 1012 | high_low = df_data["high"] - df_data["low"] 1013 | high_close = abs(df_data["high"] - df_data["adjusted_close"].shift()) 1014 | low_close = abs(df_data["low"] - df_data["adjusted_close"].shift()) 1015 | ranges = pd.concat([high_low, high_close, low_close], axis=1) 1016 | true_range = np.max(ranges, axis=1) 1017 | df_data["atr14"] = true_range.rolling(14).sum() / 14 1018 | df_data["atr14_pcnt"] = (df_data["atr14"] / df_data["adjusted_close"] * 100).round(2) 1019 | 1020 | df_dataset = df_dataset.append(df_data.tail(1)) 1021 | 1022 | # drop infinite values 1023 | df_dataset.replace([np.inf, -np.inf], np.nan, inplace=True) 1024 | df_dataset.dropna(subset=["atr14_pcnt"], inplace=True) 1025 | 1026 | df_dataset.sort_values( 1027 | by=["bull_market", "next_action", "volume", "atr14_pcnt"], 1028 | ascending=[False, True, False, False], 1029 | inplace=True, 1030 | ) 1031 | df_dataset.to_csv("dataset.csv") 1032 | 1033 | return df_dataset 1034 | -------------------------------------------------------------------------------- /eodhd/eodhdgraphs.py: -------------------------------------------------------------------------------- 1 | """graphs.py""" 2 | 3 | from operator import truediv 4 | import pandas as pd 5 | import matplotlib.pyplot as plt 6 | 7 | 8 | class EODHDGraphs: 9 | """Graph class""" 10 | 11 | @staticmethod 12 | def fibonacci_extension(df: pd.DataFrame = None, trend_direction: str = "uptrend", price_field: str = "close", save_file: str = "", quiet: bool = False) -> None: 13 | """Fibonacci retracement""" 14 | 15 | if trend_direction not in ["uptrend", "downtrend"]: 16 | raise ValueError("trend_direction must be either uptrend or downtrend") 17 | 18 | if price_field not in ["close", "adjusted_close"]: 19 | raise ValueError("price_field must be either close or adjusted_close") 20 | 21 | low = df[price_field].min() 22 | high = df[price_field].max() 23 | diff = high - low 24 | 25 | if trend_direction == "uptrend": 26 | fib2618 = high + (diff * 2.618) 27 | fib2000 = high + (diff * 2) 28 | fib1618 = high + (diff * 1.618) 29 | fib1382 = high + (diff * 1.382) 30 | fib1000 = high + (diff * 1) 31 | fib618 = high + (diff * 0.618) 32 | elif trend_direction == "downtrend": 33 | fib2618 = low - (diff * 2.618) 34 | fib2000 = low - (diff * 2) 35 | fib1618 = low - (diff * 1.618) 36 | fib1382 = low - (diff * 1.382) 37 | fib1000 = low - (diff * 1) 38 | fib618 = low - (diff * 0.618) 39 | 40 | plt.figure(figsize=(30, 10)) 41 | plt.plot(df["adjusted_close"], color="black", label="Price") 42 | plt.axhline(y=fib2618, color="limegreen", linestyle="-", label="261.8%") 43 | plt.axhline(y=fib2000, color="slateblue", linestyle="-", label="200%") 44 | plt.axhline(y=fib1618, color="mediumvioletred", linestyle="-", label="161.8%") 45 | plt.axhline(y=fib1382, color="gold", linestyle="-", label="138.2%") 46 | plt.axhline(y=fib1000, color="dodgerblue", linestyle="-", label="100%") 47 | plt.axhline(y=fib618, color="darkturquoise", linestyle="-", label="61.8%") 48 | 49 | plt.ylabel("Price") 50 | plt.xticks(rotation=90) 51 | plt.title(f"Fibonacci Extension ({trend_direction})") 52 | plt.legend() 53 | 54 | if save_file: 55 | plt.savefig(save_file) 56 | 57 | if quiet is False: 58 | plt.show() 59 | 60 | @staticmethod 61 | def fibonacci_retracement(df: pd.DataFrame = None, trend_direction: str = "uptrend", price_field: str = "close", save_file: str = "", quiet: bool = False) -> None: 62 | """Fibonacci retracement""" 63 | 64 | if trend_direction not in ["uptrend", "downtrend"]: 65 | raise ValueError("trend_direction must be either uptrend or downtrend") 66 | 67 | if price_field not in ["close", "adjusted_close"]: 68 | raise ValueError("price_field must be either close or adjusted_close") 69 | 70 | low = df[price_field].min() 71 | high = df[price_field].max() 72 | diff = high - low 73 | 74 | if trend_direction == "uptrend": 75 | fib0 = high 76 | fib236 = high - (diff * 0.236) 77 | fib382 = high - (diff * 0.382) 78 | fib50 = high - (diff * 0.5) 79 | fib618 = high - (diff * 0.618) 80 | fib764 = high - (diff * 0.764) 81 | fib100 = low 82 | elif trend_direction == "downtrend": 83 | fib100 = high 84 | fib764 = low + (diff * 0.764) 85 | fib618 = low + (diff * 0.618) 86 | fib50 = low + (diff * 0.5) 87 | fib382 = low + (diff * 0.382) 88 | fib236 = low + (diff * 0.236) 89 | fib0 = low 90 | 91 | plt.figure(figsize=(30, 10)) 92 | plt.plot(df["adjusted_close"], color="black", label="Price") 93 | plt.axhline(y=fib100, color="limegreen", linestyle="-", label="100%") 94 | plt.axhline(y=fib764, color="slateblue", linestyle="-", label="76.4%") 95 | plt.axhline(y=fib618, color="mediumvioletred", linestyle="-", label="61.8%") 96 | plt.axhline(y=fib50, color="gold", linestyle="-", label="50%") 97 | plt.axhline(y=fib382, color="dodgerblue", linestyle="-", label="38.2%") 98 | plt.axhline(y=fib236, color="darkturquoise", linestyle="-", label="23.6%") 99 | plt.axhline(y=fib0, color="lightcoral", linestyle="-", label="0%") 100 | 101 | plt.ylabel("Price") 102 | plt.xticks(rotation=90) 103 | plt.title(f"Fibonacci Retracement ({trend_direction})") 104 | plt.legend() 105 | 106 | if save_file: 107 | plt.savefig(save_file) 108 | 109 | if quiet is False: 110 | plt.show() 111 | -------------------------------------------------------------------------------- /eodhd/websocketclient.py: -------------------------------------------------------------------------------- 1 | import websocket 2 | import threading 3 | import signal 4 | import time 5 | import json 6 | import re 7 | import pandas as pd 8 | 9 | pd.set_option('display.float_format', '{:.8f}'.format) 10 | 11 | 12 | class WebSocketClient: 13 | def __init__( 14 | self, 15 | api_key: str, 16 | endpoint: str, 17 | symbols: list, 18 | store_data: bool = False, 19 | display_stream: bool = False, 20 | display_candle_1m: bool = False, 21 | display_candle_5m: bool = False, 22 | display_candle_1h: bool = False, 23 | ) -> None: 24 | # Validate API key 25 | prog = re.compile(r"^[A-z0-9.]{16,32}$") 26 | if not prog.match(api_key): 27 | raise ValueError("API key is invalid") 28 | 29 | # Validate endpoint 30 | if endpoint not in ["us", "us-quote", "forex", "crypto"]: 31 | raise ValueError("Endpoint is invalid") 32 | 33 | # Validate symbol list 34 | if len(symbols) == 0: 35 | raise ValueError("No symbol(s) provided") 36 | 37 | # Validate individual symbols 38 | prog = re.compile(r"^[A-z0-9-$]{1,48}$") 39 | for symbol in symbols: 40 | if not prog.match(symbol): 41 | raise ValueError(f"Symbol is invalid: {symbol}") 42 | 43 | # Validate max symbol subscriptions 44 | if len(symbols) > 50: 45 | raise ValueError("Max symbol subscription count is 50!") 46 | 47 | # Map class arguments to private variables 48 | self._api_key = api_key 49 | self._endpoint = endpoint 50 | self._symbols = symbols 51 | self._store_data = store_data 52 | self._display_stream = display_stream 53 | self._display_candle_1m = display_candle_1m 54 | self._display_candle_5m = display_candle_5m 55 | self._display_candle_1h = display_candle_1h 56 | 57 | self.running = True 58 | self.message = None 59 | self.stop_event = threading.Event() 60 | self.data_list = [] 61 | self.ws = None 62 | 63 | # Register signal handlers 64 | signal.signal(signal.SIGINT, self._signal_handler) 65 | signal.signal(signal.SIGTERM, self._signal_handler) 66 | 67 | def _signal_handler(self, signum, frame): 68 | print("Stopping websocket...") 69 | self.running = False 70 | self.stop_event.set() 71 | self.thread.join() 72 | print("Websocket stopped.") 73 | 74 | def _floor_to_nearest_interval(self, timestamp_ms, interval): 75 | # Convert to seconds 76 | timestamp_s = timestamp_ms // 1000 77 | 78 | # Define the number of seconds for each interval 79 | seconds_per_minute = 60 80 | seconds_per_hour = seconds_per_minute * 60 81 | 82 | # Determine the number of seconds for the given interval 83 | if interval == "1 minute": 84 | interval_seconds = seconds_per_minute 85 | elif interval == "5 minutes": 86 | interval_seconds = 5 * seconds_per_minute 87 | elif interval == "1 hour": 88 | interval_seconds = seconds_per_hour 89 | else: 90 | raise ValueError(f"Unsupported interval: {interval}") 91 | 92 | # Floor to the nearest interval 93 | floored_s = (timestamp_s // interval_seconds) * interval_seconds 94 | 95 | # Convert back to milliseconds 96 | floored_ms = floored_s * 1000 97 | 98 | return floored_ms 99 | 100 | def _collect_data(self): 101 | self.ws = websocket.create_connection(f"wss://ws.eodhistoricaldata.com/ws/{self._endpoint}?api_token={self._api_key}") 102 | 103 | # Send the subscription message 104 | payload = { 105 | "action": "subscribe", 106 | "symbols": ",".join(self._symbols), 107 | } 108 | self.ws.send(json.dumps(payload)) 109 | 110 | candle_1m = {} 111 | candle_5m = {} 112 | candle_1h = {} 113 | 114 | # Collect data until the stop event is set 115 | while not self.stop_event.is_set(): 116 | self.message = self.ws.recv() 117 | message_json = json.loads(self.message) 118 | 119 | if self._store_data: 120 | self.data_list.append(self.message) 121 | 122 | if self._display_stream: 123 | print(self.message) 124 | 125 | if self._display_candle_1m: 126 | if "t" in message_json: 127 | candle_date = self._floor_to_nearest_interval(message_json["t"], "1 minute") 128 | 129 | if "t" in candle_1m and (candle_date != candle_1m["t"]): 130 | print(candle_1m) 131 | 132 | # New candle 133 | candle_1m = {} 134 | 135 | candle_1m["t"] = candle_date 136 | 137 | if "s" in message_json: 138 | candle_1m["m"] = message_json["s"] 139 | candle_1m["g"] = 60 140 | 141 | if "p" in message_json and "o" not in candle_1m: 142 | # Forming candle 143 | candle_1m["o"] = message_json["p"] 144 | candle_1m["h"] = message_json["p"] 145 | candle_1m["l"] = message_json["p"] 146 | candle_1m["c"] = message_json["p"] 147 | if "q" in message_json: 148 | candle_1m["v"] = float(message_json["q"]) 149 | elif "p" in message_json and "o" in candle_1m: 150 | # Update candle 151 | candle_1m["c"] = message_json["p"] 152 | 153 | if message_json["p"] > candle_1m["h"]: 154 | candle_1m["h"] = message_json["p"] 155 | 156 | if message_json["p"] < candle_1m["l"]: 157 | candle_1m["l"] = message_json["p"] 158 | 159 | # Sum volume 160 | candle_1m["v"] += float(message_json["q"]) 161 | 162 | # Uncomment this to see the candle forming 163 | # print(candle_1m) 164 | 165 | if self._display_candle_5m: 166 | if "t" in message_json: 167 | candle_date = self._floor_to_nearest_interval(message_json["t"], "5 minutes") 168 | 169 | if "t" in candle_5m and (candle_date != candle_5m["t"]): 170 | print(candle_5m) 171 | 172 | # New candle 173 | candle_5m = {} 174 | 175 | candle_5m["t"] = candle_date 176 | 177 | if "s" in message_json: 178 | candle_5m["m"] = message_json["s"] 179 | candle_5m["g"] = 60 180 | 181 | if "p" in message_json and "o" not in candle_5m: 182 | # Forming candle 183 | candle_5m["o"] = message_json["p"] 184 | candle_5m["h"] = message_json["p"] 185 | candle_5m["l"] = message_json["p"] 186 | candle_5m["c"] = message_json["p"] 187 | if "q" in message_json: 188 | candle_5m["v"] = float(message_json["q"]) 189 | elif "p" in message_json and "o" in candle_5m: 190 | # Update candle 191 | candle_5m["c"] = message_json["p"] 192 | 193 | if message_json["p"] > candle_5m["h"]: 194 | candle_5m["h"] = message_json["p"] 195 | 196 | if message_json["p"] < candle_5m["l"]: 197 | candle_5m["l"] = message_json["p"] 198 | 199 | # Sum volume 200 | candle_5m["v"] += float(message_json["q"]) 201 | 202 | # Uncomment this to see the candle forming 203 | # print(candle_5m) 204 | 205 | if self._display_candle_1h: 206 | if "t" in message_json: 207 | candle_date = self._floor_to_nearest_interval(message_json["t"], "1 hour") 208 | 209 | if "t" in candle_1h and (candle_date != candle_1h["t"]): 210 | print(candle_1h) 211 | 212 | # New candle 213 | candle_1h = {} 214 | 215 | candle_1h["t"] = candle_date 216 | 217 | if "s" in message_json: 218 | candle_1h["m"] = message_json["s"] 219 | candle_1h["g"] = 60 220 | 221 | if "p" in message_json and "o" not in candle_1h: 222 | # Forming candle 223 | candle_1h["o"] = message_json["p"] 224 | candle_1h["h"] = message_json["p"] 225 | candle_1h["l"] = message_json["p"] 226 | candle_1h["c"] = message_json["p"] 227 | if "q" in message_json: 228 | candle_1h["v"] = float(message_json["q"]) 229 | elif "p" in message_json and "o" in candle_1h: 230 | # Update candle 231 | candle_1h["c"] = message_json["p"] 232 | 233 | if message_json["p"] > candle_1h["h"]: 234 | candle_1h["h"] = message_json["p"] 235 | 236 | if message_json["p"] < candle_1h["l"]: 237 | candle_1h["l"] = message_json["p"] 238 | 239 | # Sum volume 240 | candle_1h["v"] += float(message_json["q"]) 241 | 242 | # Uncomment this to see the candle forming 243 | # print(candle_1h) 244 | 245 | # Close the WebSocket connection 246 | self.ws.close() 247 | 248 | def _keepalive(self, interval=30): 249 | if (self.ws is not None) and (hasattr(self.ws, "connected")): 250 | while self.ws.connected: 251 | self.ws.ping("keepalive") 252 | time.sleep(interval) 253 | 254 | def start(self): 255 | self.thread = threading.Thread(target=self._collect_data) 256 | self.keepalive = threading.Thread(target=self._keepalive) 257 | self.thread.start() 258 | self.keepalive.start() 259 | 260 | def stop(self): 261 | self.stop_event.set() 262 | self.thread.join() 263 | self.keepalive.join() 264 | 265 | def get_data(self): 266 | return self.data_list 267 | 268 | 269 | if __name__ == "__main__": 270 | client = WebSocketClient( 271 | api_key="OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX", 272 | endpoint="crypto", 273 | symbols=["BTC-USD"], 274 | display_stream=False, 275 | display_candle_1m=True, 276 | display_candle_5m=False, 277 | display_candle_1h=False, 278 | ) 279 | client.start() 280 | 281 | try: 282 | while client.running: 283 | time.sleep(1) 284 | except KeyboardInterrupt: 285 | print("\nStopping due to user request.") 286 | client.stop() 287 | -------------------------------------------------------------------------------- /example_api.py: -------------------------------------------------------------------------------- 1 | """API example""" 2 | 3 | import config as cfg 4 | from eodhd import APIClient 5 | 6 | 7 | def main() -> None: 8 | """Main""" 9 | 10 | api = APIClient(cfg.API_KEY) 11 | 12 | resp = api.get_exchanges() 13 | print(resp) 14 | # print(resp.dtypes) 15 | # print(resp.describe()) 16 | 17 | resp = api.get_exchange_symbols("US") 18 | print(resp) 19 | # print(resp.dtypes) 20 | # print(resp.describe()) 21 | 22 | resp = api.get_intraday_historical_data('BTC-USD.CC','1m') 23 | # resp = api.get_historical_data("BTC-USD.CC", "1m", "2021-11-27 23:56:00") 24 | # resp = api.get_historical_data("BTC-USD.CC", "1m", "2021-11-27 23:56:00", "2021-11-27 23:59:00") 25 | print(resp) 26 | # print(resp.dtypes) 27 | # print(resp.describe()) 28 | 29 | resp = api.get_historical_data("BTC-USD.CC", "5m") 30 | # resp = api.get_historical_data("BTC-USD.CC", "5m", "2021-11-27 23:55:00") 31 | # resp = api.get_historical_data("BTC-USD.CC", "5m", "2021-11-27 23:55:00", "2021-11-28 02:00:00") 32 | print(resp) 33 | # print(resp.dtypes) 34 | # print(resp.describe()) 35 | 36 | resp = api.get_historical_data("BTC-USD.CC", "1h") 37 | # resp = api.get_historical_data("BTC-USD.CC", "1h", "2021-11-27 23:00:00") 38 | # resp = api.get_historical_data("BTC-USD.CC", "1h", "2021-11-27 23:00:00", "2021-11-28 23:59:00") 39 | print(resp) 40 | # print(resp.dtypes) 41 | # print(resp.describe()) 42 | 43 | resp = api.get_historical_data("BTC-USD.CC", "d") 44 | # resp = api.get_historical_data("BTC-USD.CC", "d", "2021-11-24") 45 | # resp = api.get_historical_data("BTC-USD.CC", "d", "2021-11-24", "2021-11-27") 46 | print(resp) 47 | # print(resp.dtypes) 48 | # print(resp.describe()) 49 | 50 | resp = api.get_historical_data("BTC-USD.CC", "d", results=400) 51 | # resp = api.get_historical_data("BTC-USD.CC", "d", "2021-11-24") 52 | # resp = api.get_historical_data("BTC-USD.CC", "d", "2021-11-24", "2021-11-27") 53 | print(resp) 54 | # print(resp.dtypes) 55 | # print(resp.describe()) 56 | 57 | resp = api.get_details_trading_hours_stock_market_holidays(code = 'US', from_date = '2022-12-01', to_date = '2023-01-03') 58 | # resp = api.get_details_trading_hours_stock_market_holidays(code = 'US') 59 | print(resp) 60 | 61 | resp = api.get_bonds_fundamentals_data(isin = 'DE000CB83CF0') 62 | print(resp) 63 | 64 | resp = api.get_eod_splits_dividends_data(country = 'US', type = 'splits', date = '2010-09-21', symbols = 'MSFT', filter = 'extended') 65 | # resp = api.get_eod_splits_dividends_data(country = 'US', type = 'dividends', date = '2010-09-21', symbols = 'MSFT', filter = 'extended') 66 | print(resp) 67 | 68 | resp = api.get_earning_trends_data(symbols = 'AAPL.US') 69 | # resp = api.get_earning_trends_data(symbols = 'AAPL.US, MS') 70 | print(resp) 71 | 72 | resp = api.get_economic_events_data(date_from = '2020-01-05', date_to = '2020-02-10', country = 'AU', comparison = 'mom', 73 | offset = 50, limit = 50) 74 | # resp = api.get_economic_events_data(date_from = '2020-01-05', date_to = '2020-02-10', country = 'AU', comparison = 'qoq', offset = 50, limit = 50) 75 | # resp = api.get_economic_events_data(date_from = '2020-01-05', date_to = '2020-02-10', country = 'AU', comparison = 'yoy', offset = 50, limit = 50) 76 | print(resp) 77 | 78 | resp = api.financial_news(s = 'AAPL.US', t = None, from_date = '2020-01-05', to_date = '2020-02-10', limit = 100, offset = 200) 79 | # resp = api.financial_news(s = None, t = 'balance sheet', from_date = '2020-01-05', to_date = '2020-02-10', limit = 100, offset = 200) 80 | print(resp) 81 | 82 | resp = api.get_fundamentals_data(ticker = 'AAPL') 83 | print(resp) 84 | 85 | resp = api.get_historical_dividends_data(date_from = '2020-01-05', date_to = '2020-02-10', ticker = 'AAPL.US') 86 | print(resp) 87 | 88 | resp = api.get_historical_splits_data(date_from = '2020-01-05', date_to = '2020-02-10', ticker = 'AAPL.US') 89 | print(resp) 90 | 91 | resp = api.get_insider_transactions_data(date_from = '2020-01-05', date_to = '2020-02-10', code = 'AAPL', limit = 200) 92 | #resp = api.get_insider_transactions_data(date_from = '2020-01-05', date_to = '2020-02-10', code = 'AAPL.US', limit = 200) 93 | print(resp) 94 | 95 | resp = api.get_live_stock_prices(date_from = '2020-01-05', date_to = '2020-02-10', ticker = 'AAPL.US') 96 | print(resp) 97 | 98 | resp = api.get_macro_indicators_data(country = 'US', indicator = 'population_total') 99 | # resp = api.get_macro_indicators_data(country = 'US', indicator = 'consumer_price_index') 100 | print(resp) 101 | 102 | resp = api.stock_market_screener(sort = 'market_capitalization.desc', filters = '[["market_capitalization",">",1000], ["name","match","apple"], ["code","=","AAPL"],["exchange","=","us"],["sector","=","Technology"]]', limit = 10, signals = 'bookvalue_neg', offset = 0) 103 | print(resp) 104 | 105 | resp = api.get_upcoming_earnings_data(from_date = '2020-01-05', to_date = '2020-02-10', symbols = 'MSFT') 106 | print(resp) 107 | 108 | resp = api.get_upcoming_IPOs_data(from_date = '2020-01-05', to_date = '2020-02-10') 109 | print(resp) 110 | 111 | resp = api.get_upcoming_splits_data(from_date = '2020-01-05', to_date = '2020-02-10') 112 | print(resp) 113 | 114 | resp = api.get_technical_indicator_data(ticker = 'AAPL.US', function = 'avgvolccy', period = 100, date_from = '2020-01-05', date_to = '2020-02-10', 115 | order = 'a', splitadjusted_only = '0') 116 | print(resp) 117 | 118 | resp = api.get_list_of_exchanges() 119 | print(resp) 120 | 121 | resp = api.get_list_of_tickers(delisted = 1, code = 'US') 122 | print(resp) 123 | 124 | resp = api.symbol_change_history(from_date = '2020-01-05', to_date = '2020-02-10') 125 | print(resp) 126 | 127 | resp = api.get_intraday_historical_data(symbol = 'AAPL.MX', from_unix_time = '1627896900', to_unix_time = '1630575300', interval='1h') 128 | print(resp) 129 | 130 | resp = api.get_eod_historical_stock_market_data(symbol = 'AAPL.MX', period='d', from_date = '2023-01-01', to_date = '2023-01-15', order='a') 131 | print(resp) 132 | 133 | resp = api.get_stock_market_tick_data(from_timestamp = '1627896900', to_timestamp = '1630575300', symbol = 'AAPL', limit = 1) 134 | print(resp) 135 | 136 | resp = api.get_sentiment(s = 'btc-usd.cc', from_date = '2023-01-01', to_date = '2023-01-15') 137 | print(resp) 138 | 139 | resp = api.get_historical_market_capitalization_data(ticker = 'AAPL.US', from_date = '2023-01-01', to_date = '2023-01-15') 140 | print(resp) 141 | 142 | 143 | if __name__ == "__main__": 144 | main() 145 | -------------------------------------------------------------------------------- /example_graph.py: -------------------------------------------------------------------------------- 1 | """API example""" 2 | 3 | import config as cfg 4 | from eodhd import APIClient 5 | from eodhd import EODHDGraphs 6 | 7 | 8 | def main() -> None: 9 | """Main""" 10 | 11 | api = APIClient(cfg.API_KEY) 12 | graphs = EODHDGraphs() 13 | 14 | df = api.get_historical_data("GSPC.INDX", "1d") 15 | # graphs.fibonacci_extension(df, "uptrend", "adjusted_close", save_file="") 16 | # graphs.fibonacci_extension(df, "uptrend", "adjusted_close", save_file="", quiet=False) 17 | # graphs.fibonacci_retracement(df, "downtrend", "adjusted_close", save_file="fibonacci_retracement_downtrend.png", quiet=False) 18 | # graphs.fibonacci_retracement(df, "downtrend", "adjusted_close", save_file="fibonacci_retracement_uptrend.png", quiet=True) 19 | graphs.fibonacci_retracement(df, "downtrend", "adjusted_close", save_file="") 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /example_scanner.py: -------------------------------------------------------------------------------- 1 | """Scanner example""" 2 | 3 | import config as cfg 4 | from eodhd import ScannerClient 5 | 6 | 7 | def main() -> None: 8 | """Main""" 9 | 10 | scanner = ScannerClient(cfg.API_KEY) 11 | df_data = scanner.scan_markets("CC", "1d", "USD", 5000) 12 | print(df_data) 13 | 14 | 15 | if __name__ == "__main__": 16 | main() 17 | -------------------------------------------------------------------------------- /example_websockets.py: -------------------------------------------------------------------------------- 1 | """Websocket example""" 2 | 3 | import time 4 | from eodhd import WebSocketClient 5 | 6 | client = WebSocketClient( 7 | api_key="OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX", 8 | endpoint="crypto", 9 | symbols=["BTC-USD"], 10 | display_stream=False, 11 | display_candle_1m=True, 12 | display_candle_5m=False, 13 | display_candle_1h=False, 14 | ) 15 | client.start() 16 | 17 | try: 18 | while client.running: 19 | res = client.get_data() 20 | print(res) 21 | time.sleep(1) 22 | except KeyboardInterrupt: 23 | print("\nStopping due to user request.") 24 | client.stop() 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | black>=23.9.1 2 | matplotlib>=3.7.2 3 | numpy>=1.25.2 4 | pandas>=2.1.0 5 | pylint>=2.17.5 6 | pytest>=7.4.2 7 | pytest-runner>=6.0.0 8 | requests>=2.31.0 9 | rich>=13.5.2 10 | websocket-client>=1.6.3 11 | websockets>=11.0.3 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Setup file for PyPI""" 2 | 3 | # To use a consistent encoding 4 | from os import path 5 | 6 | # Always prefer setuptools over distutils 7 | from setuptools import setup, find_packages 8 | 9 | # The directory containing this file 10 | HERE = path.abspath(path.dirname(__file__)) 11 | 12 | # Get the long description from the README file 13 | with open(path.join(HERE, "README.md"), encoding="utf-8") as f: 14 | long_description = f.read() 15 | 16 | # This call to setup() does all the work 17 | setup( 18 | name="eodhd", 19 | version="1.0.31", 20 | description="Official EODHD API Python Library", 21 | long_description=long_description, 22 | long_description_content_type="text/markdown", 23 | url="https://whittle.medium.com", 24 | author="Michael Whittle", 25 | author_email="michael@lifecycle-ps.com", 26 | license="MIT", 27 | classifiers=[ 28 | "Intended Audience :: Developers", 29 | "License :: OSI Approved :: MIT License", 30 | "Programming Language :: Python :: 3", 31 | "Programming Language :: Python :: 3.11", 32 | "Operating System :: OS Independent", 33 | ], 34 | packages=find_packages(include=["eodhd"]) + ['eodhd.APIs'], 35 | include_package_data=True, 36 | install_requires=[ 37 | "websockets>=11.0.3", 38 | "websocket-client>=1.6.3", 39 | "requests>=2.31.0", 40 | "rich>=13.5.2", 41 | "pandas>=2.1.0", 42 | "numpy>=1.25.2", 43 | "matplotlib>=3.7.2", 44 | ], 45 | entry_points={ 46 | "console_scripts": [ 47 | "whittlem=eodhd.__main__:main", 48 | ] 49 | }, 50 | setup_requires=["pytest-runner==6.0.0"], 51 | tests_require=["pytest==7.4.2"], 52 | test_suite="tests", 53 | ) 54 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ __init__.py """ 2 | -------------------------------------------------------------------------------- /tests/test_websocketclient.py: -------------------------------------------------------------------------------- 1 | """Unit tests for WebSocketClient""" 2 | 3 | import pytest 4 | from eodhd import WebSocketClient 5 | 6 | 7 | def test_api_key_invalid(): 8 | """API key is invalid""" 9 | with pytest.raises(ValueError) as execinfo: 10 | websocket = WebSocketClient(api_key="", endpoint="", symbols=[]) 11 | assert isinstance(websocket, WebSocketClient) 12 | assert str(execinfo.value) == "API key is invalid" 13 | 14 | 15 | def test_endpoint_invalid(): 16 | """Endpoint is invalid""" 17 | with pytest.raises(ValueError) as execinfo: 18 | websocket = WebSocketClient( 19 | api_key="00000000000000000000000000000000", endpoint="", symbols=[] 20 | ) 21 | assert isinstance(websocket, WebSocketClient) 22 | assert str(execinfo.value) == "Endpoint is invalid" 23 | 24 | 25 | def test_symbols_empty(): 26 | """No symbol(s) provided""" 27 | with pytest.raises(ValueError) as execinfo: 28 | websocket = WebSocketClient( 29 | api_key="00000000000000000000000000000000", endpoint="crypto", symbols=[] 30 | ) 31 | assert isinstance(websocket, WebSocketClient) 32 | assert str(execinfo.value) == "No symbol(s) provided" 33 | 34 | 35 | def test_symbols_invalid(): 36 | """Symbol is invalid: !""" 37 | with pytest.raises(ValueError) as execinfo: 38 | websocket = WebSocketClient( 39 | api_key="00000000000000000000000000000000", endpoint="crypto", symbols=[""] 40 | ) 41 | assert isinstance(websocket, WebSocketClient) 42 | assert str(execinfo.value) == "Symbol is invalid: " 43 | 44 | 45 | def test_instantiate_success(): 46 | """Instantiate success""" 47 | websocket = WebSocketClient( 48 | api_key="00000000000000000000000000000000", endpoint="crypto", symbols=["BTC-USD"] 49 | ) 50 | assert isinstance(websocket, WebSocketClient) 51 | --------------------------------------------------------------------------------