├── config
├── __init__.py
└── config.py
├── tests
├── __init__.py
└── test_data.py
├── setup.cfg
├── requirements.txt
├── requirements-dev.txt
├── example.env
├── .idea
├── vcs.xml
├── misc.xml
├── inspectionProfiles
│ ├── profiles_settings.xml
│ └── Project_Default.xml
├── .gitignore
├── modules.xml
└── python-eodhistoricaldata.iml
├── eod_historical_data
├── __init__.py
├── version.py
├── _utils.py
└── data.py
├── SECURITY.md
├── ci
└── pypi-install.sh
├── .travis.yml
├── LICENSE
├── .gitignore
├── setup.py
├── CONTRIBUTING.md
└── README.md
/config/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal = 1
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas>=0.21
2 | requests>=2.3.0
3 | requests-cache>=0.5.0
4 | python-decouple
5 | aiohttp
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | coverage
2 | codecov
3 | coveralls
4 | flake8
5 | pytest
6 | pytest-cov
7 | wrapt
8 | python-decouple
9 |
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | #save this file as .env file and make sure the dependency python-decouple is included
2 |
3 | EOD_HISTORICAL_API_KEY="OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX"
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/eod_historical_data/__init__.py:
--------------------------------------------------------------------------------
1 | from .data import (set_envar, get_eod_data, get_dividends, # noqa
2 | get_exchange_symbols, get_exchanges, # noqa
3 | get_currencies, get_indexes) # noqa
4 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/eod_historical_data/version.py:
--------------------------------------------------------------------------------
1 | __author__ = "Femto Trader"
2 | __copyright__ = "Copyright 2017"
3 | __license__ = "MIT"
4 | __version__ = "0.0.1"
5 | __maintainer__ = "femtotrader"
6 | __email__ = ""
7 | __status__ = "Development"
8 | __url__ = "https://github.com/femtotrader/python-eodhistoricaldata"
9 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /../../../../../../:\present work documents\projects\opensource\python-eodhistoricaldata\.idea/dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | We support bug reports and security vulnerabilities reports in this codebase
4 |
5 | ## Reporting a Vulnerability
6 | if you find any vulnerability please add it to our issues and it will be resolved as soon as possible.
7 | or else send the bug reports to this email address mobiusndou@gmail.com patches will be developed ASAP.
8 |
--------------------------------------------------------------------------------
/config/config.py:
--------------------------------------------------------------------------------
1 | from decouple import config
2 | import os
3 | class Config:
4 | EOD_HISTORICAL_DATA_API_KEY_ENV_VAR: str = os.getenv('EOD_HISTORICAL_API_KEY') or config('EOD_HISTORICAL_API_KEY')
5 | EOD_HISTORICAL_DATA_API_KEY_DEFAULT: str = os.getenv('EOD_HISTORICAL_API_KEY') or config('EOD_HISTORICAL_API_KEY')
6 | EOD_HISTORICAL_DATA_API_URL: str = "https://eodhistoricaldata.com/api"
7 | DEBUG: bool = False
8 |
--------------------------------------------------------------------------------
/ci/pypi-install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | pip install pip --upgrade
4 | pip install numpy=="$NUMPY" lxml
5 | pip install -r requirements-dev.txt
6 |
7 | if [[ "$PANDAS" == "MASTER" ]]; then
8 | PRE_WHEELS="https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com"
9 | pip install --pre --upgrade --timeout=60 -f "$PRE_WHEELS" pandas
10 | else
11 | pip install pandas=="$PANDAS"
12 | fi
13 |
--------------------------------------------------------------------------------
/.idea/python-eodhistoricaldata.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: bionic
2 | language: python
3 |
4 | matrix:
5 | fast_finish: true
6 | include:
7 | - python: 3.6
8 | env: PANDAS=0.23 NUMPY=1.14
9 | - python: 3.7
10 | env: PANDAS=0.25 NUMPY=1.17
11 | allow_failures:
12 | - python: 3.7
13 | env: PANDAS="MASTER" NUMPY=1.17
14 |
15 | install:
16 | - source ci/pypi-install.sh;
17 | - pip list
18 | - python setup.py install
19 |
20 | script:
21 | - export EOD_HISTORICAL_API_KEY=$EOD_HISTORICAL_API_KEY
22 | - pytest -s --cov=eod_historical_data --cov-report xml:/tmp/cov-eod_historical_data.xml --junitxml=/tmp/eod_historical_data.xml
23 | - flake8 --version
24 | - flake8 eod_historical_data tests
25 |
26 | after_success:
27 | - coveralls
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017
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 |
--------------------------------------------------------------------------------
/.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 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | venv/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *.cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # dotenv
84 | .env
85 |
86 | # virtualenv
87 | .venv
88 | venv/
89 | ENV/
90 |
91 | # Spyder project settings
92 | .spyderproject
93 | .spyproject
94 |
95 | # Rope project settings
96 | .ropeproject
97 |
98 | # mkdocs documentation
99 | /site
100 |
101 | # mypy
102 | .mypy_cache/
103 |
104 | *.sqlite
105 | .DS_Store
106 |
107 | # Pycharm
108 | .idea
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from setuptools import setup, find_packages # Always prefer setuptools over distutils
5 | from os import path
6 | import io
7 |
8 | here = path.abspath(path.dirname(__file__))
9 |
10 | try:
11 | import pypandoc
12 | long_description = pypandoc.convert('README.md', 'rst')
13 | except(IOError, ImportError):
14 | print("Can't import pypandoc - using README.md without converting to RST")
15 | long_description = open('README.md').read()
16 |
17 | NAME = 'eod_historical_data'
18 | with io.open(path.join(here, NAME, 'version.py'), 'rt', encoding='UTF-8') as f:
19 | exec(f.read())
20 |
21 | install_requires = []
22 | with open("./requirements.txt") as f:
23 | install_requires = f.read().splitlines()
24 | with open("./requirements-dev.txt") as f:
25 | tests_require = f.read().splitlines()
26 |
27 | setup(
28 | name=NAME,
29 |
30 | # Versions should comply with PEP440. For a discussion on single-sourcing
31 | # the version across setup.py and the project code, see
32 | # https://packaging.python.org/en/latest/development.html#single-sourcing-the-version
33 | # version='0.0.2',
34 | version=__version__,
35 |
36 | description='End Of Day historical data using Python, Requests, Pandas',
37 | long_description=long_description,
38 |
39 | # The project's main homepage.
40 | url=__url__,
41 |
42 | # Author details
43 | author=__author__,
44 | author_email=__email__,
45 |
46 | # Choose your license
47 | license=__license__,
48 |
49 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
50 | classifiers=[
51 | 'Development Status :: 3 - Alpha',
52 | 'Environment :: Console',
53 | 'Intended Audience :: Developers',
54 | 'Intended Audience :: Science/Research',
55 | 'Operating System :: OS Independent',
56 | 'Intended Audience :: Financial and Insurance Industry',
57 | 'Programming Language :: Cython',
58 | 'Programming Language :: Python',
59 | 'Programming Language :: Python :: 3.5',
60 | 'Programming Language :: Python :: 3.6',
61 | 'Topic :: Scientific/Engineering',
62 | 'Topic :: Office/Business :: Financial',
63 | 'License :: OSI Approved :: MIT License',
64 | ],
65 |
66 | keywords='python trading data stock index',
67 | install_requires=install_requires,
68 | packages=find_packages(exclude=["contrib", "docs", "tests*"]),
69 | test_suite="tests",
70 | tests_require=tests_require,
71 | zip_safe=False,
72 | )
73 |
--------------------------------------------------------------------------------
/tests/test_data.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import sentinel
2 | import pandas as pd
3 | from eod_historical_data import (set_envar,
4 | get_eod_data,
5 | get_dividends,
6 | get_exchange_symbols,
7 | get_exchanges, get_currencies, get_indexes)
8 | import datetime
9 | import requests_cache
10 |
11 | pd.set_option("max_rows", 10)
12 |
13 | # Cache session (to avoid too much data consumption)
14 | expire_after = datetime.timedelta(days=1)
15 | session = requests_cache.CachedSession(cache_name='cache', backend='sqlite',
16 | expire_after=expire_after)
17 |
18 | # Get API key
19 | # from environment variable
20 | # bash> export EOD_HISTORICAL_API_KEY="YOURAPI"
21 | api_key = set_envar()
22 | # api_key = "YOURAPI"
23 |
24 |
25 | def test_get_eod_data_no_date():
26 | df = get_eod_data("AAPL", "US", api_key=api_key, session=session)
27 | print(df)
28 | # Note if df is Sentinel it means that the request was sent through but the response
29 | # was forbidden indicating that the APi Key may not have been authorized to perform the
30 | # Operation
31 | if df is not sentinel:
32 | assert df.index.name == "Date"
33 |
34 |
35 | def test_get_eod_data_with_date():
36 | df = get_eod_data("AAPL", "US", start="2020-02-01", end="2020-02-10",
37 | api_key=api_key, session=session)
38 | print(df)
39 | if df is not sentinel:
40 | assert df.index.name == "Date"
41 | assert df.index[0] != ""
42 |
43 |
44 | def test_get_dividends():
45 | df = get_dividends("AAPL", "US", start="2020-02-01", end="2020-02-10",
46 | api_key=api_key, session=session)
47 | print(df)
48 | if df is not sentinel:
49 | assert df.index.name == "Date"
50 |
51 |
52 | def test_get_exchange_symbols():
53 | df = get_exchange_symbols(exchange_code="US",
54 | api_key=api_key, session=session)
55 | print(df)
56 | if df is not sentinel:
57 | assert df.index.name == "Code"
58 | assert "AAPL" in df.index
59 |
60 |
61 | def test_get_exchanges():
62 | df = get_exchanges()
63 | print(df)
64 | if df is not sentinel:
65 | assert df.index.name == "ID"
66 | assert "US" in df["Exchange Code"].unique()
67 |
68 |
69 | def test_get_currencies():
70 | df = get_currencies()
71 | print(df)
72 | if df is not sentinel:
73 | assert df.index.name == "ID"
74 | assert "USD" in df["Currency Code"].unique()
75 |
76 |
77 | def test_get_indexes():
78 | df = get_indexes()
79 | print(df)
80 | if df is not sentinel:
81 | assert df.index.name == "ID"
82 | assert "GSPC" in df["Code"].unique()
83 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Python EOD Historical Data
2 |
3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
4 |
5 | The following is a set of guidelines for contributing to EOD Historical Data and its packages,
6 | which are hosted in the [EOD Orgnization](https://github.com/EodHistoricalData) on GitHub.
7 | These are mostly guidelines, not rules. Use your best judgment,
8 | and feel free to propose changes to this document in a pull request.
9 |
10 |
11 | ## Code of Conduct
12 |
13 | Members of this project and everyone who participates in it are expected to behave well, specifically
14 | not to use bad language when communicating with others, to be helpful and considerate.
15 |
16 | ## I don't want to read this whole thing I just have a question!!!
17 | **Note:** submit an issue to ask a question
18 |
19 | ## How can I Contribute
20 | #. Fork the repo, develop and test your code changes, add docs.
21 | #. Make sure that your commit messages clearly describe the changes.
22 | #. Send a pull request. (Please Read: `Faster Pull Request Reviews`_)
23 |
24 | ***************
25 | Adding Features
26 | ***************
27 |
28 | In order to add a feature:
29 |
30 | - The feature must be documented in both the API and narrative
31 | documentation.
32 |
33 | - The feature must work fully on the following Python versions:
34 | 3.7, 3.8, 3.9 and on both Linux and Windows.
35 |
36 | - The feature must not add unnecessary dependencies (where
37 | "unnecessary" is of course subjective, but new dependencies should
38 | be discussed).
39 |
40 | ****************************
41 | Using a Development Checkout
42 | ****************************
43 |
44 | You'll have to create a development environment using a Git checkout:
45 |
46 | - While logged into your GitHub account, navigate to the
47 | ``python-eodhistoricaldata`` `repo`_ on GitHub.
48 |
49 | - Fork and clone the ``python-eodhistoricaldata`` repository to your GitHub account by
50 | clicking the "Fork" button.
51 |
52 | - Clone your fork of ``python-eodhistoricaldata`` from your GitHub account to your local
53 | computer, substituting your account username and specifying the destination
54 | as ``python-eodhistoricaldata``. E.g.::
55 |
56 | $ cd ${HOME}
57 | $ git clone git@github.com:USERNAME/python-eodhistoricaldata.git python-eodhistoricaldata
58 | $ cd python-eodhistoricaldata
59 | # Configure remotes such that you can pull changes from the python-eodhistoricaldata
60 | # repository into your local repository.
61 | $ git remote add upstream git@github.com:EodHistoricalData/python-eodhistoricaldata
62 | # fetch and merge changes from upstream into master
63 | $ git fetch upstream
64 | $ git merge upstream/master
65 |
66 | Now your local repo is set up such that you will push changes to your GitHub
67 | repo, from which you can submit a pull request.
68 |
69 |
70 | ************
71 | Coding Style
72 | ************
73 |
74 | - PEP8 compliance
75 |
76 | ************
77 | Running code Tests
78 | ************
79 |
80 | use the py.test command on the root of your local repo
81 |
82 |
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | !! We recommend using our official library, which is regularly updated and comes with detailed documentation:
2 | https://github.com/EodHistoricalData/EODHD-APIs-Python-Financial-Library
3 |
4 |
5 |
6 | # Python EOD Historical Data
7 |
8 | A library to download data from EOD historical data https://eodhd.com/ using:
9 | - [Python](https://www.python.org/)
10 | - [Requests](http://docs.python-requests.org/) / [Requests-cache](http://requests-cache.readthedocs.io/)
11 | - [Pandas](http://pandas.pydata.org/)
12 |
13 | ## Installation
14 |
15 | ### Install the latest development version
16 |
17 | ```bash
18 | $ pip install git+https://github.com/femtotrader/python-eodhistoricaldata.git
19 | ```
20 |
21 | or
22 |
23 | ```bash
24 | $ git clone https://github.com/femtotrader/python-eodhistoricaldata.git
25 | $ python setup.py install
26 | ```
27 |
28 | ## Usage
29 |
30 | Environment variable `EOD_HISTORICAL_API_KEY` should be defined using:
31 |
32 | ```bash
33 | export EOD_HISTORICAL_API_KEY="YOUR_API"
34 |
35 | or setup and .env file and write your api key
36 | like EOD_HISTORICAL_API_KEY="YOUR_API"
37 | see exampl.env
38 | ```
39 |
40 |
41 | You can download data simply using
42 |
43 | ```python
44 | In [1]: import pandas as pd
45 | In [2]: pd.set_option("max_rows", 10)
46 | In [3]: from eod_historical_data import get_eod_data
47 | In [4]: df = get_eod_data("AAPL", "US")
48 | In [5]: df
49 | Out[1]:
50 | Open High Low Close Adjusted_close \
51 | Date
52 | 2000-01-03 3.7455 4.0179 3.6317 3.9978 3.9978
53 | 2000-01-04 3.8661 3.9509 3.6138 3.6607 3.6607
54 | 2000-01-05 3.7054 3.9487 3.6786 3.7143 3.7143
55 | 2000-01-06 3.7902 3.8214 3.3929 3.3929 3.3929
56 | 2000-01-07 3.4464 3.6071 3.4107 3.5536 3.5536
57 | ... ... ... ... ... ...
58 | 2017-05-26 154.0000 154.2400 153.3100 153.6100 153.6100
59 | 2017-05-30 153.4200 154.4300 153.3300 153.6700 153.6700
60 | 2017-05-31 153.9700 154.1700 152.3800 152.7600 152.7600
61 | 2017-06-01 153.1700 153.3300 152.2200 153.1800 153.1800
62 | 2017-06-02 153.6000 155.4500 152.8900 155.4500 155.4500
63 |
64 | Volume
65 | Date
66 | 2000-01-03 133949200.0
67 | 2000-01-04 128094400.0
68 | 2000-01-05 194580400.0
69 | 2000-01-06 191993200.0
70 | 2000-01-07 115183600.0
71 | ... ...
72 | 2017-05-26 21927600.0
73 | 2017-05-30 20126900.0
74 | 2017-05-31 24451200.0
75 | 2017-06-01 16274200.0
76 | 2017-06-02 25163841.0
77 |
78 | [4382 rows x 6 columns]
79 | ```
80 |
81 | but if you want to avoid too much data consumption, you can use a cache mechanism.
82 |
83 |
84 | ```python
85 | In [1]: import datetime
86 | In [2]: import requests_cache
87 | In [3]: expire_after = datetime.timedelta(days=1)
88 | In [4]: session = requests_cache.CachedSession(cache_name='cache', backend='sqlite', expire_after=expire_after)
89 | In [5]: df = get_eod_data("AAPL", "US", session=session)
90 | ```
91 |
92 | See [tests directory](https://github.com/femtotrader/python-eodhistoricaldata/tree/master/tests) for example of other API endpoints.
93 |
94 | ## Credits
95 |
96 | - Original version by [FemtoTrader](https://github.com/femtotrader/) 2017-2021
97 | - Idea to create this project [came from this issue](https://github.com/pydata/pandas-datareader/issues/315) (Thanks [@deios0](https://github.com/deios0) )
98 | - Code was inspired by [pandas-datareader](http://pandas-datareader.readthedocs.io/)
99 |
100 |
--------------------------------------------------------------------------------
/eod_historical_data/_utils.py:
--------------------------------------------------------------------------------
1 | from sqlite3 import Timestamp
2 | from typing import Optional, Union, Dict, Tuple, Callable
3 | import functools
4 | import requests
5 | from datetime import date, datetime
6 | import traceback
7 | import pandas as pd
8 | import pandas.api.types as _types
9 | from urllib.parse import urlencode
10 | from requests.exceptions import RetryError, ConnectTimeout
11 | from config.config import Config
12 |
13 | config_instance: Config = Config()
14 | # NOTE do not remove
15 |
16 |
17 | Sanitize_Type = Tuple[Union[Timestamp, datetime], Union[Timestamp, datetime]]
18 | Handle_Request_Type = Callable[..., Optional[pd.DataFrame]]
19 |
20 |
21 | def _init_session(session: Optional[requests.Session]) -> requests.Session:
22 | """
23 | Returns a requests.Session (or CachedSession)
24 | """
25 | return requests.Session() if session is None else session
26 |
27 |
28 | def _url(url: str, params: Dict[str, str]) -> str:
29 | """
30 | Returns long url with parameters
31 | https://mydomain.com?param1=...¶m2=...
32 | """
33 | return "{}?{}".format(url, urlencode(params)) if isinstance(params, dict) and len(params) > 0 else url
34 |
35 |
36 | def _format_date(dt: Optional[datetime]) -> Optional[str]:
37 | """
38 | Returns formatted date
39 | """
40 | return None if dt is None else dt.strftime("%Y-%m-%d")
41 |
42 |
43 | def _sanitize_dates(start: Union[int, date, datetime], end: Union[int, date, datetime]) -> Sanitize_Type:
44 | """
45 | Return (datetime_start, datetime_end) tuple
46 | """
47 | if start and end:
48 | if start > end:
49 | raise ValueError("end must be after start")
50 | else:
51 | raise ValueError("start and or end must contain valid int. date or datetime object")
52 |
53 | start = datetime(start, 1, 1) if _types.is_number(start) else pd.to_datetime(start)
54 | end = datetime(end, 1, 1) if _types.is_number(end) else pd.to_datetime(end)
55 |
56 | return start, end
57 |
58 |
59 | def _handle_request_errors(func: Handle_Request_Type) -> Optional[Handle_Request_Type]:
60 | @functools.wraps(func)
61 | def wrapper(*args, **kwargs):
62 | try:
63 | return func(*args, **kwargs)
64 | except ConnectionError:
65 | if config_instance.DEBUG is True:
66 | print(traceback.format_exc())
67 | else:
68 | print("Connection Error")
69 | return None
70 | except RetryError:
71 | if config_instance.DEBUG is True:
72 | print(traceback.format_exc())
73 | else:
74 | print("Connection Error")
75 | return None
76 | except ConnectTimeout:
77 | if config_instance.DEBUG is True:
78 | print(traceback.format_exc())
79 | else:
80 | print("Connection Error")
81 | return None
82 |
83 | return wrapper
84 |
85 |
86 | def _handle_environ_error(func: Handle_Request_Type) -> Optional[Handle_Request_Type]:
87 | @functools.wraps(func)
88 | def wrapper(*args, **kwargs):
89 | try:
90 | api_key: Optional[str] = kwargs.get('api_key')
91 | assert api_key is not None
92 | assert api_key != ""
93 | return func(*args, **kwargs)
94 | except AssertionError:
95 | raise EnvironNotSet("Environment not set, see readme.md on how to setup your environment variables")
96 |
97 | return wrapper
98 |
99 |
100 | # Errors
101 |
102 | class RemoteDataError(IOError):
103 | """
104 | Remote data exception
105 | """
106 | pass
107 |
108 |
109 | class EnvironNotSet(Exception):
110 | """
111 | raised when environment variables are not set
112 | """
113 | pass
114 |
115 |
116 | api_key_not_authorized: int = 403
117 |
--------------------------------------------------------------------------------
/eod_historical_data/data.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime, date
2 | from io import StringIO
3 | from typing import Union, Optional, Dict, Tuple
4 | from unittest.mock import sentinel
5 |
6 | import aiohttp as io
7 | import pandas as pd
8 | import requests
9 |
10 | from config.config import Config
11 | from ._utils import (_init_session, _format_date,
12 | _sanitize_dates, _url, RemoteDataError, _handle_request_errors, _handle_environ_error,
13 | api_key_not_authorized)
14 |
15 | config_data: Config = Config()
16 |
17 | EOD_HISTORICAL_DATA_API_KEY_ENV_VAR: str = config_data.EOD_HISTORICAL_DATA_API_KEY_ENV_VAR
18 | EOD_HISTORICAL_DATA_API_KEY_DEFAULT: str = config_data.EOD_HISTORICAL_DATA_API_KEY_DEFAULT
19 | EOD_HISTORICAL_DATA_API_URL: str = config_data.EOD_HISTORICAL_DATA_API_URL
20 |
21 | Start_END_Type = Optional[Union[str, int, date, datetime]]
22 |
23 |
24 | def set_envar() -> str:
25 | return EOD_HISTORICAL_DATA_API_KEY_ENV_VAR
26 |
27 |
28 | def _api_key_not_authorized_message():
29 | print(f"API Key Restricted, Try upgrading your API Key: {__name__}")
30 | return sentinel
31 |
32 |
33 | def _create_params(symbol: str, exchange: str, start: Start_END_Type,
34 | end: Start_END_Type, api_key: str) -> Tuple[str, Dict[str, str]]:
35 | """
36 | **create_params**
37 | will create parameters to pass into request session
38 | """
39 | symbol_exchange: str = f"{symbol}.{exchange}"
40 | # Takes date, datetime, str , or int and returns a valid date objects or TimeStamps
41 | start, end = _sanitize_dates(start, end)
42 | endpoint: str = f"/eod/{symbol_exchange}"
43 | url: str = EOD_HISTORICAL_DATA_API_URL + endpoint
44 | params: dict = {
45 | "api_token": api_key,
46 | "from": _format_date(start),
47 | "to": _format_date(end)
48 | }
49 | return url, params
50 |
51 |
52 | def _create_request(api_key: str, end: Start_END_Type, start: Start_END_Type, exchange: str, symbol: str,
53 | session: requests.Session) -> Tuple[Dict[str, str], requests.Response, str]:
54 | """
55 | **_create_request**
56 | will create a request using request library to eod endpoint to fetch data
57 | """
58 | url, params = _create_params(symbol=symbol, exchange=exchange, start=start, end=end, api_key=api_key)
59 | r: requests.Response = session.get(url, params=params)
60 | if config_data.DEBUG:
61 | print(f"url = {url} params = {params}")
62 | print(f'status code : {r.status_code}')
63 | return params, r, url
64 |
65 |
66 | @_handle_environ_error
67 | @_handle_request_errors
68 | def get_eod_data(symbol: str, exchange: str, start: Start_END_Type = None, end: Start_END_Type = None,
69 | api_key: str = EOD_HISTORICAL_DATA_API_KEY_DEFAULT,
70 | session: Optional[requests.Session] = None) -> Optional[pd.DataFrame]:
71 | """
72 | **get_eod_data**
73 |
74 | PARAMETERS
75 | symbol: (str) -> Ticker Symbol
76 | exchange: (str) -> Exchange Code
77 | start: (str, int, date, datetime) -> start date or integer representing Year
78 | end: (str,int, date, datetime) -> end date or integer representing Year
79 | api_key: (str) -> EOD Historical API Key
80 | session: (str) -> Request Session Object
81 |
82 | EXCEPTIONS:
83 | RemoteDataError -> will be raised if data cannot be returned from EOD for any reason
84 | ValueError -> Will be raised if either start or end do not contain Valid Data
85 |
86 | returns -> DataFrame containing End oF Day Data for the Symbol
87 | """
88 |
89 | params, r, url = _create_request(api_key=api_key, end=end, start=start,
90 | exchange=exchange, session=session, symbol=symbol)
91 |
92 | if r.status_code == requests.codes.ok:
93 | # NOTE engine='c' which is default does not support skip footer
94 | df: Optional[pd.DataFrame] = pd.read_csv(StringIO(r.text), engine='python',
95 | skipfooter=0, parse_dates=[0], index_col=0)
96 | return df
97 | elif r.status_code == api_key_not_authorized:
98 | _api_key_not_authorized_message()
99 | else:
100 | # replacing api token so it does not show in debug messages
101 | params["api_token"] = "API TOKEN IS SECRET"
102 | raise RemoteDataError(r.status_code, r.reason, _url(url, params))
103 |
104 |
105 | @_handle_environ_error
106 | @_handle_request_errors
107 | async def get_eod_data_async(symbol: str, exchange: str, start: Start_END_Type = None,
108 | end: Start_END_Type = None,
109 | api_key: str = EOD_HISTORICAL_DATA_API_KEY_DEFAULT) -> \
110 | Optional[Union[pd.TextFileReader, pd.DataFrame]]:
111 | """
112 | **get_eod_data_async**
113 |
114 | PARAMETERS
115 | symbol: (str) -> Ticker Symbol
116 | exchange: (str) -> Exchange Code
117 | start: (str, int, date, datetime) -> start date or integer representing Year
118 | end: (str,int, date, datetime) -> end date or integer representing Year
119 | api_key: (str) -> EOD Historical API Key
120 | session: (str) -> Request Session Object
121 |
122 | EXCEPTIONS:
123 | RemoteDataError -> will be raised if data cannot be returned from EOD for any reason
124 | ValueError -> Will be raised if either start or end do not contain Valid Data
125 |
126 | returns -> DataFrame containing End oF Day Data for the Symbol
127 | """
128 |
129 | url, params = _create_params(symbol=symbol, exchange=exchange, start=start, end=end, api_key=api_key)
130 |
131 | async with io.ClientSession() as session:
132 | async with session.get(url, params=params) as response:
133 | if response.status == 200:
134 | response_data = await response.text()
135 | df: Union[pd.TextFileReader, pd.DataFrame] = pd.read_csv(StringIO(response_data), engine='python',
136 | skipfooter=0,
137 | parse_dates=[0], index_col=0)
138 | return df
139 | elif response.status == api_key_not_authorized:
140 | _api_key_not_authorized_message()
141 | else:
142 | # replacing api token so it does not show in debug messages
143 | params["api_token"] = "API TOKEN IS SECRET"
144 | raise RemoteDataError(response.status, response.reason, _url(url, params))
145 |
146 |
147 | @_handle_environ_error
148 | @_handle_request_errors
149 | def get_dividends(symbol: str, exchange: str, start: Start_END_Type = None, end: Start_END_Type = None,
150 | api_key: str = EOD_HISTORICAL_DATA_API_KEY_DEFAULT,
151 | session: Optional[requests.Session] = None) -> Optional[Union[pd.TextFileReader, pd.DataFrame]]:
152 | """
153 | **get_dividends**
154 |
155 | """
156 | params, r, url = _create_request(api_key=api_key, end=end, start=start,
157 | exchange=exchange, session=session, symbol=symbol)
158 |
159 | if r.status_code == requests.codes.ok:
160 | # NOTE engine='c' which is default does not support skip footer
161 | df: Union[pd.TextFileReader, pd.DataFrame] = pd.read_csv(StringIO(r.text), engine='python',
162 | skipfooter=0,
163 | parse_dates=[0], index_col=0)
164 | assert len(df.columns) == 1
165 | ts = df["Dividends"]
166 | return ts
167 | elif r.status_code == api_key_not_authorized:
168 | _api_key_not_authorized_message()
169 | else:
170 | params["api_token"] = "API TOKEN IS SECRET"
171 | raise RemoteDataError(r.status_code, r.reason, _url(url, params))
172 |
173 |
174 | @_handle_environ_error
175 | @_handle_request_errors
176 | async def get_dividends_async(symbol: str, exchange: str, start: Union[str, int] = None,
177 | end: Union[str, int] = None,
178 | api_key: str = EOD_HISTORICAL_DATA_API_KEY_DEFAULT) -> Optional[pd.DataFrame]:
179 | """
180 | Returns dividends
181 | """
182 | url, params = _create_params(symbol=symbol, exchange=exchange, start=start, end=end, api_key=api_key)
183 |
184 | async with io.ClientSession() as session:
185 | async with session.get(url, params=params) as response:
186 | if response.status == 200:
187 | response_data = await response.text()
188 | df: Union[pd.TextFileReader, pd.DataFrame] = pd.read_csv(StringIO(response_data), engine='python',
189 | skipfooter=0,
190 | parse_dates=[0], index_col=0)
191 | assert len(df.columns) == 1
192 | ts = df["Dividends"]
193 | return ts
194 | elif response.status == api_key_not_authorized:
195 | _api_key_not_authorized_message()
196 | else:
197 | params["api_token"] = "API TOKEN IS SECRET"
198 | raise RemoteDataError(response.status, response.reason, _url(url, params))
199 |
200 |
201 | @_handle_environ_error
202 | @_handle_request_errors
203 | def get_exchange_symbols(exchange_code: str,
204 | api_key: str = EOD_HISTORICAL_DATA_API_KEY_DEFAULT,
205 | session: Union[requests.Session, None] = None) -> Optional[pd.DataFrame]:
206 | """
207 | Returns list of symbols for a given exchange
208 | """
209 | session: requests.Session = _init_session(session)
210 | endpoint: str = f"/exchanges/{exchange_code}"
211 | url: str = EOD_HISTORICAL_DATA_API_URL + endpoint
212 | params: dict = {"api_token": api_key}
213 |
214 | r: requests.Response = session.get(url, params=params)
215 | if config_data.DEBUG:
216 | print(f'status code : {r.status_code}')
217 | if r.status_code == requests.codes.ok:
218 | df: Union[pd.TextFileReader, pd.DataFrame] = pd.read_csv(StringIO(r.text), engine='python', skipfooter=0,
219 | index_col=0)
220 | return df
221 | elif r.status_code == api_key_not_authorized:
222 | _api_key_not_authorized_message()
223 | else:
224 | # replacing api token so it does not show in debug messages
225 | params["api_token"] = "API TOKEN IS SECRET"
226 | raise RemoteDataError(r.status_code, r.reason, _url(url, params))
227 |
228 |
229 | @_handle_environ_error
230 | @_handle_request_errors
231 | async def get_exchange_symbols_async(exchange_code: str,
232 | api_key: str = EOD_HISTORICAL_DATA_API_KEY_DEFAULT) -> Optional[pd.DataFrame]:
233 | """
234 | Returns list of symbols for a given exchange
235 | """
236 | endpoint: str = f"/exchanges/{exchange_code}"
237 | url: str = EOD_HISTORICAL_DATA_API_URL + endpoint
238 | params: dict = {
239 | "api_token": api_key
240 | }
241 | async with io.ClientSession() as session:
242 | async with session.get(url, params=params) as response:
243 | if response.status == 200:
244 | response_data = await response.text()
245 | df: Union[pd.TextFileReader, pd.DataFrame] = pd.read_csv(StringIO(response_data), engine='python',
246 | skipfooter=0, index_col=0)
247 | return df
248 | elif response.status == api_key_not_authorized:
249 | _api_key_not_authorized_message()
250 | else:
251 | # replacing api token so it does not show in debug messages
252 | params["api_token"] = "API TOKEN IS SECRET"
253 | raise RemoteDataError(response.status, response.reason, _url(url, params))
254 |
255 |
256 | def get_exchanges() -> pd.DataFrame:
257 | """
258 | Returns list of exchanges
259 | https://eodhistoricaldata.com/knowledgebase/list-supported-exchanges/
260 | """
261 | data: str = """
262 | ID Exchange Name Exchange Code
263 | 1 Munich Exchange MU
264 | 2 Berlin Exchange BE
265 | 3 Frankfurt Exchange F
266 | 4 Stuttgart Exchange STU
267 | 5 Mexican Exchange MX
268 | 6 Hanover Exchange HA
269 | 8 Australian Exchange AU
270 | 9 Singapore Exchange SG
271 | 10 Indexes INDX
272 | 11 USA Stocks US
273 | 12 Kuala Lumpur Exchange KLSE
274 | 13 Funds FUND
275 | 14 Bombay Exchange BSE
276 | 15 Dusseldorf Exchange DU
277 | 16 London Exchange LSE
278 | 17 Euronext Paris PA
279 | 18 XETRA Exchange XETRA
280 | 19 NSE (India) NSE
281 | 20 Hong Kong Exchange HK
282 | 21 Borsa Italiana MI
283 | 22 SIX Swiss Exchange SW
284 | 23 Hamburg Exchange HM
285 | 24 Toronto Exchange TO
286 | 25 Stockholm Exchange ST
287 | 26 Oslo Stock Exchange OL
288 | 27 Euronext Amsterdam AS
289 | 28 Coppenhagen Exchange CO
290 | 29 Euronext Lisbon LS
291 | 30 Korea Stock Exchange KO
292 | 31 Shanghai Exchange SS
293 | 32 Taiwan Exchange TW
294 | 33 Sao Paolo Exchange SA
295 | 34 Euronext Brussels BR
296 | 35 Madrid Exchange MC
297 | 36 Vienna Exchange VI
298 | 37 New Zealand Exchange NZ
299 | 38 FOREX FX
300 | 39 London IL IL
301 | 40 Irish Exchange IR
302 | 41 MICEX Russia MCX
303 | 42 OTC Market OTC
304 | 43 ETF-Euronext NX
305 | 44 Johannesburg Exchange JSE"""
306 | df: Union[pd.TextFileReader, pd.DataFrame] = pd.read_csv(StringIO(data), sep="\t")
307 | df: pd.DataFrame = df.set_index("ID")
308 | return df
309 |
310 |
311 | def get_currencies() -> pd.DataFrame:
312 | """
313 | Returns list of supported currencies
314 | https://eodhistoricaldata.com/knowledgebase/list-supported-currencies/
315 | """
316 | data: str = """ID Exchange Code Currency Code
317 | 1 FX USD
318 | 2 FX EUR
319 | 3 FX RUB
320 | 4 FX GBP
321 | 5 FX CNY
322 | 6 FX JPY
323 | 7 FX SGD
324 | 8 FX INR
325 | 9 FX CHF
326 | 10 FX AUD
327 | 11 FX CAD
328 | 12 FX HKD
329 | 13 FX MYR
330 | 14 FX NOK
331 | 15 FX NZD
332 | 16 FX ZAR
333 | 17 FX SEK
334 | 18 FX DKK
335 | 19 FX BRL
336 | 20 FX ZAC
337 | 21 FX MXN
338 | 22 FX TWD
339 | 23 FX KRW
340 | 24 FX CLP
341 | 25 FX CZK
342 | 26 FX HUF
343 | 27 FX IDR
344 | 28 FX ISK
345 | 29 FX MXV
346 | 30 FX PLN
347 | 31 FX TRY
348 | 32 FX UYU
349 | 33 FX BTC"""
350 | df: Union[pd.TextFileReader, pd.DataFrame] = pd.read_csv(StringIO(data), sep="\t")
351 | df: pd.DataFrame = df.set_index("ID")
352 | return df
353 |
354 |
355 | def get_indexes() -> pd.DataFrame:
356 | """
357 | Returns list of supported indexes
358 | https://eodhistoricaldata.com/knowledgebase/list-supported-indexes/
359 | """
360 | data: str = """ID Exchange Code Code Index Name
361 | 1 INDX GSPC S&P 500
362 | 2 INDX GDAXI DAX Index
363 | 3 INDX SSEC Shanghai Composite Index (China)
364 | 4 INDX MERV MERVAL Index (Argentina)
365 | 5 INDX FTSE FTSE 100 Index (UK)
366 | 6 INDX AORD All Ordinaries Index (Australia)
367 | 7 INDX BSESN BSE 30 Sensitivity Index (SENSEX)
368 | 8 INDX VIX VIX S&P 500 Volatility Index
369 | 9 INDX HSI Hang Seng Index (Hong Kong)
370 | 10 INDX GSPTSE S&P TSX Composite Index (Canada)
371 | 11 INDX FCHI CAC 40 Index
372 | 12 INDX TA100 Tel Aviv 100 Index (Israel
373 | 13 INDX CYC Morgan Stanley Cyclical Index
374 | 14 INDX IIX Interactive Week Internet Index
375 | 15 INDX CMR Morgan Stanley Consumer Index
376 | 16 INDX GOX CBOE Gold Inde
377 | 17 INDX RTS_RS RTSI Index
378 | 18 INDX GD_AT Athens Composite Inde
379 | 19 INDX FTSEMIB_MI Untitled Dataset 2015-07-13 20:00:12
380 | 20 INDX WILREIT Wilshire US REIT Inde
381 | 21 INDX W5KMCG Wilshire US Mid Cap Growt
382 | 22 INDX IBEX IBEX 35 Index
383 | 23 INDX W5KLCV Wilshire US Large Cap Valu
384 | 24 INDX SSMI Swiss Market Index
385 | 25 INDX OEX S&P 100 Inde
386 | 26 INDX RUI Russell 1000 Inde
387 | 27 INDX XAX NYSE AMEX Composite Inde
388 | 28 INDX WILRESI Wilshire US Real Estate Securities Inde
389 | 29 INDX NZ50 NZSE 50 (New Zealand)
390 | 30 INDX UTY PHLX Utility Sector Inde
391 | 31 INDX CSE Colombo All Shares Index (Sri Lanka
392 | 32 INDX XOI NYSE AMEX Oil Inde
393 | 33 INDX OSX PHLX Oil Service Sector Inde
394 | 34 INDX XAL NYSE AMEX Airline Inde
395 | 35 INDX W5KSCG Wilshire US Small Cap Growt
396 | 36 INDX TWII Taiwan Weighted Inde
397 | 37 INDX ATX ATX Index (Austria
398 | 38 INDX NWX NYSE ARCA Networking Inde
399 | 39 INDX W5KSCV Wilshire US Small Cap Valu
400 | 40 INDX XAU PHLX Gold/Silver Sector Inde
401 | 41 INDX W5KMCV Wilshire US Mid Cap Valu
402 | 42 INDX WGREIT Wilshire Global REIT Inde
403 | 43 INDX SML S&P Small-Cap 600 Inde
404 | 44 INDX RUT Russell 2000 Inde
405 | 45 INDX JKSE Jakarta Composite Index (Indonesia
406 | 46 INDX BFX Euronext BEL-20 Index (Belgium)
407 | 47 INDX XBD NYSE AMEX Securities Broker/Dealer Inde
408 | 48 INDX RUA Russell 3000 Inde
409 | 49 INDX XII NYSE ARCA Institutional Inde
410 | 50 INDX IETP ISEQ 20 Price Index (Ireland
411 | 51 INDX DRG NYSE AMEX Pharmaceutical Inde
412 | 52 INDX W5000 Wilshire 5000 Total Market Inde
413 | 53 INDX HGX PHLX Housing Sector Inde
414 | 54 INDX MXX IPC Index (Mexico)
415 | 55 INDX W5KLCG Wilshire US Large Cap Growt
416 | 56 INDX STI Straits Times Index
417 | 57 INDX KS11 KOSPI Composite Index
418 | 58 INDX AEX AEX Amsterdam Index
419 | 59 INDX NYA NYSE Composite Index
420 | 60 INDX XMI NYSE ARCA Major Market Inde
421 | 61 INDX BTK NYSE AMEX Biotechnology Inde
422 | 62 INDX EPX NASDAQ SIG Oil Exploration and Production Inde
423 | 63 INDX MID S&P Mid-Cap 400 Inde
424 | 64 INDX HUI NYSE Arca Gold Bugs Inde
425 | 65 INDX SOX PHLX Semiconductor Inde
426 | 66 INDX HCX CBOE S&P Healthcare Index
427 | 67 INDX XCI NYSE AMEX Computer Technology Inde
428 | 68 INDX XNG NYSE AMEX Natural Gas Inde
429 | 69 INDX RMZ MSCI US REIT Inde
430 | 70 INDX WGRESI Wilshire Global Real Estate Securities Inde
431 | 71 INDX N225 Nikkei 225 Index (Japan
432 | 72 INDX VDAX Deutsche Boerse VDAX Volatility Inde
433 | 73 INDX MXY NYSE ARCA Mexico Inde
434 | 74 INDX OSEAX Oslo Exchange All Share Index (Norway)
435 | 75 INDX TYX Treasury Yield 30 Years Inde
436 | 76 INDX DJI Dow Jones Industrial Average
437 | 77 INDX AXPJ S&P/ASX 200 Australia REIT Inde
438 | 78 INDX PSI20 PSI 20 Stock Index (Portugal
439 | 79 INDX IRX 13-week Treasury Bill Inde
440 | 80 INDX FVX Treasury Yield 5 Years Inde
441 | 81 INDX NYI NYSE International 100 Index
442 | 82 INDX AXJO S&P/ASX 200 Index (Australia
443 | 83 INDX 512NTR S&P 500 GBP Hdg (Net TR) (^512NTR)
444 | 84 INDX CTES_VI Czech Trading Inde
445 | 85 INDX NSEI S&P/CNX Nifty Index (India
446 | 86 INDX NYY NYSE TMT Inde
447 | 87 INDX CCSI EGX 70 Price Index (Egypt
448 | 88 INDX SPSUPX S&P Composite 1500 Inde
449 | 89 INDX BVSP Bovespa Index (Brazil)
450 | 90 INDX ISEQ ISEQ Overall Price Index (Ireland
451 | 91 INDX JPN NYSE AMEX Japan Inde
452 | 92 INDX NYL NYSE World Leaders Inde
453 | 93 INDX TNX CBOE Interest Rate 10-Year T-Note Inde
454 | 94 INDX NY NYSE US 100 Inde
455 | 95 INDX SPLV PowerShares S&P 500 Low Volatil
456 | 96 INDX OMXSPI Stockholm General Index (Sweden)
457 | 97 INDX GVZ CBOE Gold Volatility Inde
458 | 98 INDX SPY SPDR S&P 500 (SPY
459 | 99 INDX IEQR_IR ISEQ General Total Return Index (Ireland
460 | 100 INDX OMXC20_CO OMX Copenhagen 20 Index
461 | 101 INDX DJUSFN ^DJUSFN: Dow Jones U.S. Financials Inde
462 | 102 INDX DJASD ^DJASD: Dow Jones Asia Select Dividen
463 | 103 INDX IMUS ^IMUS: Dow Jones Islamic Market U.S.
464 | 104 INDX W1SGI ^W1SGI: Dow Jones Sustainability Worl
465 | 105 INDX DJT ^DJT: Dow Jones Transportation Averag
466 | 106 INDX DJUSM ^DJUSM: Dow Jones U.S. Mid-Cap Inde
467 | 107 INDX W1XGA ^W1XGA: Dow Jones Sustainability Worl
468 | 108 INDX DWC ^DWC: DJUS Market Index (full-cap
469 | 109 INDX DJC ^DJC: Dow Jones-UBS Commodity Inde
470 | 110 INDX IMXL ^IMXL: Dow Jones Islamic Market Titan
471 | 111 INDX XLHK ^XLHK: Dow Jones Hong Kong Titans 30
472 | 112 INDX DJTMDI ^DJTMDI: Dow Jones Media Titans 30 Inde
473 | 113 INDX DJU ^DJU: Dow Jones Utility Averag
474 | 114 INDX DWCOGS ^DWCOGS: Dow Jones U.S. Oil & Gas Tota
475 | 115 INDX DJUSST ^DJUSST: Dow Jones U.S. Iron & Steel In
476 | 116 INDX PSE ^PSE: NYSE Arca Tech 100 Index - New York Stock Exchange
477 | 117 INDX DWCF ^DWCF: Dow Jones U.S. Total Stock Mar
478 | 118 INDX W1SUS ^W1SUS: Dow Jones Sustainability Worl
479 | 119 INDX DJASDT ^DJASDT: Dow Jones Asia Select Dividen
480 | 120 INDX RCI ^RCI: Dow Jones Composite All REIT I
481 | 121 INDX DJUSL ^DJUSL: Dow Jones U.S. Large-Cap Inde
482 | 122 INDX P1DOW ^P1DOW: Dow Jones Asia/Pacific Inde
483 | 123 INDX DJAT ^DJAT: Dow Jones Asian Titans 50 Inde
484 | 124 INDX DJUS ^DJUS: Dow Jones U.S. Inde
485 | 125 INDX DWMI ^DWMI: Dow Jones U.S. Micro-Cap Tota
486 | 126 INDX DJUSS ^DJUSS: Dow Jones U.S. Small-Cap Inde
487 | 127 INDX OMX OMXS 30 Index (Sweden
488 | 128 INDX STOXX50E EuroStoxx 50 Inde
489 | 129 INDX FTAS FTSE All-Share Index (UK)
490 | 130 INDX WIHUN_L FTSE HUngary Index
491 | 131 INDX WITUR_L FTSE Turkey Index
492 | 132 INDX WITHA_L FTSE Thailand Index
493 | 133 INDX WIPOL_L FTSE Poland Index
494 | 134 INDX WICZH_L FTSE Czech Republic Index
495 | 135 INDX OMXC20 OMX Copenhagen 20 Inde
496 | 136 INDX IXE ^IXE: Select Sector Spdr-energy Inde
497 | 137 INDX IXIC NASDAQ Composite
498 | 138 INDX SPEUP S&P EUROPE 350"""
499 | df: Union[pd.TextFileReader, pd.DataFrame] = pd.read_csv(StringIO(data), sep="\t")
500 | df: pd.DataFrame = df.set_index("ID")
501 | return df
502 |
--------------------------------------------------------------------------------