kensho-kfinance 2.4.3__py3-none-any.whl → 2.6.2__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.3.dist-info → kensho_kfinance-2.6.2.dist-info}/METADATA +38 -5
- {kensho_kfinance-2.4.3.dist-info → kensho_kfinance-2.6.2.dist-info}/RECORD +23 -20
- kfinance/CHANGELOG.md +18 -0
- kfinance/batch_request_handling.py +2 -16
- kfinance/constants.py +14 -0
- kfinance/fetch.py +56 -0
- kfinance/kfinance.py +409 -90
- kfinance/meta_classes.py +37 -12
- kfinance/tests/conftest.py +4 -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 +101 -23
- 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_merger_info_from_transaction_id.py +68 -0
- kfinance/tool_calling/get_mergers_from_identifier.py +41 -0
- kfinance/version.py +2 -2
- kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +0 -19
- {kensho_kfinance-2.4.3.dist-info → kensho_kfinance-2.6.2.dist-info}/WHEEL +0 -0
- {kensho_kfinance-2.4.3.dist-info → kensho_kfinance-2.6.2.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.4.3.dist-info → kensho_kfinance-2.6.2.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-2.4.3.dist-info → kensho_kfinance-2.6.2.dist-info}/top_level.txt +0 -0
kfinance/meta_classes.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
1
2
|
from datetime import datetime
|
|
2
|
-
from functools import cached_property
|
|
3
3
|
import logging
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional
|
|
5
5
|
|
|
@@ -7,13 +7,19 @@ from cachetools import LRUCache, cached
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
9
9
|
|
|
10
|
-
from .constants import
|
|
10
|
+
from .constants import (
|
|
11
|
+
LINE_ITEMS,
|
|
12
|
+
BusinessRelationshipType,
|
|
13
|
+
CompetitorSource,
|
|
14
|
+
PeriodType,
|
|
15
|
+
SegmentType,
|
|
16
|
+
)
|
|
11
17
|
from .fetch import KFinanceApiClient
|
|
12
18
|
from .pydantic_models import RelationshipResponse
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
if TYPE_CHECKING:
|
|
16
|
-
from .kfinance import BusinessRelationships
|
|
22
|
+
from .kfinance import BusinessRelationships, Companies
|
|
17
23
|
|
|
18
24
|
logger = logging.getLogger(__name__)
|
|
19
25
|
|
|
@@ -21,7 +27,8 @@ logger = logging.getLogger(__name__)
|
|
|
21
27
|
class CompanyFunctionsMetaClass:
|
|
22
28
|
kfinance_api_client: KFinanceApiClient
|
|
23
29
|
|
|
24
|
-
@
|
|
30
|
+
@property
|
|
31
|
+
@abstractmethod
|
|
25
32
|
def company_id(self) -> Any:
|
|
26
33
|
"""Set and return the company id for the object"""
|
|
27
34
|
raise NotImplementedError("child classes must implement company id property")
|
|
@@ -213,6 +220,7 @@ class CompanyFunctionsMetaClass:
|
|
|
213
220
|
.set_index(pd.Index([line_item]))
|
|
214
221
|
)
|
|
215
222
|
|
|
223
|
+
@cached(cache=LRUCache(maxsize=100))
|
|
216
224
|
def relationships(self, relationship_type: BusinessRelationshipType) -> "BusinessRelationships":
|
|
217
225
|
"""Returns a BusinessRelationships object that includes the current and previous Companies associated with company_id and filtered by relationship_type. The function calls fetch_companies_from_business_relationship.
|
|
218
226
|
|
|
@@ -416,6 +424,24 @@ class CompanyFunctionsMetaClass:
|
|
|
416
424
|
end_quarter=end_quarter,
|
|
417
425
|
)
|
|
418
426
|
|
|
427
|
+
def competitors(
|
|
428
|
+
self, competitor_source: CompetitorSource = CompetitorSource.all
|
|
429
|
+
) -> "Companies":
|
|
430
|
+
"""Get the list of companies that are competitors of company_id, optionally filtered by the competitor_source type.
|
|
431
|
+
|
|
432
|
+
:return: The list of companies that are competitors of company_id, optionally filtered by the competitor_source type
|
|
433
|
+
:rtype: Companies
|
|
434
|
+
"""
|
|
435
|
+
from .kfinance import Companies
|
|
436
|
+
|
|
437
|
+
competitors_data = self.kfinance_api_client.fetch_competitors(
|
|
438
|
+
company_id=self.company_id, competitor_source=competitor_source
|
|
439
|
+
)["companies"]
|
|
440
|
+
return Companies(
|
|
441
|
+
kfinance_api_client=self.kfinance_api_client,
|
|
442
|
+
company_ids=[company["company_id"] for company in competitors_data],
|
|
443
|
+
)
|
|
444
|
+
|
|
419
445
|
|
|
420
446
|
for line_item in LINE_ITEMS:
|
|
421
447
|
line_item_name = line_item["name"]
|
|
@@ -494,7 +520,7 @@ class DelegatedCompanyFunctionsMetaClass(CompanyFunctionsMetaClass):
|
|
|
494
520
|
delegated_function(company_function_name),
|
|
495
521
|
)
|
|
496
522
|
|
|
497
|
-
@
|
|
523
|
+
@property
|
|
498
524
|
def company(self) -> Any:
|
|
499
525
|
"""Set and return the company for the object"""
|
|
500
526
|
raise NotImplementedError("child classes must implement company property")
|
|
@@ -502,8 +528,8 @@ class DelegatedCompanyFunctionsMetaClass(CompanyFunctionsMetaClass):
|
|
|
502
528
|
|
|
503
529
|
for relationship in BusinessRelationshipType:
|
|
504
530
|
|
|
505
|
-
def _relationship_outer_wrapper(relationship_type: BusinessRelationshipType) ->
|
|
506
|
-
"""Creates a
|
|
531
|
+
def _relationship_outer_wrapper(relationship_type: BusinessRelationshipType) -> property:
|
|
532
|
+
"""Creates a property for a relationship type.
|
|
507
533
|
|
|
508
534
|
This function returns a property that retrieves the associated company's current and previous
|
|
509
535
|
relationships of the specified type.
|
|
@@ -512,7 +538,7 @@ for relationship in BusinessRelationshipType:
|
|
|
512
538
|
relationship_type (BusinessRelationshipType): The type of relationship to be wrapped.
|
|
513
539
|
|
|
514
540
|
Returns:
|
|
515
|
-
property: A
|
|
541
|
+
property: A property that calls the inner wrapper to retrieve the relationship data.
|
|
516
542
|
"""
|
|
517
543
|
|
|
518
544
|
def relationship_inner_wrapper(
|
|
@@ -533,12 +559,11 @@ for relationship in BusinessRelationshipType:
|
|
|
533
559
|
relationship_inner_wrapper.__doc__ = doc
|
|
534
560
|
relationship_inner_wrapper.__name__ = relationship
|
|
535
561
|
|
|
536
|
-
return
|
|
562
|
+
return property(relationship_inner_wrapper)
|
|
537
563
|
|
|
538
|
-
|
|
539
|
-
relationship_cached_property.__set_name__(CompanyFunctionsMetaClass, relationship)
|
|
564
|
+
relationship_property = _relationship_outer_wrapper(relationship)
|
|
540
565
|
setattr(
|
|
541
566
|
CompanyFunctionsMetaClass,
|
|
542
567
|
relationship,
|
|
543
|
-
|
|
568
|
+
relationship_property,
|
|
544
569
|
)
|
kfinance/tests/conftest.py
CHANGED
|
@@ -29,4 +29,8 @@ def mock_client(requests_mock: Mocker) -> Client:
|
|
|
29
29
|
"company_id": SPGI_COMPANY_ID,
|
|
30
30
|
},
|
|
31
31
|
)
|
|
32
|
+
requests_mock.get(
|
|
33
|
+
url="https://kfinance.kensho.com/api/v1/id/MSFT",
|
|
34
|
+
json={"trading_item_id": 2630413, "security_id": 2630412, "company_id": 21835},
|
|
35
|
+
)
|
|
32
36
|
return client
|
|
@@ -67,38 +67,6 @@ class TestTradingItem(TestCase):
|
|
|
67
67
|
expected_id_based_result = {1001: "Mock City A", 1002: "Mock City B"}
|
|
68
68
|
self.assertDictEqual(id_based_result, expected_id_based_result)
|
|
69
69
|
|
|
70
|
-
@requests_mock.Mocker()
|
|
71
|
-
def test_batch_request_cached_properties(self, m):
|
|
72
|
-
"""GIVEN a kfinance group object like Companies
|
|
73
|
-
WHEN we batch request a cached property for each object in the group
|
|
74
|
-
THEN the batch request completes successfully and we get back a mapping of
|
|
75
|
-
company objects to the corresponding values."""
|
|
76
|
-
|
|
77
|
-
m.get(
|
|
78
|
-
"https://kfinance.kensho.com/api/v1/securities/1001",
|
|
79
|
-
json={"securities": [101, 102, 103]},
|
|
80
|
-
)
|
|
81
|
-
m.get(
|
|
82
|
-
"https://kfinance.kensho.com/api/v1/securities/1002",
|
|
83
|
-
json={"securities": [104, 105, 106, 107]},
|
|
84
|
-
)
|
|
85
|
-
m.get("https://kfinance.kensho.com/api/v1/securities/1005", json={"securities": [108, 109]})
|
|
86
|
-
|
|
87
|
-
companies = Companies(self.kfinance_api_client, [1001, 1002, 1005])
|
|
88
|
-
result = companies.securities
|
|
89
|
-
|
|
90
|
-
id_based_result = self.company_object_keys_as_company_id(result)
|
|
91
|
-
for k, v in id_based_result.items():
|
|
92
|
-
id_based_result[k] = set(map(lambda s: s.security_id, v))
|
|
93
|
-
|
|
94
|
-
expected_id_based_result = {
|
|
95
|
-
1001: set([101, 102, 103]),
|
|
96
|
-
1002: set([104, 105, 106, 107]),
|
|
97
|
-
1005: set([108, 109]),
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
self.assertDictEqual(id_based_result, expected_id_based_result)
|
|
101
|
-
|
|
102
70
|
@requests_mock.Mocker()
|
|
103
71
|
def test_batch_request_function(self, m):
|
|
104
72
|
"""GIVEN a kfinance group object like TradingItems
|
kfinance/tests/test_fetch.py
CHANGED
|
@@ -272,6 +272,27 @@ class TestFetchItem(TestCase):
|
|
|
272
272
|
)
|
|
273
273
|
self.kfinance_api_client.fetch.assert_called_with(expected_fetch_url)
|
|
274
274
|
|
|
275
|
+
def test_fetch_mergers_for_company(self) -> None:
|
|
276
|
+
company_id = 21719
|
|
277
|
+
expected_fetch_url = f"{self.kfinance_api_client.url_base}mergers/{company_id}"
|
|
278
|
+
self.kfinance_api_client.fetch_mergers_for_company(company_id=company_id)
|
|
279
|
+
self.kfinance_api_client.fetch.assert_called_with(expected_fetch_url)
|
|
280
|
+
|
|
281
|
+
def test_fetch_merger_info(self) -> None:
|
|
282
|
+
transaction_id = 554979212
|
|
283
|
+
expected_fetch_url = f"{self.kfinance_api_client.url_base}merger/info/{transaction_id}"
|
|
284
|
+
self.kfinance_api_client.fetch_merger_info(transaction_id=transaction_id)
|
|
285
|
+
self.kfinance_api_client.fetch.assert_called_with(expected_fetch_url)
|
|
286
|
+
|
|
287
|
+
def test_fetch_advisors_for_company_in_merger(self) -> None:
|
|
288
|
+
transaction_id = 554979212
|
|
289
|
+
advised_company_id = 251994106
|
|
290
|
+
expected_fetch_url = f"{self.kfinance_api_client.url_base}merger/info/{transaction_id}/advisors/{advised_company_id}"
|
|
291
|
+
self.kfinance_api_client.fetch_advisors_for_company_in_merger(
|
|
292
|
+
transaction_id=transaction_id, advised_company_id=advised_company_id
|
|
293
|
+
)
|
|
294
|
+
self.kfinance_api_client.fetch.assert_called_with(expected_fetch_url)
|
|
295
|
+
|
|
275
296
|
|
|
276
297
|
class TestMarketCap:
|
|
277
298
|
@pytest.mark.parametrize(
|
kfinance/tests/test_objects.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
from datetime import date, datetime, timezone
|
|
2
3
|
from io import BytesIO
|
|
3
4
|
import re
|
|
@@ -9,7 +10,19 @@ import pandas as pd
|
|
|
9
10
|
from PIL.Image import open as image_open
|
|
10
11
|
import time_machine
|
|
11
12
|
|
|
12
|
-
from kfinance.
|
|
13
|
+
from kfinance.constants import BusinessRelationshipType
|
|
14
|
+
from kfinance.kfinance import (
|
|
15
|
+
AdvisedCompany,
|
|
16
|
+
BusinessRelationships,
|
|
17
|
+
Company,
|
|
18
|
+
Earnings,
|
|
19
|
+
MergerOrAcquisition,
|
|
20
|
+
Security,
|
|
21
|
+
Ticker,
|
|
22
|
+
TradingItem,
|
|
23
|
+
Transcript,
|
|
24
|
+
)
|
|
25
|
+
from kfinance.pydantic_models import CompanyIdAndName, RelationshipResponse
|
|
13
26
|
|
|
14
27
|
|
|
15
28
|
msft_company_id = "21835"
|
|
@@ -17,6 +30,7 @@ msft_security_id = "2630412"
|
|
|
17
30
|
msft_isin = "US5949181045"
|
|
18
31
|
msft_cusip = "594918104"
|
|
19
32
|
msft_trading_item_id = "2630413"
|
|
33
|
+
msft_buys_mongo = "517414"
|
|
20
34
|
|
|
21
35
|
|
|
22
36
|
MOCK_TRADING_ITEM_DB = {
|
|
@@ -108,7 +122,47 @@ MOCK_COMPANY_DB = {
|
|
|
108
122
|
},
|
|
109
123
|
}
|
|
110
124
|
},
|
|
111
|
-
|
|
125
|
+
"mergers": {
|
|
126
|
+
"target": [
|
|
127
|
+
{"transaction_id": 10998717, "merger_title": "Closed M/A of Microsoft Corporation"},
|
|
128
|
+
{"transaction_id": 28237969, "merger_title": "Closed M/A of Microsoft Corporation"},
|
|
129
|
+
],
|
|
130
|
+
"buyer": [
|
|
131
|
+
{"transaction_id": 517414, "merger_title": "Closed M/A of MongoMusic, Inc."},
|
|
132
|
+
{"transaction_id": 596722, "merger_title": "Closed M/A of Digital Anvil, Inc."},
|
|
133
|
+
],
|
|
134
|
+
"seller": [
|
|
135
|
+
{"transaction_id": 455551, "merger_title": "Closed M/A of VacationSpot.com, Inc."},
|
|
136
|
+
{"transaction_id": 456045, "merger_title": "Closed M/A of TransPoint, LLC"},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
"advisors": {
|
|
140
|
+
msft_buys_mongo: {
|
|
141
|
+
"advisors": [
|
|
142
|
+
{
|
|
143
|
+
"advisor_company_id": 251994106,
|
|
144
|
+
"advisor_company_name": "Kensho Technologies, Inc.",
|
|
145
|
+
"advisor_type_name": "Professional Mongo Enjoyer",
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
BusinessRelationshipType.supplier: RelationshipResponse(
|
|
151
|
+
current=[CompanyIdAndName(company_name="foo", company_id=883103)],
|
|
152
|
+
previous=[
|
|
153
|
+
CompanyIdAndName(company_name="bar", company_id=472898),
|
|
154
|
+
CompanyIdAndName(company_name="baz", company_id=8182358),
|
|
155
|
+
],
|
|
156
|
+
),
|
|
157
|
+
},
|
|
158
|
+
31696: {"info": {"name": "MongoMusic, Inc."}},
|
|
159
|
+
21835: {"info": {"name": "Microsoft Corporation"}},
|
|
160
|
+
18805: {"info": {"name": "Angel Investors L.P."}},
|
|
161
|
+
20087: {"info": {"name": "Draper Richards, L.P."}},
|
|
162
|
+
22103: {"info": {"name": "BRV Partners, LLC"}},
|
|
163
|
+
23745: {"info": {"name": "Venture Frogs, LLC"}},
|
|
164
|
+
105902: {"info": {"name": "ARGUS Capital International Limited"}},
|
|
165
|
+
880300: {"info": {"name": "Sony Music Entertainment, Inc."}},
|
|
112
166
|
}
|
|
113
167
|
|
|
114
168
|
MOCK_TRANSCRIPT_DB = {
|
|
@@ -165,6 +219,51 @@ MOCK_CUSIP_DB = {
|
|
|
165
219
|
}
|
|
166
220
|
}
|
|
167
221
|
|
|
222
|
+
MOCK_MERGERS_DB = {
|
|
223
|
+
msft_buys_mongo: {
|
|
224
|
+
"timeline": [
|
|
225
|
+
{"status": "Announced", "date": "2000-09-12"},
|
|
226
|
+
{"status": "Closed", "date": "2000-09-12"},
|
|
227
|
+
],
|
|
228
|
+
"participants": {
|
|
229
|
+
"target": {"company_id": 31696, "company_name": "MongoMusic, Inc."},
|
|
230
|
+
"buyers": [{"company_id": 21835, "company_name": "Microsoft Corporation"}],
|
|
231
|
+
"sellers": [
|
|
232
|
+
{"company_id": 18805, "company_name": "Angel Investors L.P."},
|
|
233
|
+
{"company_id": 20087, "company_name": "Draper Richards, L.P."},
|
|
234
|
+
{"company_id": 22103, "company_name": "BRV Partners, LLC"},
|
|
235
|
+
{"company_id": 23745, "company_name": "Venture Frogs, LLC"},
|
|
236
|
+
{"company_id": 105902, "company_name": "ARGUS Capital International Limited"},
|
|
237
|
+
{"company_id": 880300, "company_name": "Sony Music Entertainment, Inc."},
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
"consideration": {
|
|
241
|
+
"currency_name": "US Dollar",
|
|
242
|
+
"current_calculated_gross_total_transaction_value": "51609375.000000",
|
|
243
|
+
"current_calculated_implied_equity_value": "51609375.000000",
|
|
244
|
+
"current_calculated_implied_enterprise_value": "51609375.000000",
|
|
245
|
+
"details": [
|
|
246
|
+
{
|
|
247
|
+
"scenario": "Stock Lump Sum",
|
|
248
|
+
"subtype": "Common Equity",
|
|
249
|
+
"cash_or_cash_equivalent_per_target_share_unit": None,
|
|
250
|
+
"number_of_target_shares_sought": "1000000.000000",
|
|
251
|
+
"current_calculated_gross_value_of_consideration": "51609375.000000",
|
|
252
|
+
}
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def ordered(obj):
|
|
260
|
+
if isinstance(obj, dict):
|
|
261
|
+
return sorted((k, ordered(v)) for k, v in obj.items())
|
|
262
|
+
if isinstance(obj, list):
|
|
263
|
+
return sorted(ordered(x) for x in obj)
|
|
264
|
+
else:
|
|
265
|
+
return obj
|
|
266
|
+
|
|
168
267
|
|
|
169
268
|
class MockKFinanceApiClient:
|
|
170
269
|
def __init__(self):
|
|
@@ -261,6 +360,11 @@ class MockKFinanceApiClient:
|
|
|
261
360
|
"""Get a segment"""
|
|
262
361
|
return MOCK_COMPANY_DB[company_id]
|
|
263
362
|
|
|
363
|
+
def fetch_companies_from_business_relationship(
|
|
364
|
+
self, company_id: int, relationship_type: BusinessRelationshipType
|
|
365
|
+
) -> RelationshipResponse:
|
|
366
|
+
return MOCK_COMPANY_DB[company_id][relationship_type]
|
|
367
|
+
|
|
264
368
|
def fetch_earnings(self, company_id: int) -> dict:
|
|
265
369
|
"""Get the earnings for a company."""
|
|
266
370
|
return MOCK_COMPANY_DB[company_id]["earnings"]
|
|
@@ -269,6 +373,15 @@ class MockKFinanceApiClient:
|
|
|
269
373
|
"""Get the transcript for an earnings item."""
|
|
270
374
|
return MOCK_TRANSCRIPT_DB[key_dev_id]
|
|
271
375
|
|
|
376
|
+
def fetch_mergers_for_company(self, company_id):
|
|
377
|
+
return copy.deepcopy(MOCK_COMPANY_DB[company_id]["mergers"])
|
|
378
|
+
|
|
379
|
+
def fetch_merger_info(self, transaction_id):
|
|
380
|
+
return copy.deepcopy(MOCK_MERGERS_DB[transaction_id])
|
|
381
|
+
|
|
382
|
+
def fetch_advisors_for_company_in_merger(self, transaction_id, advised_company_id):
|
|
383
|
+
return copy.deepcopy(MOCK_COMPANY_DB[advised_company_id]["advisors"][transaction_id])
|
|
384
|
+
|
|
272
385
|
|
|
273
386
|
class TestTradingItem(TestCase):
|
|
274
387
|
def setUp(self):
|
|
@@ -327,7 +440,9 @@ class TestCompany(TestCase):
|
|
|
327
440
|
def setUp(self):
|
|
328
441
|
"""setup tests"""
|
|
329
442
|
self.kfinance_api_client = MockKFinanceApiClient()
|
|
330
|
-
self.msft_company =
|
|
443
|
+
self.msft_company = AdvisedCompany(
|
|
444
|
+
self.kfinance_api_client, company_id=msft_company_id, transaction_id=msft_buys_mongo
|
|
445
|
+
)
|
|
331
446
|
|
|
332
447
|
def test_company_id(self) -> None:
|
|
333
448
|
"""test company id"""
|
|
@@ -396,6 +511,67 @@ class TestCompany(TestCase):
|
|
|
396
511
|
business_segment = self.msft_company.business_segments()
|
|
397
512
|
self.assertEqual(expected_segments, business_segment)
|
|
398
513
|
|
|
514
|
+
def test_relationships(self) -> None:
|
|
515
|
+
"""
|
|
516
|
+
WHEN we fetch the relationships of a company
|
|
517
|
+
THEN we get back a BusinessRelationships object.
|
|
518
|
+
"""
|
|
519
|
+
|
|
520
|
+
expected_suppliers = MOCK_COMPANY_DB[msft_company_id][BusinessRelationshipType.supplier]
|
|
521
|
+
|
|
522
|
+
suppliers_via_method = self.msft_company.relationships(BusinessRelationshipType.supplier)
|
|
523
|
+
self.assertIsInstance(suppliers_via_method, BusinessRelationships)
|
|
524
|
+
# Company ids should match
|
|
525
|
+
self.assertEqual(
|
|
526
|
+
sorted([c.company_id for c in suppliers_via_method.current]),
|
|
527
|
+
sorted([c.company_id for c in expected_suppliers.current]),
|
|
528
|
+
)
|
|
529
|
+
self.assertEqual(
|
|
530
|
+
sorted([c.company_id for c in suppliers_via_method.previous]),
|
|
531
|
+
sorted([c.company_id for c in expected_suppliers.previous]),
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# Fetching via property should return the same result
|
|
535
|
+
suppliers_via_property = self.msft_company.supplier
|
|
536
|
+
self.assertEqual(suppliers_via_property, suppliers_via_method)
|
|
537
|
+
|
|
538
|
+
def test_mergers(self) -> None:
|
|
539
|
+
expected_mergers = MOCK_COMPANY_DB[msft_company_id]["mergers"]
|
|
540
|
+
mergers = self.msft_company.mergers_and_acquisitions
|
|
541
|
+
mergers_json = {
|
|
542
|
+
"target": [
|
|
543
|
+
{"transaction_id": merger.transaction_id, "merger_title": merger.merger_title}
|
|
544
|
+
for merger in mergers["target"]
|
|
545
|
+
],
|
|
546
|
+
"buyer": [
|
|
547
|
+
{"transaction_id": merger.transaction_id, "merger_title": merger.merger_title}
|
|
548
|
+
for merger in mergers["buyer"]
|
|
549
|
+
],
|
|
550
|
+
"seller": [
|
|
551
|
+
{"transaction_id": merger.transaction_id, "merger_title": merger.merger_title}
|
|
552
|
+
for merger in mergers["seller"]
|
|
553
|
+
],
|
|
554
|
+
}
|
|
555
|
+
self.assertEqual(ordered(expected_mergers), ordered(mergers_json))
|
|
556
|
+
|
|
557
|
+
def test_advisors(self) -> None:
|
|
558
|
+
expected_advisors_json = MOCK_COMPANY_DB[msft_company_id]["advisors"][msft_buys_mongo][
|
|
559
|
+
"advisors"
|
|
560
|
+
]
|
|
561
|
+
expected_company_ids: list[int] = []
|
|
562
|
+
expected_advisor_type_names: list[str] = []
|
|
563
|
+
for advisor in expected_advisors_json:
|
|
564
|
+
expected_company_ids.append(int(advisor["advisor_company_id"]))
|
|
565
|
+
expected_advisor_type_names.append(str(advisor["advisor_type_name"]))
|
|
566
|
+
advisors = self.msft_company.advisors
|
|
567
|
+
company_ids: list[int] = []
|
|
568
|
+
advisor_type_names: list[str] = []
|
|
569
|
+
for advisor in advisors:
|
|
570
|
+
company_ids.append(advisor.company_id)
|
|
571
|
+
advisor_type_names.append(advisor.advisor_type_name)
|
|
572
|
+
self.assertListEqual(expected_company_ids, company_ids)
|
|
573
|
+
self.assertListEqual(expected_advisor_type_names, advisor_type_names)
|
|
574
|
+
|
|
399
575
|
|
|
400
576
|
class TestSecurity(TestCase):
|
|
401
577
|
def setUp(self):
|
|
@@ -782,3 +958,63 @@ class TestCompanyEarnings(TestCase):
|
|
|
782
958
|
"""test company next_earnings property"""
|
|
783
959
|
next_earnings = self.msft_company.next_earnings
|
|
784
960
|
self.assertEqual(next_earnings.key_dev_id, 1916266380)
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
class TestMerger(TestCase):
|
|
964
|
+
def setUp(self):
|
|
965
|
+
self.kfinance_api_client = MockKFinanceApiClient()
|
|
966
|
+
self.merger = MergerOrAcquisition(
|
|
967
|
+
self.kfinance_api_client,
|
|
968
|
+
transaction_id=msft_buys_mongo,
|
|
969
|
+
merger_title="Closed M/A of MongoMusic, Inc.",
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
def test_merger_info(self) -> None:
|
|
973
|
+
expected_merger_info = MOCK_MERGERS_DB[msft_buys_mongo]
|
|
974
|
+
merger_info = {
|
|
975
|
+
"timeline": [
|
|
976
|
+
{"status": timeline["status"], "date": timeline["date"].strftime("%Y-%m-%d")}
|
|
977
|
+
for timeline in self.merger.get_timeline.to_dict(orient="records")
|
|
978
|
+
],
|
|
979
|
+
"participants": {
|
|
980
|
+
"target": {
|
|
981
|
+
"company_id": self.merger.get_participants["target"].company_id,
|
|
982
|
+
"company_name": self.merger.get_participants["target"].name,
|
|
983
|
+
},
|
|
984
|
+
"buyers": [
|
|
985
|
+
{"company_id": company.company_id, "company_name": company.name}
|
|
986
|
+
for company in self.merger.get_participants["buyers"]
|
|
987
|
+
],
|
|
988
|
+
"sellers": [
|
|
989
|
+
{"company_id": company.company_id, "company_name": company.name}
|
|
990
|
+
for company in self.merger.get_participants["sellers"]
|
|
991
|
+
],
|
|
992
|
+
},
|
|
993
|
+
"consideration": {
|
|
994
|
+
"currency_name": self.merger.get_consideration["currency_name"],
|
|
995
|
+
"current_calculated_gross_total_transaction_value": self.merger.get_consideration[
|
|
996
|
+
"current_calculated_gross_total_transaction_value"
|
|
997
|
+
],
|
|
998
|
+
"current_calculated_implied_equity_value": self.merger.get_consideration[
|
|
999
|
+
"current_calculated_implied_equity_value"
|
|
1000
|
+
],
|
|
1001
|
+
"current_calculated_implied_enterprise_value": self.merger.get_consideration[
|
|
1002
|
+
"current_calculated_implied_enterprise_value"
|
|
1003
|
+
],
|
|
1004
|
+
"details": [
|
|
1005
|
+
{
|
|
1006
|
+
"scenario": detail["scenario"],
|
|
1007
|
+
"subtype": detail["subtype"],
|
|
1008
|
+
"cash_or_cash_equivalent_per_target_share_unit": detail[
|
|
1009
|
+
"cash_or_cash_equivalent_per_target_share_unit"
|
|
1010
|
+
],
|
|
1011
|
+
"number_of_target_shares_sought": detail["number_of_target_shares_sought"],
|
|
1012
|
+
"current_calculated_gross_value_of_consideration": detail[
|
|
1013
|
+
"current_calculated_gross_value_of_consideration"
|
|
1014
|
+
],
|
|
1015
|
+
}
|
|
1016
|
+
for detail in self.merger.get_consideration["details"].to_dict(orient="records")
|
|
1017
|
+
],
|
|
1018
|
+
},
|
|
1019
|
+
}
|
|
1020
|
+
self.assertEqual(ordered(expected_merger_info), ordered(merger_info))
|
kfinance/tests/test_tools.py
CHANGED
|
@@ -9,12 +9,19 @@ from pytest import raises
|
|
|
9
9
|
from requests_mock import Mocker
|
|
10
10
|
import time_machine
|
|
11
11
|
|
|
12
|
-
from kfinance.constants import
|
|
12
|
+
from kfinance.constants import (
|
|
13
|
+
BusinessRelationshipType,
|
|
14
|
+
Capitalization,
|
|
15
|
+
CompetitorSource,
|
|
16
|
+
SegmentType,
|
|
17
|
+
StatementType,
|
|
18
|
+
)
|
|
13
19
|
from kfinance.kfinance import Client, NoEarningsDataError
|
|
14
20
|
from kfinance.tests.conftest import SPGI_COMPANY_ID, SPGI_SECURITY_ID, SPGI_TRADING_ITEM_ID
|
|
21
|
+
from kfinance.tests.test_objects import MOCK_COMPANY_DB, MOCK_MERGERS_DB, ordered
|
|
15
22
|
from kfinance.tool_calling import (
|
|
23
|
+
GetCompetitorsFromIdentifier,
|
|
16
24
|
GetEarnings,
|
|
17
|
-
GetEarningsCallDatetimesFromIdentifier,
|
|
18
25
|
GetFinancialLineItemFromIdentifier,
|
|
19
26
|
GetFinancialStatementFromIdentifier,
|
|
20
27
|
GetHistoryMetadataFromIdentifier,
|
|
@@ -28,6 +35,10 @@ from kfinance.tool_calling import (
|
|
|
28
35
|
GetTranscript,
|
|
29
36
|
ResolveIdentifier,
|
|
30
37
|
)
|
|
38
|
+
from kfinance.tool_calling.get_advisors_for_company_in_transaction_from_identifier import (
|
|
39
|
+
GetAdvisorsForCompanyInTransactionFromIdentifier,
|
|
40
|
+
GetAdvisorsForCompanyInTransactionFromIdentifierArgs,
|
|
41
|
+
)
|
|
31
42
|
from kfinance.tool_calling.get_business_relationship_from_identifier import (
|
|
32
43
|
GetBusinessRelationshipFromIdentifier,
|
|
33
44
|
GetBusinessRelationshipFromIdentifierArgs,
|
|
@@ -36,6 +47,9 @@ from kfinance.tool_calling.get_capitalization_from_identifier import (
|
|
|
36
47
|
GetCapitalizationFromIdentifier,
|
|
37
48
|
GetCapitalizationFromIdentifierArgs,
|
|
38
49
|
)
|
|
50
|
+
from kfinance.tool_calling.get_competitors_from_identifier import (
|
|
51
|
+
GetCompetitorsFromIdentifierArgs,
|
|
52
|
+
)
|
|
39
53
|
from kfinance.tool_calling.get_cusip_from_ticker import GetCusipFromTicker, GetCusipFromTickerArgs
|
|
40
54
|
from kfinance.tool_calling.get_financial_line_item_from_identifier import (
|
|
41
55
|
GetFinancialLineItemFromIdentifierArgs,
|
|
@@ -45,6 +59,11 @@ from kfinance.tool_calling.get_financial_statement_from_identifier import (
|
|
|
45
59
|
)
|
|
46
60
|
from kfinance.tool_calling.get_isin_from_ticker import GetIsinFromTickerArgs
|
|
47
61
|
from kfinance.tool_calling.get_latest import GetLatestArgs
|
|
62
|
+
from kfinance.tool_calling.get_merger_info_from_transaction_id import (
|
|
63
|
+
GetMergerInfoFromTransactionId,
|
|
64
|
+
GetMergerInfoFromTransactionIdArgs,
|
|
65
|
+
)
|
|
66
|
+
from kfinance.tool_calling.get_mergers_from_identifier import GetMergersFromIdentifier
|
|
48
67
|
from kfinance.tool_calling.get_n_quarters_ago import GetNQuartersAgoArgs
|
|
49
68
|
from kfinance.tool_calling.get_prices_from_identifier import GetPricesFromIdentifierArgs
|
|
50
69
|
from kfinance.tool_calling.get_segments_from_identifier import (
|
|
@@ -55,6 +74,59 @@ from kfinance.tool_calling.get_transcript import GetTranscriptArgs
|
|
|
55
74
|
from kfinance.tool_calling.shared_models import ToolArgsWithIdentifier, ValidQuarter
|
|
56
75
|
|
|
57
76
|
|
|
77
|
+
class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
|
|
78
|
+
def test_get_companies_advising_company_in_transaction_from_identifier(
|
|
79
|
+
self, requests_mock: Mocker, mock_client: Client
|
|
80
|
+
):
|
|
81
|
+
expected_response = {
|
|
82
|
+
"advisors": [
|
|
83
|
+
{
|
|
84
|
+
"advisor_company_id": 251994106,
|
|
85
|
+
"advisor_company_name": "Kensho Technologies, Inc.",
|
|
86
|
+
"advisor_type_name": "Professional Mongo Enjoyer",
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
transaction_id = 517414
|
|
91
|
+
requests_mock.get(
|
|
92
|
+
url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}/advisors/21835",
|
|
93
|
+
json=expected_response,
|
|
94
|
+
)
|
|
95
|
+
tool = GetAdvisorsForCompanyInTransactionFromIdentifier(kfinance_client=mock_client)
|
|
96
|
+
args = GetAdvisorsForCompanyInTransactionFromIdentifierArgs(
|
|
97
|
+
identifier="MSFT", transaction_id=transaction_id
|
|
98
|
+
)
|
|
99
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
100
|
+
assert response == expected_response["advisors"]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestGetMergerInfoFromTransactionId:
|
|
104
|
+
def test_get_merger_info_from_transaction_id(self, requests_mock: Mocker, mock_client: Client):
|
|
105
|
+
expected_response = MOCK_MERGERS_DB["517414"]
|
|
106
|
+
transaction_id = 517414
|
|
107
|
+
requests_mock.get(
|
|
108
|
+
url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}",
|
|
109
|
+
json=expected_response,
|
|
110
|
+
)
|
|
111
|
+
tool = GetMergerInfoFromTransactionId(kfinance_client=mock_client)
|
|
112
|
+
args = GetMergerInfoFromTransactionIdArgs(transaction_id=transaction_id)
|
|
113
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
114
|
+
assert ordered(response) == ordered(expected_response)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TestGetMergersFromIdentifier:
|
|
118
|
+
def test_get_mergers_from_identifier(self, requests_mock: Mocker, mock_client: Client):
|
|
119
|
+
expected_response = MOCK_COMPANY_DB["21835"]["mergers"]
|
|
120
|
+
company_id = 21835
|
|
121
|
+
requests_mock.get(
|
|
122
|
+
url=f"https://kfinance.kensho.com/api/v1/mergers/{company_id}", json=expected_response
|
|
123
|
+
)
|
|
124
|
+
tool = GetMergersFromIdentifier(kfinance_client=mock_client)
|
|
125
|
+
args = ToolArgsWithIdentifier(identifier="MSFT")
|
|
126
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
127
|
+
assert ordered(response) == ordered(expected_response)
|
|
128
|
+
|
|
129
|
+
|
|
58
130
|
class TestGetBusinessRelationshipFromIdentifier:
|
|
59
131
|
def test_get_business_relationship_from_identifier(
|
|
60
132
|
self, requests_mock: Mocker, mock_client: Client
|
|
@@ -137,27 +209,6 @@ class TestGetCusipFromTicker:
|
|
|
137
209
|
assert resp == spgi_cusip
|
|
138
210
|
|
|
139
211
|
|
|
140
|
-
class TestGetEarningsCallDatetimesFromTicker:
|
|
141
|
-
def test_get_earnings_call_datetimes_from_ticker(
|
|
142
|
-
self, requests_mock: Mocker, mock_client: Client
|
|
143
|
-
):
|
|
144
|
-
"""
|
|
145
|
-
GIVEN the GetEarningsCallDatetimesFromIdentifier tool
|
|
146
|
-
WHEN we request earnings call datetimes for SPGI
|
|
147
|
-
THEN we get back the expected SPGI earnings call datetimes
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
requests_mock.get(
|
|
151
|
-
url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}/dates",
|
|
152
|
-
json={"earnings": ["2025-04-29T12:30:00", "2025-02-11T13:30:00"]},
|
|
153
|
-
)
|
|
154
|
-
expected_response = '["2025-04-29T12:30:00+00:00", "2025-02-11T13:30:00+00:00"]'
|
|
155
|
-
|
|
156
|
-
tool = GetEarningsCallDatetimesFromIdentifier(kfinance_client=mock_client)
|
|
157
|
-
response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
|
|
158
|
-
assert response == expected_response
|
|
159
|
-
|
|
160
|
-
|
|
161
212
|
class TestGetFinancialLineItemFromIdentifier:
|
|
162
213
|
def test_get_financial_line_item_from_identifier(
|
|
163
214
|
self, mock_client: Client, requests_mock: Mocker
|
|
@@ -644,6 +695,33 @@ class TestGetTranscript:
|
|
|
644
695
|
assert response == expected_response
|
|
645
696
|
|
|
646
697
|
|
|
698
|
+
class TestGetCompetitorsFromIdentifier:
|
|
699
|
+
def test_get_competitors_from_identifier(self, mock_client: Client, requests_mock: Mocker):
|
|
700
|
+
"""
|
|
701
|
+
GIVEN the GetCompetitorsFromIdentifier tool
|
|
702
|
+
WHEN we request the SPGI competitors that are named by competitors
|
|
703
|
+
THEN we get back the SPGI competitors that are named by competitors
|
|
704
|
+
"""
|
|
705
|
+
expected_competitors_response = {
|
|
706
|
+
"companies": [
|
|
707
|
+
{"company_id": 35352, "company_name": "The Descartes Systems Group Inc."},
|
|
708
|
+
{"company_id": 4003514, "company_name": "London Stock Exchange Group plc"},
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
requests_mock.get(
|
|
712
|
+
url=f"https://kfinance.kensho.com/api/v1/competitors/{SPGI_COMPANY_ID}/named_by_competitor",
|
|
713
|
+
# truncated from the original API response
|
|
714
|
+
json=expected_competitors_response,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
tool = GetCompetitorsFromIdentifier(kfinance_client=mock_client)
|
|
718
|
+
args = GetCompetitorsFromIdentifierArgs(
|
|
719
|
+
identifier="SPGI", competitor_source=CompetitorSource.named_by_competitor
|
|
720
|
+
)
|
|
721
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
722
|
+
assert response == expected_competitors_response
|
|
723
|
+
|
|
724
|
+
|
|
647
725
|
class TestValidQuarter:
|
|
648
726
|
class QuarterModel(BaseModel):
|
|
649
727
|
quarter: ValidQuarter | None
|