11#apiclient.py
22
3- from json .decoder import JSONDecodeError
43import sys
4+ from json .decoder import JSONDecodeError
55from enum import Enum
66from datetime import datetime
77from 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