11"""Borsdata API client implementation."""
22
33from datetime import datetime
4- from typing import Any , Dict , List , Optional
4+ from typing import Any , Dict , Iterable , List , Optional
55
66import httpx
77from tenacity import (
2525 KpiAllResponse ,
2626 KpiCalcUpdatedResponse ,
2727 KpiMetadata ,
28+ KpisHistoryArrayResp ,
2829 KpisSummaryResponse ,
2930 KpiSummaryGroup ,
3031 Market ,
3334 ReportCalendarListResponse ,
3435 ReportMetadata ,
3536 ReportMetadataResponse ,
37+ ReportsArrayResp ,
38+ ReportsCombineResp ,
3639 Sector ,
3740 SectorsResponse ,
3841 ShortsListResponse ,
@@ -220,22 +223,26 @@ def get_stock_prices(
220223
221224 def get_stock_prices_batch (
222225 self ,
223- instrument_ids : list [int ],
226+ instrument_ids : Iterable [int ],
224227 from_date : Optional [datetime ] = None ,
225228 to_date : Optional [datetime ] = None ,
226229 ) -> List [StockPricesArrayRespList ]:
227230 """Get stock prices for multiple instruments, max 50 instruments per call.
228231
229232 Args:
230- instrument_ids: List of instrument IDs
233+ instrument_ids: Iterable of instrument IDs
231234 from_date: Start date for price data
232235 to_date: End date for price data
233236
234237 Returns:
235238 List of StockPrice objects
236239 """
237- assert isinstance (instrument_ids , list ), "instrument_ids must be a list"
238- assert len (instrument_ids ) <= 50 , "Max 50 instrument IDs allowed per request"
240+ assert isinstance (
241+ instrument_ids , Iterable
242+ ), "instrument_ids must be an iterable"
243+ assert (
244+ len (list (instrument_ids )) <= 50
245+ ), "Max 50 instrument IDs allowed per request"
239246
240247 params = {"instList" : "," .join (map (str , instrument_ids ))}
241248
@@ -278,6 +285,56 @@ def get_reports(
278285 )
279286 return [Report (** report ) for report in response .get ("reports" , [])]
280287
288+ def get_reports_batch (
289+ self ,
290+ instrument_ids : Iterable [int ],
291+ max_year_count : Optional [int ] = 10 ,
292+ max_quarter_r12_count : Optional [int ] = 10 ,
293+ original_currency : bool = False ,
294+ ) -> List [ReportsCombineResp ]:
295+ """Get financial reports for multiple instruments, max 50 instruments per call.
296+
297+ Args:
298+ instrument_ids: Iterable of instrument IDs
299+ max_year_count: Maximum number of year reports to return, max 20.
300+ max_quarter_r12_count: Maximum number of quarter/R12 reports to return, max 40.
301+ original_currency: Whether to return values in original currency
302+
303+ Returns:
304+ List of Report objects
305+ """
306+ assert isinstance (
307+ instrument_ids , Iterable
308+ ), "instrument_ids must be an iterable"
309+ assert (
310+ len (list (instrument_ids )) <= 50
311+ ), "Max 50 instrument IDs allowed per request"
312+ assert max_quarter_r12_count is None or isinstance (
313+ max_quarter_r12_count , int
314+ ), "max_quarter_r12_count must be an integer"
315+ assert max_year_count <= 20 , "max_year_count must be 20 or less"
316+ assert max_quarter_r12_count <= 40 , "max_quarter_r12_count must be 40 or less"
317+
318+ params = {"instList" : "," .join (map (str , instrument_ids ))}
319+
320+ if max_year_count is not None :
321+ assert (
322+ isinstance (max_year_count , int ) and 0 < max_year_count <= 20
323+ ), "max_year_count must be a positive integer"
324+ params ["maxYearCount" ] = str (max_year_count )
325+ if max_quarter_r12_count is not None :
326+ assert (
327+ isinstance (max_quarter_r12_count , int )
328+ and 0 < max_quarter_r12_count <= 40
329+ ), "max_quarter_r12_count must be a positive integer"
330+ params ["maxQuarterR12Count" ] = str (max_quarter_r12_count )
331+
332+ params ["original" ] = "1" if original_currency else "0"
333+
334+ response = self ._get (f"/instruments/reports" , params )
335+ response_model = ReportsArrayResp (** response )
336+ return response_model .report_list
337+
281338 def get_reports_metadata (
282339 self ,
283340 ) -> List [ReportMetadata ]:
@@ -341,8 +398,47 @@ def get_kpi_history(
341398 )
342399 return KpiAllResponse (** response )
343400
401+ def get_kpi_history_batch (
402+ self ,
403+ instrument_ids : Iterable [int ],
404+ kpi_id : int ,
405+ report_type : str ,
406+ price_type : str = "mean" ,
407+ max_count : Optional [int ] = None ,
408+ ) -> List [KpiAllResponse ]:
409+ """Get KPI history for multiple instruments, max 50 instruments per call.
410+
411+ Args:
412+ instrument_ids: IDs of the instruments
413+ kpi_id: ID of the KPI
414+ report_type: Type of report ('year', 'r12', 'quarter')
415+ price_type: Type of price calculation
416+ max_count: Maximum number of results to return, 10 by default. report_type 'year' can return max 20, 'r12' and 'quarter' can return max 40.
417+
418+ Returns:
419+ List of KPI responses
420+ """
421+
422+ assert isinstance (
423+ instrument_ids , Iterable
424+ ), "instrument_ids must be an iterable"
425+ assert (
426+ len (list (instrument_ids )) <= 50
427+ ), "Max 50 instrument IDs allowed per request"
428+
429+ params = {"instList" : "," .join (map (str , instrument_ids ))}
430+ if max_count :
431+ params ["maxCount" ] = str (max_count )
432+
433+ response = self ._get (
434+ f"/instruments/kpis/{ kpi_id } /{ report_type } /{ price_type } /history" ,
435+ params ,
436+ )
437+
438+ return KpisHistoryArrayResp (** response )
439+
344440 def get_kpi_summary (
345- self , instrument_id : str , report_type : str , max_count : Optional [int ] = None
441+ self , instrument_id : int , report_type : str , max_count : Optional [int ] = None
346442 ) -> List [KpiSummaryGroup ]:
347443 """Get all KPI history for an instrument.
348444
@@ -364,7 +460,7 @@ def get_kpi_summary(
364460 return KpisSummaryResponse (** response ).kpis or []
365461
366462 def get_insider_holdings (
367- self , instrument_ids : List [int ]
463+ self , instrument_ids : Iterable [int ]
368464 ) -> List [InsiderListResponse ]:
369465 """Get insider holdings for specified instruments.
370466
@@ -387,7 +483,7 @@ def get_short_positions(self) -> List[ShortsListResponse]:
387483 response = self ._get ("/holdings/shorts" )
388484 return ShortsListResponse (** response ).list or []
389485
390- def get_buybacks (self , instrument_ids : List [int ]) -> List [BuybackListResponse ]:
486+ def get_buybacks (self , instrument_ids : Iterable [int ]) -> List [BuybackListResponse ]:
391487 """Get buyback data for specified instruments.
392488
393489 Args:
@@ -401,12 +497,12 @@ def get_buybacks(self, instrument_ids: List[int]) -> List[BuybackListResponse]:
401497 return BuybackListResponse (** response ).list or []
402498
403499 def get_instrument_descriptions (
404- self , instrument_ids : List [int ]
500+ self , instrument_ids : Iterable [int ]
405501 ) -> List [InstrumentDescriptionListResponse ]:
406502 """Get descriptions for specified instruments.
407503
408504 Args:
409- instrument_ids: List of instrument IDs to get descriptions for
505+ instrument_ids: Iterable of instrument IDs to get descriptions for
410506
411507 Returns:
412508 List of instrument description responses
@@ -416,12 +512,12 @@ def get_instrument_descriptions(
416512 return InstrumentDescriptionListResponse (** response ).list or []
417513
418514 def get_report_calendar (
419- self , instrument_ids : List [int ]
515+ self , instrument_ids : Iterable [int ]
420516 ) -> List [ReportCalendarListResponse ]:
421517 """Get report calendar for specified instruments.
422518
423519 Args:
424- instrument_ids: List of instrument IDs to get calendar for
520+ instrument_ids: Iterable of instrument IDs to get calendar for
425521
426522 Returns:
427523 List of report calendar responses
@@ -431,12 +527,12 @@ def get_report_calendar(
431527 return ReportCalendarListResponse (** response ).list or []
432528
433529 def get_dividend_calendar (
434- self , instrument_ids : List [int ]
530+ self , instrument_ids : Iterable [int ]
435531 ) -> List [DividendCalendarListResponse ]:
436532 """Get dividend calendar for specified instruments.
437533
438534 Args:
439- instrument_ids: List of instrument IDs to get calendar for
535+ instrument_ids: Iterable of instrument IDs to get calendar for
440536
441537 Returns:
442538 List of dividend calendar responses
0 commit comments