Skip to content

Commit 5a9f90b

Browse files
authored
Merge pull request #63 from EodHistoricalData/v1.3.2
V1.3.2
2 parents c05dfbe + ec660c5 commit 5a9f90b

2 files changed

Lines changed: 116 additions & 33 deletions

File tree

eodhd/APIs/BaseAPI.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# APIs/BaseAPI.py
22

33
from json.decoder import JSONDecodeError
4-
import sys
54
from requests import get as requests_get
65
from requests import ConnectionError as requests_ConnectionError
76
from requests import Timeout as requests_Timeout
@@ -29,8 +28,9 @@ def _rest_get_method(self, api_key: str, endpoint: str = "", uri: str = "", quer
2928
if "message" in resp.json():
3029
resp_message = resp.json()["message"]
3130
elif "errors" in resp.json():
32-
self.console.log(resp.json())
33-
sys.exit(1)
31+
errors = resp.json()
32+
self.console.log(errors)
33+
raise RuntimeError(f"EODHD API returned errors (HTTP {resp.status_code}): {errors}")
3434
else:
3535
resp_message = ""
3636

eodhd/apiclient.py

Lines changed: 113 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#apiclient.py
22

3-
from json.decoder import JSONDecodeError
43
import sys
4+
from json.decoder import JSONDecodeError
55
from enum import Enum
66
from datetime import datetime
77
from datetime import timedelta
@@ -132,8 +132,9 @@ def _rest_get(self, endpoint: str = "", uri: str = "", querystring: str = "") ->
132132
if "message" in resp.json():
133133
resp_message = resp.json()["message"]
134134
elif "errors" in resp.json():
135-
self.console.log(resp.json())
136-
sys.exit(1)
135+
errors = resp.json()
136+
self.console.log(errors)
137+
raise RuntimeError(f"EODHD API returned errors (HTTP {resp.status_code}): {errors}")
137138
else:
138139
resp_message = ""
139140

@@ -167,17 +168,53 @@ def get_exchanges(self) -> pd.DataFrame:
167168

168169
return self._rest_get("exchanges-list")
169170

170-
def get_exchange_symbols(self, uri: str = "", delisted=False) -> pd.DataFrame:
171-
"""Get supported exchange symbols"""
172-
171+
def get_exchange_symbols(
172+
self,
173+
uri: str = "",
174+
delisted: bool = False,
175+
include_delisted: bool = False,
176+
) -> pd.DataFrame:
177+
"""Get supported exchange symbols.
178+
179+
Parameters
180+
----------
181+
uri : str
182+
Exchange code, e.g. "US".
183+
delisted : bool, optional
184+
If True, return delisted symbols only. Ignored if include_delisted=True.
185+
include_delisted : bool, optional
186+
If True, return both listed and delisted symbols in one DataFrame.
187+
188+
Returns
189+
-------
190+
pd.DataFrame
191+
"""
173192
try:
174-
if uri.strip() == "":
193+
if uri is None or str(uri).strip() == "":
175194
raise ValueError("endpoint uri is empty!")
176195

177-
if delisted:
178-
return self._rest_get("exchange-symbol-list", uri, "&delisted=1")
196+
# allow 0/1 and True/False
197+
delisted = bool(delisted)
198+
include_delisted = bool(include_delisted)
199+
200+
if not include_delisted:
201+
if delisted:
202+
return self._rest_get("exchange-symbol-list", uri, "&delisted=1")
203+
return self._rest_get("exchange-symbol-list", uri)
204+
205+
# include_delisted=True -> merge both
206+
listed_df = self._rest_get("exchange-symbol-list", uri)
207+
delisted_df = self._rest_get("exchange-symbol-list", uri, "&delisted=1")
208+
209+
# If either is empty, return the other
210+
if listed_df is None or len(listed_df) == 0:
211+
return delisted_df if delisted_df is not None else pd.DataFrame()
212+
if delisted_df is None or len(delisted_df) == 0:
213+
return listed_df
214+
215+
# Concatenate safely
216+
return pd.concat([listed_df, delisted_df], ignore_index=True)
179217

180-
return self._rest_get("exchange-symbol-list", uri)
181218
except ValueError as err:
182219
self.console.log(err)
183220
return pd.DataFrame()
@@ -554,13 +591,10 @@ def get_live_stock_prices(self, ticker, s=None) -> list:
554591
api_call = LiveStockPricesAPI()
555592
return api_call.get_live_stock_prices(api_token=self._api_key, ticker=ticker, s=s)
556593

557-
def get_us_extended_quotes(
558-
self,
559-
s,
560-
page_limit = None,
561-
page_offset = None,
562-
fmt = None, # "json" or "csv"
563-
) ->list:
594+
def get_us_extended_quotes(self, s, page_limit=None, page_offset=None, fmt=None) -> list:
595+
if fmt is not None and str(fmt).lower() != "json":
596+
raise ValueError("This library currently supports only fmt='json' for us-quote-delayed.")
597+
564598
"""Available args (Live v2 US Stocks — Extended/Delayed Quotes):
565599
566600
api_token (required) - your API access token (if not already configured in the client)
@@ -585,8 +619,12 @@ def get_us_extended_quotes(
585619
https://eodhd.com/financial-apis/live-v2-for-us-stocks-extended-quotes-2025
586620
"""
587621
api_call = LiveExtendedQuotesAPI()
588-
return api_call.get_us_extended_quotes(api_token=self._api_key, symbols=s, page_limit = page_limit, page_offset = page_offset,
589-
fmt = fmt)
622+
return api_call.get_us_extended_quotes(
623+
api_token=self._api_key,
624+
symbols=s,
625+
page_limit=page_limit,
626+
page_offset=page_offset,
627+
)
590628

591629
def get_economic_events_data(
592630
self,
@@ -780,19 +818,64 @@ def get_list_of_exchanges(self):
780818
api_call = ListOfExchangesAPI()
781819
return api_call.get_list_of_exchanges(api_token=self._api_key)
782820

783-
def get_list_of_tickers(self, code, delisted=0):
784-
"""Available args:
785-
delisted (not required) - by default, this API provides only tickers that were active at least a month ago,
786-
to get the list of inactive (delisted) tickers please use the parameter “delisted=1”
787-
code (required) - For US exchanges you can also get all US tickers,
788-
then you should use the ‘US’ exchange code and tickers only for the particular exchange,
789-
the list of possible US exchanges to request:'US', 'NYSE', 'NASDAQ', 'BATS', 'OTCQB', 'PINK', 'OTCQX',
790-
'OTCMKTS', 'NMFQS', 'NYSE MKT', 'OTCBB', 'OTCGREY', 'BATS', 'OTC'
791-
For more information visit: https://eodhistoricaldata.com/financial-apis/exchanges-api-list-of-tickers-and-trading-hours/
821+
def get_list_of_tickers(self, code: str, delisted: int = 0, include_delisted: bool = False):
822+
"""Get list of tickers for an exchange.
823+
824+
Parameters
825+
----------
826+
code : str
827+
Exchange code (e.g., "US", "NYSE", "NASDAQ", etc.).
828+
delisted : int, optional
829+
0 = listed only, 1 = delisted only. Ignored if include_delisted=True.
830+
include_delisted : bool, optional
831+
If True, returns both listed and delisted tickers.
832+
833+
Notes
834+
-----
835+
- By default, the API returns only tickers that were active at least a month ago.
836+
- delisted=1 returns delisted tickers only.
837+
- include_delisted=True returns both listed and delisted tickers.
792838
"""
839+
if code is None or str(code).strip() == "":
840+
raise ValueError("Parameter 'code' is required (exchange code).")
841+
842+
# Allow 0/1 as well as True/False
843+
include_delisted = bool(include_delisted)
844+
845+
if delisted not in (0, 1):
846+
raise ValueError("Parameter 'delisted' must be 0 or 1.")
793847

794848
api_call = ListOfExchangesAPI()
795-
return api_call.get_list_of_tickers(api_token=self._api_key, delisted=delisted, code=code)
849+
850+
if not include_delisted:
851+
return api_call.get_list_of_tickers(api_token=self._api_key, delisted=delisted, code=code)
852+
853+
# include_delisted=True: fetch both
854+
listed = api_call.get_list_of_tickers(api_token=self._api_key, delisted=0, code=code)
855+
delisted_list = api_call.get_list_of_tickers(api_token=self._api_key, delisted=1, code=code)
856+
857+
if isinstance(listed, list) and isinstance(delisted_list, list):
858+
return listed + delisted_list
859+
860+
if isinstance(listed, dict) and isinstance(delisted_list, dict):
861+
if "data" in listed and "data" in delisted_list and isinstance(listed["data"], list) and isinstance(
862+
delisted_list["data"], list):
863+
merged = dict(listed)
864+
merged["data"] = listed["data"] + delisted_list["data"]
865+
# optional: recompute meta.total if present
866+
if "meta" in merged and isinstance(merged["meta"], dict):
867+
total = None
868+
try:
869+
total = len(merged["data"])
870+
except Exception:
871+
total = None
872+
if total is not None:
873+
merged["meta"]["total"] = total
874+
return merged
875+
876+
return {"listed": listed, "delisted": delisted_list}
877+
878+
return {"listed": listed, "delisted": delisted_list}
796879

797880
def get_details_trading_hours_stock_market_holidays(self, code, from_date=None, to_date=None):
798881
"""Available args:
@@ -1366,4 +1449,4 @@ def scan_markets(
13661449
)
13671450
df_dataset.to_csv("dataset.csv")
13681451

1369-
return df_dataset
1452+
return df_dataset

0 commit comments

Comments
 (0)