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.
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/METADATA +1 -1
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/RECORD +29 -26
- kfinance/CHANGELOG.md +15 -0
- kfinance/batch_request_handling.py +2 -16
- kfinance/constants.py +14 -2
- kfinance/fetch.py +56 -0
- kfinance/kfinance.py +403 -90
- kfinance/mcp.py +23 -35
- kfinance/meta_classes.py +37 -12
- kfinance/tests/conftest.py +4 -0
- kfinance/tests/scratch.py +0 -0
- kfinance/tests/test_batch_requests.py +0 -32
- kfinance/tests/test_fetch.py +21 -0
- kfinance/tests/test_objects.py +239 -3
- kfinance/tests/test_tools.py +136 -24
- kfinance/tool_calling/__init__.py +2 -4
- kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +39 -0
- kfinance/tool_calling/get_competitors_from_identifier.py +24 -0
- kfinance/tool_calling/get_financial_line_item_from_identifier.py +3 -3
- kfinance/tool_calling/get_financial_statement_from_identifier.py +3 -3
- kfinance/tool_calling/get_merger_info_from_transaction_id.py +62 -0
- kfinance/tool_calling/get_mergers_from_identifier.py +41 -0
- kfinance/tool_calling/get_segments_from_identifier.py +3 -3
- kfinance/tool_calling/shared_models.py +17 -2
- kfinance/version.py +2 -2
- kfinance/tests/test_mcp.py +0 -16
- kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +0 -19
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/WHEEL +0 -0
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
84
|
-
self.
|
|
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.
|
|
108
|
-
trading_item.exchange_code = exchange_code
|
|
107
|
+
trading_item._ticker = ticker
|
|
109
108
|
return trading_item
|
|
110
109
|
|
|
111
|
-
@
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
self.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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__(
|
|
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.
|
|
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
|
-
@
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
661
|
-
self.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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.
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
@
|
|
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.
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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__(
|
|
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
|
-
|
|
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.
|