kensho-kfinance 2.4.2__py3-none-any.whl → 2.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kensho-kfinance might be problematic. Click here for more details.

Files changed (31) hide show
  1. {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/METADATA +1 -1
  2. {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/RECORD +29 -26
  3. kfinance/CHANGELOG.md +15 -0
  4. kfinance/batch_request_handling.py +2 -16
  5. kfinance/constants.py +14 -2
  6. kfinance/fetch.py +56 -0
  7. kfinance/kfinance.py +403 -90
  8. kfinance/mcp.py +23 -35
  9. kfinance/meta_classes.py +37 -12
  10. kfinance/tests/conftest.py +4 -0
  11. kfinance/tests/scratch.py +0 -0
  12. kfinance/tests/test_batch_requests.py +0 -32
  13. kfinance/tests/test_fetch.py +21 -0
  14. kfinance/tests/test_objects.py +239 -3
  15. kfinance/tests/test_tools.py +136 -24
  16. kfinance/tool_calling/__init__.py +2 -4
  17. kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +39 -0
  18. kfinance/tool_calling/get_competitors_from_identifier.py +24 -0
  19. kfinance/tool_calling/get_financial_line_item_from_identifier.py +3 -3
  20. kfinance/tool_calling/get_financial_statement_from_identifier.py +3 -3
  21. kfinance/tool_calling/get_merger_info_from_transaction_id.py +62 -0
  22. kfinance/tool_calling/get_mergers_from_identifier.py +41 -0
  23. kfinance/tool_calling/get_segments_from_identifier.py +3 -3
  24. kfinance/tool_calling/shared_models.py +17 -2
  25. kfinance/version.py +2 -2
  26. kfinance/tests/test_mcp.py +0 -16
  27. kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +0 -19
  28. {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/WHEEL +0 -0
  29. {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/licenses/AUTHORS.md +0 -0
  30. {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/licenses/LICENSE +0 -0
  31. {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/top_level.txt +0 -0
kfinance/kfinance.py CHANGED
@@ -4,7 +4,6 @@ from collections.abc import Sequence
4
4
  from concurrent.futures import ThreadPoolExecutor
5
5
  from copy import deepcopy
6
6
  from datetime import date, datetime, timezone
7
- from functools import cached_property
8
7
  from io import BytesIO
9
8
  import logging
10
9
  import re
@@ -80,8 +79,9 @@ class TradingItem:
80
79
  """
81
80
  self.kfinance_api_client = kfinance_api_client
82
81
  self.trading_item_id = trading_item_id
83
- self.ticker: Optional[str] = None
84
- self.exchange_code: Optional[str] = None
82
+
83
+ self._ticker: str | None = None
84
+ self._history_metadata: HistoryMetadata | None = None
85
85
 
86
86
  def __str__(self) -> str:
87
87
  """String representation for the company object"""
@@ -104,36 +104,35 @@ class TradingItem:
104
104
  "trading_item_id"
105
105
  ]
106
106
  trading_item = TradingItem(kfinance_api_client, trading_item_id)
107
- trading_item.ticker = ticker
108
- trading_item.exchange_code = exchange_code
107
+ trading_item._ticker = ticker
109
108
  return trading_item
110
109
 
111
- @cached_property
112
- def trading_item_id(self) -> int:
113
- """Get the trading item id for the object
114
-
115
- :return: the CIQ trading item id
116
- :rtype: int
117
- """
118
- return self.trading_item_id
119
-
120
- @cached_property
110
+ @property
121
111
  def history_metadata(self) -> HistoryMetadata:
122
112
  """Get information about exchange and quotation
123
113
 
124
114
  :return: A dict containing data about the currency, symbol, exchange, type of instrument, and the first trading date
125
115
  :rtype: HistoryMetadata
126
116
  """
127
- metadata = self.kfinance_api_client.fetch_history_metadata(self.trading_item_id)
128
- if self.exchange_code is None:
129
- self.exchange_code = metadata["exchange_name"]
130
- return {
131
- "currency": metadata["currency"],
132
- "symbol": metadata["symbol"],
133
- "exchange_name": metadata["exchange_name"],
134
- "instrument_type": metadata["instrument_type"],
135
- "first_trade_date": datetime.strptime(metadata["first_trade_date"], "%Y-%m-%d").date(),
136
- }
117
+ if self._history_metadata is None:
118
+ metadata = self.kfinance_api_client.fetch_history_metadata(self.trading_item_id)
119
+ self._history_metadata = HistoryMetadata(
120
+ currency=metadata["currency"],
121
+ symbol=metadata["symbol"],
122
+ exchange_name=metadata["exchange_name"],
123
+ instrument_type=metadata["instrument_type"],
124
+ first_trade_date=datetime.strptime(metadata["first_trade_date"], "%Y-%m-%d").date(),
125
+ )
126
+ return self._history_metadata
127
+
128
+ @property
129
+ def exchange_code(self) -> str:
130
+ """Return the exchange_code of the trading item
131
+
132
+ :return: The exchange code of the trading item.
133
+ :rtype: str
134
+ """
135
+ return self.history_metadata["exchange_name"]
137
136
 
138
137
  def history(
139
138
  self,
@@ -307,7 +306,12 @@ class Company(CompanyFunctionsMetaClass):
307
306
  :type company_id: int
308
307
  """
309
308
 
310
- def __init__(self, kfinance_api_client: KFinanceApiClient, company_id: int):
309
+ def __init__(
310
+ self,
311
+ kfinance_api_client: KFinanceApiClient,
312
+ company_id: int,
313
+ company_name: str | None = None,
314
+ ):
311
315
  """Initialize the Company object
312
316
 
313
317
  :param kfinance_api_client: The KFinanceApiClient used to fetch data
@@ -317,45 +321,69 @@ class Company(CompanyFunctionsMetaClass):
317
321
  """
318
322
  super().__init__()
319
323
  self.kfinance_api_client = kfinance_api_client
320
- self.company_id = company_id
324
+ self._company_id = company_id
321
325
  self._all_earnings: list[Earnings] | None = None
326
+ self._mergers_for_company: dict[str, MergersAndAcquisitions] | None = None
327
+ self._company_name = company_name
328
+
329
+ self._securities: Securities | None = None
330
+ self._primary_security: Security | None = None
331
+ self._info: dict | None = None
332
+ self._earnings_call_datetimes: list[datetime] | None = None
333
+
334
+ @property
335
+ def company_id(self) -> int:
336
+ """Return the company_id of the company.
337
+
338
+ :return: the company_id of the company
339
+ :rtype: int
340
+ """
341
+ return self._company_id
322
342
 
323
343
  def __str__(self) -> str:
324
344
  """String representation for the company object"""
325
345
  return f"{type(self).__module__}.{type(self).__qualname__} of {self.company_id}"
326
346
 
327
- @cached_property
347
+ @property
328
348
  def primary_security(self) -> Security:
329
349
  """Return the primary security item for the Company object
330
350
 
331
351
  :return: a Security object of the primary security of company_id
332
352
  :rtype: Security
333
353
  """
334
- primary_security_id = self.kfinance_api_client.fetch_primary_security(self.company_id)[
335
- "primary_security"
336
- ]
337
- return Security(
338
- kfinance_api_client=self.kfinance_api_client, security_id=primary_security_id
339
- )
354
+ if self._primary_security is None:
355
+ primary_security_id = self.kfinance_api_client.fetch_primary_security(self.company_id)[
356
+ "primary_security"
357
+ ]
358
+ self._primary_security = Security(
359
+ kfinance_api_client=self.kfinance_api_client, security_id=primary_security_id
360
+ )
361
+ return self._primary_security
340
362
 
341
- @cached_property
363
+ @property
342
364
  def securities(self) -> Securities:
343
365
  """Return the security items for the Company object
344
366
 
345
367
  :return: a Securities object containing the list of securities of company_id
346
368
  :rtype: Securities
347
369
  """
348
- security_ids = self.kfinance_api_client.fetch_securities(self.company_id)["securities"]
349
- return Securities(kfinance_api_client=self.kfinance_api_client, security_ids=security_ids)
370
+ if self._securities is None:
371
+ security_ids = self.kfinance_api_client.fetch_securities(self.company_id)["securities"]
372
+ self._securities = Securities(
373
+ kfinance_api_client=self.kfinance_api_client, security_ids=security_ids
374
+ )
375
+ return self._securities
350
376
 
351
- @cached_property
377
+ @property
352
378
  def info(self) -> dict:
353
379
  """Get the company info
354
380
 
355
381
  :return: a dict with containing: name, status, type, simple industry, number of employees (if available), founding date, webpage, address, city, zip code, state, country, & iso_country
356
382
  :rtype: dict
357
383
  """
358
- return self.kfinance_api_client.fetch_info(self.company_id)
384
+ if self._info is None:
385
+ self._info = self.kfinance_api_client.fetch_info(self.company_id)
386
+ return self._info
359
387
 
360
388
  @property
361
389
  def name(self) -> str:
@@ -364,7 +392,7 @@ class Company(CompanyFunctionsMetaClass):
364
392
  :return: The company name
365
393
  :rtype: str
366
394
  """
367
- return self.info["name"]
395
+ return self._company_name if self._company_name else self.info["name"]
368
396
 
369
397
  @property
370
398
  def status(self) -> str:
@@ -474,19 +502,21 @@ class Company(CompanyFunctionsMetaClass):
474
502
  """
475
503
  return self.info["iso_country"]
476
504
 
477
- @cached_property
505
+ @property
478
506
  def earnings_call_datetimes(self) -> list[datetime]:
479
507
  """Get the datetimes of the companies earnings calls
480
508
 
481
509
  :return: a list of datetimes for the companies earnings calls
482
510
  :rtype: list[datetime]
483
511
  """
484
- return [
485
- datetime.fromisoformat(earnings_call).replace(tzinfo=timezone.utc)
486
- for earnings_call in self.kfinance_api_client.fetch_earnings_dates(self.company_id)[
487
- "earnings"
512
+ if self._earnings_call_datetimes is None:
513
+ self._earnings_call_datetimes = [
514
+ datetime.fromisoformat(earnings_call).replace(tzinfo=timezone.utc)
515
+ for earnings_call in self.kfinance_api_client.fetch_earnings_dates(self.company_id)[
516
+ "earnings"
517
+ ]
488
518
  ]
489
- ]
519
+ return self._earnings_call_datetimes
490
520
 
491
521
  @property
492
522
  def all_earnings(self) -> list[Earnings]:
@@ -603,6 +633,103 @@ class Company(CompanyFunctionsMetaClass):
603
633
  # Sort by datetime ascending and get the earliest
604
634
  return min(future_earnings, key=lambda x: x.datetime)
605
635
 
636
+ @property
637
+ def mergers_and_acquisitions(self) -> dict[str, MergersAndAcquisitions]:
638
+ """Get the mergers and acquisitions this company has been party to.
639
+
640
+ :return: three lists of transactions, one each for 'target', 'buyer', and 'seller'
641
+ :rtype: dict[str, MergersAndAcquisitions]
642
+ """
643
+ if self._mergers_for_company is None:
644
+ mergers_for_company = self.kfinance_api_client.fetch_mergers_for_company(
645
+ company_id=self.company_id
646
+ )
647
+ output: dict = {}
648
+ for literal in ["target", "buyer", "seller"]:
649
+ output[literal] = MergersAndAcquisitions(
650
+ self.kfinance_api_client, mergers_for_company[literal]
651
+ )
652
+ self._mergers_for_company = output
653
+ return self._mergers_for_company
654
+
655
+
656
+ class AdvisedCompany(Company):
657
+ """A Company that has been involved in a transaction is a company that may have been advised."""
658
+
659
+ def __init__(
660
+ self,
661
+ kfinance_api_client: KFinanceApiClient,
662
+ company_id: int,
663
+ transaction_id: int,
664
+ company_name: str | None = None,
665
+ ):
666
+ """Initialize the AdvisedCompany object
667
+
668
+ :param kfinance_api_client: The KFinanceApiClient used to fetch data
669
+ :type kfinance_api_client: KFinanceApiClient
670
+ :param company_id: The S&P Global CIQ Company Id
671
+ :type company_id: int
672
+ :param transaction_id: The S&P Global CIP Transaction Id
673
+ :type transaction_id: int
674
+ """
675
+
676
+ super().__init__(
677
+ kfinance_api_client=kfinance_api_client,
678
+ company_id=company_id,
679
+ company_name=company_name,
680
+ )
681
+ self.transaction_id = transaction_id
682
+
683
+ @property
684
+ def advisors(self) -> Companies | None:
685
+ """Get the companies that advised this company during the current transaction."""
686
+ advisors = self.kfinance_api_client.fetch_advisors_for_company_in_merger(
687
+ transaction_id=self.transaction_id, advised_company_id=self.company_id
688
+ )["advisors"]
689
+ companies = [
690
+ AdvisorCompany(
691
+ kfinance_api_client=self.kfinance_api_client,
692
+ company_id=int(advisor["advisor_company_id"]),
693
+ company_name=str(advisor["advisor_company_name"]),
694
+ advisor_type_name=str(advisor["advisor_type_name"]),
695
+ )
696
+ for advisor in advisors
697
+ ]
698
+ return Companies(kfinance_api_client=self.kfinance_api_client, companies=companies)
699
+
700
+
701
+ class AdvisorCompany(Company):
702
+ """A company that advised another company during a transaction."""
703
+
704
+ def __init__(
705
+ self,
706
+ kfinance_api_client: KFinanceApiClient,
707
+ company_id: int,
708
+ advisor_type_name: str,
709
+ company_name: str | None = None,
710
+ ):
711
+ """Initialize the AdvisorCompany object
712
+
713
+ :param kfinance_api_client: The KFinanceApiClient used to fetch data
714
+ :type kfinance_api_client: KFinanceApiClient
715
+ :param company_id: The S&P Global CIQ Company Id
716
+ :type company_id: int
717
+ :param advisor_type_name: The type of the advisor company
718
+ :type advisor_type_name: str
719
+ """
720
+
721
+ super().__init__(
722
+ kfinance_api_client=kfinance_api_client,
723
+ company_id=company_id,
724
+ company_name=company_name,
725
+ )
726
+ self._advisor_type_name = advisor_type_name
727
+
728
+ @property
729
+ def advisor_type_name(self) -> str | None:
730
+ """When this company advised another during a transaction, get the advisor type name."""
731
+ return self._advisor_type_name
732
+
606
733
 
607
734
  class Security:
608
735
  """Security class
@@ -628,57 +755,69 @@ class Security:
628
755
  self.kfinance_api_client = kfinance_api_client
629
756
  self.security_id = security_id
630
757
 
758
+ self._cusip: str | None = None
759
+ self._isin: str | None = None
760
+ self._primary_trading_item: TradingItem | None = None
761
+ self._trading_items: TradingItems | None = None
762
+
631
763
  def __str__(self) -> str:
632
764
  """String representation for the security object"""
633
765
  return f"{type(self).__module__}.{type(self).__qualname__} of {self.security_id}"
634
766
 
635
- @cached_property
767
+ @property
636
768
  def isin(self) -> str:
637
769
  """Get the ISIN for the object
638
770
 
639
771
  :return: The ISIN
640
772
  :rtype: str
641
773
  """
642
- return self.kfinance_api_client.fetch_isin(self.security_id)["isin"]
774
+ if self._isin is None:
775
+ self._isin = self.kfinance_api_client.fetch_isin(self.security_id)["isin"]
776
+ return self._isin
643
777
 
644
- @cached_property
778
+ @property
645
779
  def cusip(self) -> str:
646
780
  """Get the CUSIP for the object
647
781
 
648
782
  :return: The CUSIP
649
783
  :rtype: str
650
784
  """
651
- return self.kfinance_api_client.fetch_cusip(self.security_id)["cusip"]
785
+ if self._cusip is None:
786
+ self._cusip = self.kfinance_api_client.fetch_cusip(self.security_id)["cusip"]
787
+ return self._cusip
652
788
 
653
- @cached_property
789
+ @property
654
790
  def primary_trading_item(self) -> TradingItem:
655
791
  """Return the primary trading item for the Security object
656
792
 
657
793
  :return: a TradingItem object of the primary trading item of security_id
658
794
  :rtype: TradingItem
659
795
  """
660
- primary_trading_item_id = self.kfinance_api_client.fetch_primary_trading_item(
661
- self.security_id
662
- )["primary_trading_item"]
663
- self.primary_trading_item = TradingItem(
664
- kfinance_api_client=self.kfinance_api_client, trading_item_id=primary_trading_item_id
665
- )
666
- return self.primary_trading_item
796
+ if self._primary_trading_item is None:
797
+ primary_trading_item_id = self.kfinance_api_client.fetch_primary_trading_item(
798
+ self.security_id
799
+ )["primary_trading_item"]
800
+ self._primary_trading_item = TradingItem(
801
+ kfinance_api_client=self.kfinance_api_client,
802
+ trading_item_id=primary_trading_item_id,
803
+ )
804
+ return self._primary_trading_item
667
805
 
668
- @cached_property
806
+ @property
669
807
  def trading_items(self) -> TradingItems:
670
808
  """Return the trading items for the Security object
671
809
 
672
810
  :return: a TradingItems object containing the list of trading items of security_id
673
811
  :rtype: TradingItems
674
812
  """
675
- trading_item_ids = self.kfinance_api_client.fetch_trading_items(self.security_id)[
676
- "trading_items"
677
- ]
678
- self.trading_items = TradingItems(
679
- kfinance_api_client=self.kfinance_api_client, trading_item_ids=trading_item_ids
680
- )
681
- return self.trading_items
813
+ if self._trading_items is None:
814
+ trading_item_ids = self.kfinance_api_client.fetch_trading_items(self.security_id)[
815
+ "trading_items"
816
+ ]
817
+ self._trading_items = TradingItems(
818
+ kfinance_api_client=self.kfinance_api_client, trading_item_ids=trading_item_ids
819
+ )
820
+ return self._trading_items
682
821
 
683
822
 
684
823
  class Ticker(DelegatedCompanyFunctionsMetaClass):
@@ -740,6 +879,11 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
740
879
  "Neither an identifier nor an identification triple (company id, security id, & trading item id) were passed in"
741
880
  )
742
881
 
882
+ self._primary_security: Security | None = None
883
+ self._primary_trading_item: TradingItem | None = None
884
+ self._company: Company | None = None
885
+ self._history_metadata: HistoryMetadata | None = None
886
+
743
887
  @property
744
888
  def id_triple(self) -> IdentificationTriple:
745
889
  """Returns a unique identification triple for the Ticker object.
@@ -823,44 +967,46 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
823
967
  """
824
968
  return self.id_triple.trading_item_id
825
969
 
826
- @cached_property
970
+ @property
827
971
  def primary_security(self) -> Security:
828
972
  """Set and return the primary security for the object
829
973
 
830
974
  :return: The primary security as a Security object
831
975
  :rtype: Security
832
976
  """
977
+ if self._primary_security is None:
978
+ self._primary_security = Security(
979
+ kfinance_api_client=self.kfinance_api_client, security_id=self.security_id
980
+ )
981
+ return self._primary_security
833
982
 
834
- self.primary_security = Security(
835
- kfinance_api_client=self.kfinance_api_client, security_id=self.security_id
836
- )
837
- return self.primary_security
838
-
839
- @cached_property
983
+ @property
840
984
  def company(self) -> Company:
841
985
  """Set and return the company for the object
842
986
 
843
987
  :return: The company returned as Company object
844
988
  :rtype: Company
845
989
  """
846
- self.company = Company(
847
- kfinance_api_client=self.kfinance_api_client, company_id=self.company_id
848
- )
849
- return self.company
990
+ if self._company is None:
991
+ self._company = Company(
992
+ kfinance_api_client=self.kfinance_api_client, company_id=self.company_id
993
+ )
994
+ return self._company
850
995
 
851
- @cached_property
996
+ @property
852
997
  def primary_trading_item(self) -> TradingItem:
853
998
  """Set and return the trading item for the object
854
999
 
855
1000
  :return: The trading item returned as TradingItem object
856
1001
  :rtype: TradingItem
857
1002
  """
858
- self.primary_trading_item = TradingItem(
859
- kfinance_api_client=self.kfinance_api_client, trading_item_id=self.trading_item_id
860
- )
861
- return self.primary_trading_item
1003
+ if self._primary_trading_item is None:
1004
+ self._primary_trading_item = TradingItem(
1005
+ kfinance_api_client=self.kfinance_api_client, trading_item_id=self.trading_item_id
1006
+ )
1007
+ return self._primary_trading_item
862
1008
 
863
- @cached_property
1009
+ @property
864
1010
  def isin(self) -> str:
865
1011
  """Get the ISIN for the object
866
1012
 
@@ -873,7 +1019,7 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
873
1019
  self._isin = isin
874
1020
  return isin
875
1021
 
876
- @cached_property
1022
+ @property
877
1023
  def cusip(self) -> str:
878
1024
  """Get the CUSIP for the object
879
1025
 
@@ -886,7 +1032,7 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
886
1032
  self._cusip = cusip
887
1033
  return cusip
888
1034
 
889
- @cached_property
1035
+ @property
890
1036
  def info(self) -> dict:
891
1037
  """Get the company info for the ticker
892
1038
 
@@ -1012,7 +1158,7 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
1012
1158
  """
1013
1159
  return self.company.iso_country
1014
1160
 
1015
- @cached_property
1161
+ @property
1016
1162
  def earnings_call_datetimes(self) -> list[datetime]:
1017
1163
  """Get the datetimes of the companies earnings calls
1018
1164
 
@@ -1021,7 +1167,7 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
1021
1167
  """
1022
1168
  return self.company.earnings_call_datetimes
1023
1169
 
1024
- @cached_property
1170
+ @property
1025
1171
  def history_metadata(self) -> HistoryMetadata:
1026
1172
  """Get information about exchange and quotation
1027
1173
 
@@ -1035,7 +1181,7 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
1035
1181
  self._ticker = metadata["symbol"]
1036
1182
  return metadata
1037
1183
 
1038
- @cached_property
1184
+ @property
1039
1185
  def ticker(self) -> str:
1040
1186
  """Get the ticker if it isn't available from initialization"""
1041
1187
  if self._ticker is not None:
@@ -1111,20 +1257,146 @@ class BusinessRelationships(NamedTuple):
1111
1257
  return f"{type(self).__module__}.{type(self).__qualname__} of {str(dictionary)}"
1112
1258
 
1113
1259
 
1260
+ class MergerOrAcquisition:
1261
+ """An object that represents a merger or an acquisition of a company."""
1262
+
1263
+ def __init__(
1264
+ self, kfinance_api_client: KFinanceApiClient, transaction_id: int, merger_title: str | None
1265
+ ) -> None:
1266
+ """MergerOrAcqusition initializer.
1267
+
1268
+ :param kfinance_api_client: The KFinanceApiClient used to retrieve data.
1269
+ :type kfinance_api_client: KFinanceApiClient
1270
+
1271
+ """
1272
+ self.kfinance_api_client = kfinance_api_client
1273
+ self.transaction_id = transaction_id
1274
+ self.merger_title = merger_title
1275
+ self._merger_info: dict | None = None
1276
+
1277
+ @property
1278
+ def merger_info(self) -> dict:
1279
+ """Property for the combined information in the merger."""
1280
+ if not self._merger_info:
1281
+ self._merger_info = self.kfinance_api_client.fetch_merger_info(self.transaction_id)
1282
+ timeline = pd.DataFrame(self.merger_info["timeline"])
1283
+ timeline["date"] = pd.to_datetime(timeline["date"])
1284
+ self._merger_info["timeline"] = timeline
1285
+ details = pd.DataFrame(self.merger_info["consideration"]["details"])
1286
+ self._merger_info["consideration"]["details"] = details
1287
+ return self._merger_info
1288
+
1289
+ @property
1290
+ def get_merger_title(self) -> str | None:
1291
+ """The merger title includes the status of the merger and its target."""
1292
+ return self.merger_title
1293
+
1294
+ @property
1295
+ def get_timeline(self) -> pd.DataFrame:
1296
+ """The timeline of the merger includes every new status, along with the dates of each status change."""
1297
+ return self.merger_info["timeline"]
1298
+
1299
+ @property
1300
+ def get_participants(self) -> dict:
1301
+ """A merger's participants are organized into categories: the target, the buyer or buyers, and the seller or sellers.
1302
+
1303
+ Each category is a single Company or a list of Companies.
1304
+ """
1305
+ return {
1306
+ "target": AdvisedCompany(
1307
+ kfinance_api_client=self.kfinance_api_client,
1308
+ company_id=self.merger_info["participants"]["target"]["company_id"],
1309
+ company_name=self.merger_info["participants"]["target"]["company_name"],
1310
+ transaction_id=self.transaction_id,
1311
+ ),
1312
+ "buyers": Companies(
1313
+ kfinance_api_client=self.kfinance_api_client,
1314
+ companies=[
1315
+ AdvisedCompany(
1316
+ kfinance_api_client=self.kfinance_api_client,
1317
+ company_id=company["company_id"],
1318
+ company_name=company["company_name"],
1319
+ transaction_id=self.transaction_id,
1320
+ )
1321
+ for company in self.merger_info["participants"]["buyers"]
1322
+ ],
1323
+ ),
1324
+ "sellers": Companies(
1325
+ kfinance_api_client=self.kfinance_api_client,
1326
+ companies=[
1327
+ AdvisedCompany(
1328
+ kfinance_api_client=self.kfinance_api_client,
1329
+ company_id=company["company_id"],
1330
+ company_name=company["company_name"],
1331
+ transaction_id=self.transaction_id,
1332
+ )
1333
+ for company in self.merger_info["participants"]["sellers"]
1334
+ ],
1335
+ ),
1336
+ }
1337
+
1338
+ @property
1339
+ def get_consideration(self) -> dict:
1340
+ """A merger's consideration is the assets exchanged for the target company.
1341
+
1342
+ Properties in the consideration include:
1343
+ - The currency of the consideration.
1344
+ - The current gross total value of the consideration.
1345
+ - The current implied equity value of the consideration.
1346
+ - The current implied enterprise value of the consideration.
1347
+ - A list of the consideration's details (sub-components of the consideration), where each consideration detail contains:
1348
+ - The detail scenario.
1349
+ - The detail subtype.
1350
+ - The cash or cash equivalent offered per share.
1351
+ - The number of shares in the target company.
1352
+ - The current gross total of the consideration detail.
1353
+ """
1354
+ return self.merger_info["consideration"]
1355
+
1356
+
1114
1357
  @add_methods_of_singular_class_to_iterable_class(Company)
1115
1358
  class Companies(set):
1116
1359
  """Base class for representing a set of Companies"""
1117
1360
 
1118
- def __init__(self, kfinance_api_client: KFinanceApiClient, company_ids: Iterable[int]) -> None:
1361
+ def __init__(
1362
+ self,
1363
+ kfinance_api_client: KFinanceApiClient,
1364
+ company_ids: Optional[Iterable[int]] = None,
1365
+ transaction_id: Optional[int] = None,
1366
+ companies: Optional[Iterable[Company]] = None,
1367
+ ) -> None:
1119
1368
  """Initialize the Companies object
1120
1369
 
1121
1370
  :param kfinance_api_client: The KFinanceApiClient used to fetch data
1122
1371
  :type kfinance_api_client: KFinanceApiClient
1123
1372
  :param company_ids: An iterable of S&P CIQ Company ids
1124
1373
  :type company_ids: Iterable[int]
1374
+ :param transaction_id: If the companies were party to a transaction, the S&P CIQ Transaction Id
1375
+ :type transaction_id: Optional[int]
1376
+ :param companies: If there's already an iterable of Company objects
1377
+ :type companies: Iterable[Company]
1125
1378
  """
1126
1379
  self.kfinance_api_client = kfinance_api_client
1127
- super().__init__(Company(kfinance_api_client, company_id) for company_id in company_ids)
1380
+ if companies is not None:
1381
+ super().__init__(company for company in companies)
1382
+ elif company_ids is not None:
1383
+ if transaction_id is not None:
1384
+ super().__init__(
1385
+ AdvisedCompany(
1386
+ kfinance_api_client=kfinance_api_client,
1387
+ company_id=company_id,
1388
+ transaction_id=transaction_id,
1389
+ )
1390
+ for company_id in company_ids
1391
+ )
1392
+ else:
1393
+ super().__init__(
1394
+ Company(
1395
+ kfinance_api_client=kfinance_api_client,
1396
+ company_id=company_id,
1397
+ )
1398
+ for company_id in company_ids
1399
+ )
1128
1400
 
1129
1401
 
1130
1402
  @add_methods_of_singular_class_to_iterable_class(Security)
@@ -1241,6 +1513,29 @@ class Tickers(set):
1241
1513
  )
1242
1514
 
1243
1515
 
1516
+ @add_methods_of_singular_class_to_iterable_class(MergerOrAcquisition)
1517
+ class MergersAndAcquisitions(set):
1518
+ def __init__(
1519
+ self, kfinance_api_client: KFinanceApiClient, ids_and_titles: Iterable[dict]
1520
+ ) -> None:
1521
+ """MergersAndAcquisitions initializer.
1522
+
1523
+ :param kfinance_api_client: The KFinanceApiClient used to fetch data.
1524
+ :type kfinance_api_client: KFinanceApiClient
1525
+ :param ids_and_titles: A iterable of transaction IDs and merger titles.
1526
+ :type ids_and_titles: Iterable[dict]
1527
+ """
1528
+ self.kfinance_api_client = kfinance_api_client
1529
+ super().__init__(
1530
+ MergerOrAcquisition(
1531
+ kfinance_api_client=kfinance_api_client,
1532
+ transaction_id=id_and_title["transaction_id"],
1533
+ merger_title=id_and_title["merger_title"],
1534
+ )
1535
+ for id_and_title in ids_and_titles
1536
+ )
1537
+
1538
+
1244
1539
  class Client:
1245
1540
  """Client class with LLM tools and a pre-credentialed Ticker object
1246
1541
 
@@ -1561,6 +1856,24 @@ class Client:
1561
1856
  transcript_data = self.kfinance_api_client.fetch_transcript(key_dev_id)
1562
1857
  return Transcript(transcript_data["transcript"])
1563
1858
 
1859
+ def mergers_and_acquisitions(self, company_id: int) -> dict[str, MergersAndAcquisitions]:
1860
+ """Generate 3 named lists of MergersAndAcquisitions objects from company_id.
1861
+
1862
+ :param company_id: S&P Global company ID
1863
+ :type company_id: int
1864
+ :return: A dictionary of three keys ('target', 'buyer', and 'seller'), each of whose values is a MergersAndAcquisitions.
1865
+ :rtype: dict[str, MergersAndAcquisitions]
1866
+ """
1867
+ mergers_for_company = self.kfinance_api_client.fetch_mergers_for_company(
1868
+ company_id=company_id
1869
+ )
1870
+ output: dict = {}
1871
+ for literal in ["target", "buyer", "seller"]:
1872
+ output[literal] = MergersAndAcquisitions(
1873
+ self.kfinance_api_client, mergers_for_company[literal]
1874
+ )
1875
+ return output
1876
+
1564
1877
  @staticmethod
1565
1878
  def get_latest(use_local_timezone: bool = True) -> LatestPeriods:
1566
1879
  """Get the latest annual reporting year, latest quarterly reporting quarter and year, and current date.