kensho-kfinance 2.9.0__py3-none-any.whl → 3.0.0__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.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/METADATA +1 -1
- kensho_kfinance-3.0.0.dist-info/RECORD +110 -0
- kfinance/CHANGELOG.md +3 -0
- kfinance/__init__.py +1 -0
- kfinance/client/README.md +9 -0
- kfinance/{batch_request_handling.py → client/batch_request_handling.py} +63 -27
- kfinance/{fetch.py → client/fetch.py} +23 -29
- kfinance/{kfinance.py → client/kfinance.py} +37 -33
- kfinance/{meta_classes.py → client/meta_classes.py} +26 -35
- kfinance/{decimal_with_unit.py → client/models/decimal_with_unit.py} +1 -1
- kfinance/{tests → client/models/tests}/test_decimal_with_unit.py +1 -1
- kfinance/client/tests/__init__.py +0 -0
- kfinance/{tests → client/tests}/test_batch_requests.py +8 -6
- kfinance/{tests → client/tests}/test_client.py +25 -19
- kfinance/{tests → client/tests}/test_fetch.py +11 -29
- kfinance/{tests → client/tests}/test_group_objects.py +1 -1
- kfinance/{tests → client/tests}/test_objects.py +33 -29
- kfinance/{tests/conftest.py → conftest.py} +14 -2
- kfinance/domains/README.md +14 -0
- kfinance/domains/__init__.py +0 -0
- kfinance/domains/business_relationships/__init__.py +0 -0
- kfinance/{models → domains/business_relationships}/business_relationship_models.py +10 -0
- kfinance/domains/business_relationships/business_relationship_tools.py +74 -0
- kfinance/domains/business_relationships/tests/__init__.py +0 -0
- kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +55 -0
- kfinance/domains/capitalizations/__init__.py +0 -0
- kfinance/{models → domains/capitalizations}/capitalization_models.py +24 -17
- kfinance/domains/capitalizations/capitalization_tools.py +89 -0
- kfinance/domains/capitalizations/tests/__init__.py +0 -0
- kfinance/{tests/test_models → domains/capitalizations/tests}/test_capitalization_models.py +8 -10
- kfinance/domains/capitalizations/tests/test_capitalization_tools.py +85 -0
- kfinance/domains/companies/__init__.py +0 -0
- kfinance/domains/companies/company_identifiers.py +175 -0
- kfinance/domains/companies/company_models.py +27 -0
- kfinance/domains/companies/company_tools.py +66 -0
- kfinance/domains/companies/tests/__init__.py +0 -0
- kfinance/domains/companies/tests/test_company_tools.py +26 -0
- kfinance/domains/competitors/__init__.py +0 -0
- kfinance/{models → domains/competitors}/competitor_models.py +7 -0
- kfinance/domains/competitors/competitor_tools.py +62 -0
- kfinance/domains/competitors/tests/__init__.py +0 -0
- kfinance/domains/competitors/tests/test_competitor_tools.py +45 -0
- kfinance/domains/cusip_and_isin/__init__.py +0 -0
- kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +80 -0
- kfinance/domains/cusip_and_isin/tests/__init__.py +0 -0
- kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +57 -0
- kfinance/domains/earnings/__init__.py +0 -0
- kfinance/domains/earnings/earning_models.py +41 -0
- kfinance/domains/earnings/earning_tools.py +174 -0
- kfinance/domains/earnings/tests/__init__.py +0 -0
- kfinance/domains/earnings/tests/test_earnings_tools.py +195 -0
- kfinance/domains/line_items/__init__.py +0 -0
- kfinance/domains/line_items/line_item_tools.py +114 -0
- kfinance/domains/line_items/tests/__init__.py +0 -0
- kfinance/domains/line_items/tests/test_line_item_tools.py +86 -0
- kfinance/domains/mergers_and_acquisitions/__init__.py +0 -0
- kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +176 -0
- kfinance/domains/mergers_and_acquisitions/tests/__init__.py +0 -0
- kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +124 -0
- kfinance/domains/prices/__init__.py +0 -0
- kfinance/{models → domains/prices}/price_models.py +1 -1
- kfinance/domains/prices/price_tools.py +165 -0
- kfinance/domains/prices/tests/__init__.py +0 -0
- kfinance/{tests/test_models → domains/prices/tests}/test_price_models.py +2 -2
- kfinance/domains/prices/tests/test_price_tools.py +141 -0
- kfinance/domains/segments/__init__.py +0 -0
- kfinance/domains/segments/segment_tools.py +91 -0
- kfinance/domains/segments/tests/__init__.py +0 -0
- kfinance/domains/segments/tests/test_segment_tools.py +80 -0
- kfinance/domains/statements/__init__.py +0 -0
- kfinance/domains/statements/statement_tools.py +113 -0
- kfinance/domains/statements/tests/__init__.py +0 -0
- kfinance/domains/statements/tests/test_statement_tools.py +73 -0
- kfinance/integrations/README.md +8 -0
- kfinance/integrations/__init__.py +0 -0
- kfinance/integrations/mcp/__init__.py +0 -0
- kfinance/{mcp.py → integrations/mcp/mcp.py} +2 -2
- kfinance/integrations/tests/__init__.py +0 -0
- kfinance/{tests → integrations/tests}/test_example_notebook.py +4 -4
- kfinance/{tool_calling → integrations/tool_calling}/README.md +2 -2
- kfinance/integrations/tool_calling/__init__.py +0 -0
- kfinance/integrations/tool_calling/all_tools.py +55 -0
- kfinance/{tool_calling → integrations/tool_calling}/prompts.py +3 -2
- kfinance/integrations/tool_calling/static_tools/README.md +4 -0
- kfinance/integrations/tool_calling/static_tools/__init__.py +0 -0
- kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_latest.py +3 -3
- kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_n_quarters_ago.py +3 -3
- kfinance/integrations/tool_calling/static_tools/tests/__init__.py +0 -0
- kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +30 -0
- kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +24 -0
- kfinance/integrations/tool_calling/tests/__init__.py +0 -0
- kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +69 -0
- kfinance/{tool_calling/shared_models.py → integrations/tool_calling/tool_calling_models.py} +37 -7
- kfinance/version.py +2 -2
- kensho_kfinance-2.9.0.dist-info/RECORD +0 -70
- kfinance/models/id_models.py +0 -7
- kfinance/prompt.py +0 -526
- kfinance/pydantic_models.py +0 -33
- kfinance/tests/test_tools.py +0 -804
- kfinance/tool_calling/__init__.py +0 -53
- kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +0 -42
- kfinance/tool_calling/get_business_relationship_from_identifier.py +0 -30
- kfinance/tool_calling/get_capitalization_from_identifier.py +0 -35
- kfinance/tool_calling/get_competitors_from_identifier.py +0 -25
- kfinance/tool_calling/get_cusip_from_ticker.py +0 -20
- kfinance/tool_calling/get_earnings.py +0 -33
- kfinance/tool_calling/get_financial_line_item_from_identifier.py +0 -48
- kfinance/tool_calling/get_financial_statement_from_identifier.py +0 -44
- kfinance/tool_calling/get_history_metadata_from_identifier.py +0 -17
- kfinance/tool_calling/get_info_from_identifier.py +0 -16
- kfinance/tool_calling/get_isin_from_ticker.py +0 -20
- kfinance/tool_calling/get_latest_earnings.py +0 -30
- kfinance/tool_calling/get_merger_info_from_transaction_id.py +0 -69
- kfinance/tool_calling/get_mergers_from_identifier.py +0 -44
- kfinance/tool_calling/get_next_earnings.py +0 -30
- kfinance/tool_calling/get_prices_from_identifier.py +0 -46
- kfinance/tool_calling/get_segments_from_identifier.py +0 -44
- kfinance/tool_calling/get_transcript.py +0 -23
- kfinance/tool_calling/resolve_identifier.py +0 -18
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/WHEEL +0 -0
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/top_level.txt +0 -0
- /kfinance/{models → client}/__init__.py +0 -0
- /kfinance/{models → client}/industry_models.py +0 -0
- /kfinance/{tests → client/models}/__init__.py +0 -0
- /kfinance/{models → client/models}/currency_models.py +0 -0
- /kfinance/{models → client/models}/date_and_period_models.py +0 -0
- /kfinance/{tests/test_models → client/models/tests}/__init__.py +0 -0
- /kfinance/{models → client}/permission_models.py +0 -0
- /kfinance/{server_thread.py → client/server_thread.py} +0 -0
- /kfinance/{models → domains/line_items}/line_item_models.py +0 -0
- /kfinance/{models → domains/segments}/segment_models.py +0 -0
- /kfinance/{models → domains/statements}/statement_models.py +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from textwrap import dedent
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
|
|
7
|
+
from kfinance.client.kfinance import Company, MergerOrAcquisition, ParticipantInMerger
|
|
8
|
+
from kfinance.client.permission_models import Permission
|
|
9
|
+
from kfinance.domains.companies.company_identifiers import (
|
|
10
|
+
CompanyId,
|
|
11
|
+
fetch_company_ids_from_identifiers,
|
|
12
|
+
parse_identifiers,
|
|
13
|
+
)
|
|
14
|
+
from kfinance.integrations.tool_calling.tool_calling_models import (
|
|
15
|
+
KfinanceTool,
|
|
16
|
+
ToolArgsWithIdentifier,
|
|
17
|
+
ToolArgsWithIdentifiers,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GetMergersFromIdentifier(KfinanceTool):
|
|
22
|
+
name: str = "get_mergers_from_identifiers"
|
|
23
|
+
description: str = dedent("""
|
|
24
|
+
Get the transaction IDs that involve the given identifiers.
|
|
25
|
+
|
|
26
|
+
For example, "Which companies did Microsoft purchase?" or "Which company bought Ben & Jerrys?"
|
|
27
|
+
""").strip()
|
|
28
|
+
args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
|
|
29
|
+
accepted_permissions: set[Permission] | None = {Permission.MergersPermission}
|
|
30
|
+
|
|
31
|
+
def _run(self, identifiers: list[str]) -> dict:
|
|
32
|
+
api_client = self.kfinance_client.kfinance_api_client
|
|
33
|
+
parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
|
|
34
|
+
identifiers_to_company_ids = fetch_company_ids_from_identifiers(
|
|
35
|
+
identifiers=parsed_identifiers, api_client=api_client
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
tasks = [
|
|
39
|
+
Task(
|
|
40
|
+
func=api_client.fetch_mergers_for_company,
|
|
41
|
+
kwargs=dict(company_id=company_id),
|
|
42
|
+
result_key=identifier,
|
|
43
|
+
)
|
|
44
|
+
for identifier, company_id in identifiers_to_company_ids.items()
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
merger_responses = process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
|
|
48
|
+
|
|
49
|
+
return {str(identifier): mergers for identifier, mergers in merger_responses.items()}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class GetMergerInfoFromTransactionIdArgs(BaseModel):
|
|
53
|
+
transaction_id: int | None = Field(description="The ID of the transaction.", default=None)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class GetMergerInfoFromTransactionId(KfinanceTool):
|
|
57
|
+
name: str = "get_merger_info_from_transaction_id"
|
|
58
|
+
description: str = dedent("""
|
|
59
|
+
Get the timeline, the participants, and the consideration of the merger or acquisition from the given transaction ID.
|
|
60
|
+
|
|
61
|
+
For example, "How much was Ben & Jerrys purchased for?" or "What was the price per share for LinkedIn?" or "When did S&P purchase Kensho?"
|
|
62
|
+
""").strip()
|
|
63
|
+
args_schema: Type[BaseModel] = GetMergerInfoFromTransactionIdArgs
|
|
64
|
+
accepted_permissions: set[Permission] | None = {Permission.MergersPermission}
|
|
65
|
+
|
|
66
|
+
def _run(self, transaction_id: int) -> dict:
|
|
67
|
+
merger_or_acquisition = MergerOrAcquisition(
|
|
68
|
+
kfinance_api_client=self.kfinance_client.kfinance_api_client,
|
|
69
|
+
transaction_id=transaction_id,
|
|
70
|
+
merger_title=None,
|
|
71
|
+
closed_date=None,
|
|
72
|
+
)
|
|
73
|
+
merger_timeline = merger_or_acquisition.get_timeline
|
|
74
|
+
merger_participants = merger_or_acquisition.get_participants
|
|
75
|
+
merger_consideration = merger_or_acquisition.get_consideration
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"timeline": [
|
|
79
|
+
{"status": timeline["status"], "date": timeline["date"].strftime("%Y-%m-%d")}
|
|
80
|
+
for timeline in merger_timeline.to_dict(orient="records")
|
|
81
|
+
]
|
|
82
|
+
if merger_timeline is not None
|
|
83
|
+
else None,
|
|
84
|
+
"participants": {
|
|
85
|
+
"target": {
|
|
86
|
+
"company_id": str(
|
|
87
|
+
CompanyId(
|
|
88
|
+
company_id=merger_participants["target"].company.company_id,
|
|
89
|
+
api_client=self.kfinance_client.kfinance_api_client,
|
|
90
|
+
)
|
|
91
|
+
),
|
|
92
|
+
"company_name": merger_participants["target"].company.name,
|
|
93
|
+
},
|
|
94
|
+
"buyers": [
|
|
95
|
+
{
|
|
96
|
+
"company_id": str(
|
|
97
|
+
CompanyId(
|
|
98
|
+
buyer.company.company_id,
|
|
99
|
+
api_client=self.kfinance_client.kfinance_api_client,
|
|
100
|
+
)
|
|
101
|
+
),
|
|
102
|
+
"company_name": buyer.company.name,
|
|
103
|
+
}
|
|
104
|
+
for buyer in merger_participants["buyers"]
|
|
105
|
+
],
|
|
106
|
+
"sellers": [
|
|
107
|
+
{
|
|
108
|
+
"company_id": str(
|
|
109
|
+
CompanyId(
|
|
110
|
+
seller.company.company_id,
|
|
111
|
+
api_client=self.kfinance_client.kfinance_api_client,
|
|
112
|
+
)
|
|
113
|
+
),
|
|
114
|
+
"company_name": seller.company.name,
|
|
115
|
+
}
|
|
116
|
+
for seller in merger_participants["sellers"]
|
|
117
|
+
],
|
|
118
|
+
}
|
|
119
|
+
if merger_participants is not None
|
|
120
|
+
else None,
|
|
121
|
+
"consideration": {
|
|
122
|
+
"currency_name": merger_consideration["currency_name"],
|
|
123
|
+
"current_calculated_gross_total_transaction_value": merger_consideration[
|
|
124
|
+
"current_calculated_gross_total_transaction_value"
|
|
125
|
+
],
|
|
126
|
+
"current_calculated_implied_equity_value": merger_consideration[
|
|
127
|
+
"current_calculated_implied_equity_value"
|
|
128
|
+
],
|
|
129
|
+
"current_calculated_implied_enterprise_value": merger_consideration[
|
|
130
|
+
"current_calculated_implied_enterprise_value"
|
|
131
|
+
],
|
|
132
|
+
"details": merger_consideration["details"].to_dict(orient="records"),
|
|
133
|
+
}
|
|
134
|
+
if merger_consideration is not None
|
|
135
|
+
else None,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class GetAdvisorsForCompanyInTransactionFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
140
|
+
transaction_id: int | None = Field(description="The ID of the merger.", default=None)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class GetAdvisorsForCompanyInTransactionFromIdentifier(KfinanceTool):
|
|
144
|
+
name: str = "get_advisors_for_company_in_transaction_from_identifier"
|
|
145
|
+
description: str = 'Get the companies advising a company in a given transaction. For example, "Who advised S&P Global during their purchase of Kensho?"'
|
|
146
|
+
args_schema: Type[BaseModel] = GetAdvisorsForCompanyInTransactionFromIdentifierArgs
|
|
147
|
+
accepted_permissions: set[Permission] | None = {Permission.MergersPermission}
|
|
148
|
+
|
|
149
|
+
def _run(self, identifier: str, transaction_id: int) -> list:
|
|
150
|
+
ticker = self.kfinance_client.ticker(identifier)
|
|
151
|
+
participant_in_merger = ParticipantInMerger(
|
|
152
|
+
kfinance_api_client=ticker.kfinance_api_client,
|
|
153
|
+
transaction_id=transaction_id,
|
|
154
|
+
company=Company(
|
|
155
|
+
kfinance_api_client=ticker.kfinance_api_client,
|
|
156
|
+
company_id=ticker.company.company_id,
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
advisors = participant_in_merger.advisors
|
|
160
|
+
|
|
161
|
+
if advisors:
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
"advisor_company_id": str(
|
|
165
|
+
CompanyId(
|
|
166
|
+
company_id=advisor.company.company_id,
|
|
167
|
+
api_client=self.kfinance_client.kfinance_api_client,
|
|
168
|
+
)
|
|
169
|
+
),
|
|
170
|
+
"advisor_company_name": advisor.company.name,
|
|
171
|
+
"advisor_type_name": advisor.advisor_type_name,
|
|
172
|
+
}
|
|
173
|
+
for advisor in advisors
|
|
174
|
+
]
|
|
175
|
+
else:
|
|
176
|
+
return []
|
|
File without changes
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
|
|
3
|
+
from requests_mock import Mocker
|
|
4
|
+
|
|
5
|
+
from kfinance.client.kfinance import Client
|
|
6
|
+
from kfinance.client.tests.test_objects import MOCK_COMPANY_DB, ordered
|
|
7
|
+
from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
|
|
8
|
+
from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_tools import (
|
|
9
|
+
GetAdvisorsForCompanyInTransactionFromIdentifier,
|
|
10
|
+
GetAdvisorsForCompanyInTransactionFromIdentifierArgs,
|
|
11
|
+
GetMergerInfoFromTransactionId,
|
|
12
|
+
GetMergerInfoFromTransactionIdArgs,
|
|
13
|
+
GetMergersFromIdentifier,
|
|
14
|
+
)
|
|
15
|
+
from kfinance.integrations.tool_calling.tool_calling_models import ToolArgsWithIdentifiers
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestGetMergersFromIdentifiers:
|
|
19
|
+
def test_get_mergers_from_identifiers(self, requests_mock: Mocker, mock_client: Client):
|
|
20
|
+
msft_mergers = MOCK_COMPANY_DB["21835"]["mergers"]
|
|
21
|
+
expected_response = {"MSFT": msft_mergers}
|
|
22
|
+
company_id = 21835
|
|
23
|
+
requests_mock.get(
|
|
24
|
+
url=f"https://kfinance.kensho.com/api/v1/mergers/{company_id}", json=msft_mergers
|
|
25
|
+
)
|
|
26
|
+
tool = GetMergersFromIdentifier(kfinance_client=mock_client)
|
|
27
|
+
args = ToolArgsWithIdentifiers(identifiers=["MSFT"])
|
|
28
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
29
|
+
assert ordered(response) == ordered(expected_response)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
|
|
33
|
+
def test_get_companies_advising_company_in_transaction_from_identifier(
|
|
34
|
+
self, requests_mock: Mocker, mock_client: Client
|
|
35
|
+
):
|
|
36
|
+
api_response = {
|
|
37
|
+
"advisors": [
|
|
38
|
+
{
|
|
39
|
+
"advisor_company_id": 251994106,
|
|
40
|
+
"advisor_company_name": "Kensho Technologies, Inc.",
|
|
41
|
+
"advisor_type_name": "Professional Mongo Enjoyer",
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
expected_response = deepcopy(api_response)
|
|
46
|
+
expected_response["advisors"][0]["advisor_company_id"] = f"{COMPANY_ID_PREFIX}251994106"
|
|
47
|
+
transaction_id = 517414
|
|
48
|
+
requests_mock.get(
|
|
49
|
+
url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}/advisors/21835",
|
|
50
|
+
json=api_response,
|
|
51
|
+
)
|
|
52
|
+
tool = GetAdvisorsForCompanyInTransactionFromIdentifier(kfinance_client=mock_client)
|
|
53
|
+
args = GetAdvisorsForCompanyInTransactionFromIdentifierArgs(
|
|
54
|
+
identifier="MSFT", transaction_id=transaction_id
|
|
55
|
+
)
|
|
56
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
57
|
+
assert response == expected_response["advisors"]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestGetMergerInfoFromTransactionId:
|
|
61
|
+
def test_get_merger_info_from_transaction_id(self, requests_mock: Mocker, mock_client: Client):
|
|
62
|
+
response_base = {
|
|
63
|
+
"timeline": [
|
|
64
|
+
{"status": "Announced", "date": "2000-09-12"},
|
|
65
|
+
{"status": "Closed", "date": "2000-09-12"},
|
|
66
|
+
],
|
|
67
|
+
"participants": {},
|
|
68
|
+
"consideration": {
|
|
69
|
+
"currency_name": "US Dollar",
|
|
70
|
+
"current_calculated_gross_total_transaction_value": "51609375.000000",
|
|
71
|
+
"current_calculated_implied_equity_value": "51609375.000000",
|
|
72
|
+
"current_calculated_implied_enterprise_value": "51609375.000000",
|
|
73
|
+
"details": [
|
|
74
|
+
{
|
|
75
|
+
"scenario": "Stock Lump Sum",
|
|
76
|
+
"subtype": "Common Equity",
|
|
77
|
+
"cash_or_cash_equivalent_per_target_share_unit": None,
|
|
78
|
+
"number_of_target_shares_sought": "1000000.000000",
|
|
79
|
+
"current_calculated_gross_value_of_consideration": "51609375.000000",
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
participants_api_response = {
|
|
86
|
+
"target": {"company_id": 31696, "company_name": "MongoMusic, Inc."},
|
|
87
|
+
"buyers": [{"company_id": 21835, "company_name": "Microsoft Corporation"}],
|
|
88
|
+
"sellers": [
|
|
89
|
+
{"company_id": 18805, "company_name": "Angel Investors L.P."},
|
|
90
|
+
{"company_id": 20087, "company_name": "Draper Richards, L.P."},
|
|
91
|
+
],
|
|
92
|
+
}
|
|
93
|
+
api_response = deepcopy(response_base)
|
|
94
|
+
api_response["participants"] = participants_api_response
|
|
95
|
+
|
|
96
|
+
# Returned company IDs should be prefixed
|
|
97
|
+
expected_participants_tool_response = {
|
|
98
|
+
"target": {
|
|
99
|
+
"company_id": f"{COMPANY_ID_PREFIX}31696",
|
|
100
|
+
"company_name": "MongoMusic, Inc.",
|
|
101
|
+
},
|
|
102
|
+
"buyers": [
|
|
103
|
+
{"company_id": f"{COMPANY_ID_PREFIX}21835", "company_name": "Microsoft Corporation"}
|
|
104
|
+
],
|
|
105
|
+
"sellers": [
|
|
106
|
+
{"company_id": f"{COMPANY_ID_PREFIX}18805", "company_name": "Angel Investors L.P."},
|
|
107
|
+
{
|
|
108
|
+
"company_id": f"{COMPANY_ID_PREFIX}20087",
|
|
109
|
+
"company_name": "Draper Richards, L.P.",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
expected_response = deepcopy(response_base)
|
|
114
|
+
expected_response["participants"] = expected_participants_tool_response
|
|
115
|
+
|
|
116
|
+
transaction_id = 517414
|
|
117
|
+
requests_mock.get(
|
|
118
|
+
url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}",
|
|
119
|
+
json=api_response,
|
|
120
|
+
)
|
|
121
|
+
tool = GetMergerInfoFromTransactionId(kfinance_client=mock_client)
|
|
122
|
+
args = GetMergerInfoFromTransactionIdArgs(transaction_id=transaction_id)
|
|
123
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
124
|
+
assert ordered(response) == ordered(expected_response)
|
|
File without changes
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from textwrap import dedent
|
|
3
|
+
from typing import Type
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
|
|
8
|
+
from kfinance.client.models.date_and_period_models import Periodicity
|
|
9
|
+
from kfinance.client.permission_models import Permission
|
|
10
|
+
from kfinance.domains.companies.company_identifiers import (
|
|
11
|
+
fetch_trading_item_ids_from_identifiers,
|
|
12
|
+
parse_identifiers,
|
|
13
|
+
)
|
|
14
|
+
from kfinance.integrations.tool_calling.tool_calling_models import (
|
|
15
|
+
KfinanceTool,
|
|
16
|
+
ToolArgsWithIdentifiers,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GetPricesFromIdentifiersArgs(ToolArgsWithIdentifiers):
|
|
21
|
+
start_date: date | None = Field(
|
|
22
|
+
description="The start date for historical price retrieval", default=None
|
|
23
|
+
)
|
|
24
|
+
end_date: date | None = Field(
|
|
25
|
+
description="The end date for historical price retrieval", default=None
|
|
26
|
+
)
|
|
27
|
+
# no description because the description for enum fields comes from the enum docstring.
|
|
28
|
+
periodicity: Periodicity = Field(default=Periodicity.day)
|
|
29
|
+
adjusted: bool = Field(
|
|
30
|
+
description="Whether to retrieve adjusted prices that account for corporate actions such as dividends and splits.",
|
|
31
|
+
default=True,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class GetPricesFromIdentifiers(KfinanceTool):
|
|
36
|
+
name: str = "get_prices_from_identifiers"
|
|
37
|
+
description: str = dedent("""
|
|
38
|
+
Get the historical open, high, low, and close prices, and volume of a group of identifiers between inclusive start_date and inclusive end date.
|
|
39
|
+
|
|
40
|
+
- When possible, pass multiple identifiers in a single call rather than making multiple calls.
|
|
41
|
+
- When requesting the most recent values, leave start_date and end_date empty.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
Query: "What are the prices of Facebook and Google?"
|
|
45
|
+
Do:
|
|
46
|
+
get_prices_from_identifiers(identifiers=["META", "GOOGL"])
|
|
47
|
+
Don't:
|
|
48
|
+
get_prices_from_identifiers(trading_item_ids=["META"])
|
|
49
|
+
get_prices_from_identifiers(trading_item_ids=["GOOGL"])
|
|
50
|
+
""").strip()
|
|
51
|
+
args_schema: Type[BaseModel] = GetPricesFromIdentifiersArgs
|
|
52
|
+
accepted_permissions: set[Permission] | None = {Permission.PricingPermission}
|
|
53
|
+
|
|
54
|
+
def _run(
|
|
55
|
+
self,
|
|
56
|
+
identifiers: list[str],
|
|
57
|
+
start_date: date | None = None,
|
|
58
|
+
end_date: date | None = None,
|
|
59
|
+
periodicity: Periodicity = Periodicity.day,
|
|
60
|
+
adjusted: bool = True,
|
|
61
|
+
) -> dict:
|
|
62
|
+
"""Sample Response:
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
"SPGI": {
|
|
66
|
+
'prices': [
|
|
67
|
+
{
|
|
68
|
+
'date': '2024-04-11',
|
|
69
|
+
'open': {'value': '424.26', 'unit': 'USD'},
|
|
70
|
+
'high': {'value': '425.99', 'unit': 'USD'},
|
|
71
|
+
'low': {'value': '422.04', 'unit': 'USD'},
|
|
72
|
+
'close': {'value': '422.92', 'unit': 'USD'},
|
|
73
|
+
'volume': {'value': '1129158', 'unit': 'Shares'}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
'date': '2024-04-12',
|
|
77
|
+
'open': {'value': '419.23', 'unit': 'USD'},
|
|
78
|
+
'high': {'value': '421.94', 'unit': 'USD'},
|
|
79
|
+
'low': {'value': '416.45', 'unit': 'USD'},
|
|
80
|
+
'close': {'value': '417.81', 'unit': 'USD'},
|
|
81
|
+
'volume': {'value': '1182229', 'unit': 'Shares'}
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
"""
|
|
87
|
+
api_client = self.kfinance_client.kfinance_api_client
|
|
88
|
+
parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
|
|
89
|
+
identifiers_to_trading_item_ids = fetch_trading_item_ids_from_identifiers(
|
|
90
|
+
identifiers=parsed_identifiers, api_client=api_client
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
tasks = [
|
|
94
|
+
Task(
|
|
95
|
+
func=api_client.fetch_history,
|
|
96
|
+
kwargs=dict(
|
|
97
|
+
trading_item_id=trading_item_id,
|
|
98
|
+
start_date=start_date,
|
|
99
|
+
end_date=end_date,
|
|
100
|
+
periodicity=periodicity,
|
|
101
|
+
is_adjusted=adjusted,
|
|
102
|
+
),
|
|
103
|
+
result_key=identifier,
|
|
104
|
+
)
|
|
105
|
+
for identifier, trading_item_id in identifiers_to_trading_item_ids.items()
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
price_responses = process_tasks_in_thread_pool_executor(api_client=api_client, tasks=tasks)
|
|
109
|
+
|
|
110
|
+
# Only include most recent price if more than one identifier passed and start_date == end_date == None
|
|
111
|
+
dump_include_filter = None
|
|
112
|
+
if len(identifiers) > 1 and start_date == end_date is None:
|
|
113
|
+
dump_include_filter = {"prices": {0: True}}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
str(identifier): prices.model_dump(mode="json", include=dump_include_filter)
|
|
117
|
+
for identifier, prices in price_responses.items()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class GetHistoryMetadataFromIdentifiers(KfinanceTool):
|
|
122
|
+
name: str = "get_history_metadata_from_identifiers"
|
|
123
|
+
description: str = dedent("""
|
|
124
|
+
Get the history metadata associated with a list of identifiers. History metadata includes currency, symbol, exchange name, instrument type, and first trade date.
|
|
125
|
+
|
|
126
|
+
- When possible, pass multiple identifiers in a single call rather than making multiple calls.
|
|
127
|
+
""").strip()
|
|
128
|
+
args_schema: Type[BaseModel] = ToolArgsWithIdentifiers
|
|
129
|
+
accepted_permissions: set[Permission] | None = None
|
|
130
|
+
|
|
131
|
+
def _run(self, identifiers: list[str]) -> dict:
|
|
132
|
+
"""Sample response:
|
|
133
|
+
|
|
134
|
+
{
|
|
135
|
+
'SPGI': {
|
|
136
|
+
'currency': 'USD',
|
|
137
|
+
'exchange_name': 'NYSE',
|
|
138
|
+
'first_trade_date': '1968-01-02',
|
|
139
|
+
'instrument_type': 'Equity',
|
|
140
|
+
'symbol': 'SPGI'
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
"""
|
|
144
|
+
api_client = self.kfinance_client.kfinance_api_client
|
|
145
|
+
parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
|
|
146
|
+
identifiers_to_trading_item_ids = fetch_trading_item_ids_from_identifiers(
|
|
147
|
+
identifiers=parsed_identifiers, api_client=api_client
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
tasks = [
|
|
151
|
+
Task(
|
|
152
|
+
func=api_client.fetch_history_metadata,
|
|
153
|
+
kwargs=dict(trading_item_id=trading_item_id),
|
|
154
|
+
result_key=identifier,
|
|
155
|
+
)
|
|
156
|
+
for identifier, trading_item_id in identifiers_to_trading_item_ids.items()
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
history_metadata_responses = process_tasks_in_thread_pool_executor(
|
|
160
|
+
api_client=api_client, tasks=tasks
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
str(identifier): result for identifier, result in history_metadata_responses.items()
|
|
165
|
+
}
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
2
|
|
|
3
|
-
from kfinance.decimal_with_unit import Money, Shares
|
|
4
|
-
from kfinance.
|
|
3
|
+
from kfinance.client.models.decimal_with_unit import Money, Shares
|
|
4
|
+
from kfinance.domains.prices.price_models import PriceHistory, Prices
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class TestPriceHistory:
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from requests_mock import Mocker
|
|
2
|
+
|
|
3
|
+
from kfinance.client.kfinance import Client
|
|
4
|
+
from kfinance.conftest import SPGI_TRADING_ITEM_ID
|
|
5
|
+
from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
|
|
6
|
+
from kfinance.domains.prices.price_tools import (
|
|
7
|
+
GetHistoryMetadataFromIdentifiers,
|
|
8
|
+
GetPricesFromIdentifiers,
|
|
9
|
+
GetPricesFromIdentifiersArgs,
|
|
10
|
+
)
|
|
11
|
+
from kfinance.integrations.tool_calling.tool_calling_models import ToolArgsWithIdentifiers
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestGetHistoryMetadataFromIdentifiers:
|
|
15
|
+
def test_get_history_metadata_from_identifiers(
|
|
16
|
+
self, mock_client: Client, requests_mock: Mocker
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
GIVEN the GetHistoryMetadataFromIdentifiers tool
|
|
20
|
+
WHEN we request the history metadata for SPGI
|
|
21
|
+
THEN we get back SPGI's history metadata
|
|
22
|
+
"""
|
|
23
|
+
metadata_resp = {
|
|
24
|
+
"currency": "USD",
|
|
25
|
+
"exchange_name": "NYSE",
|
|
26
|
+
"first_trade_date": "1968-01-02",
|
|
27
|
+
"instrument_type": "Equity",
|
|
28
|
+
"symbol": "SPGI",
|
|
29
|
+
}
|
|
30
|
+
expected_resp = {"SPGI": metadata_resp}
|
|
31
|
+
|
|
32
|
+
requests_mock.get(
|
|
33
|
+
url=f"https://kfinance.kensho.com/api/v1/pricing/{SPGI_TRADING_ITEM_ID}/metadata",
|
|
34
|
+
json=metadata_resp,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
tool = GetHistoryMetadataFromIdentifiers(kfinance_client=mock_client)
|
|
38
|
+
resp = tool.run(ToolArgsWithIdentifiers(identifiers=["SPGI"]).model_dump(mode="json"))
|
|
39
|
+
assert resp == expected_resp
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestPricesFromIdentifiers:
|
|
43
|
+
prices_resp = {
|
|
44
|
+
"currency": "USD",
|
|
45
|
+
"prices": [
|
|
46
|
+
{
|
|
47
|
+
"date": "2024-04-11",
|
|
48
|
+
"open": "424.260000",
|
|
49
|
+
"high": "425.990000",
|
|
50
|
+
"low": "422.040000",
|
|
51
|
+
"close": "422.920000",
|
|
52
|
+
"volume": "1129158",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"date": "2024-04-12",
|
|
56
|
+
"open": "419.230000",
|
|
57
|
+
"high": "421.940000",
|
|
58
|
+
"low": "416.450000",
|
|
59
|
+
"close": "417.810000",
|
|
60
|
+
"volume": "1182229",
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def test_get_prices_from_identifiers(self, mock_client: Client, requests_mock: Mocker):
|
|
66
|
+
"""
|
|
67
|
+
GIVEN the GetPricesFromIdentifiers tool
|
|
68
|
+
WHEN we request prices for SPGI
|
|
69
|
+
THEN we get back prices for SPGI
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
requests_mock.get(
|
|
73
|
+
url=f"https://kfinance.kensho.com/api/v1/pricing/{SPGI_TRADING_ITEM_ID}/none/none/day/adjusted",
|
|
74
|
+
json=self.prices_resp,
|
|
75
|
+
)
|
|
76
|
+
expected_response = {
|
|
77
|
+
"SPGI": {
|
|
78
|
+
"prices": [
|
|
79
|
+
{
|
|
80
|
+
"date": "2024-04-11",
|
|
81
|
+
"open": {"value": "424.26", "unit": "USD"},
|
|
82
|
+
"high": {"value": "425.99", "unit": "USD"},
|
|
83
|
+
"low": {"value": "422.04", "unit": "USD"},
|
|
84
|
+
"close": {"value": "422.92", "unit": "USD"},
|
|
85
|
+
"volume": {"value": "1129158", "unit": "Shares"},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"date": "2024-04-12",
|
|
89
|
+
"open": {"value": "419.23", "unit": "USD"},
|
|
90
|
+
"high": {"value": "421.94", "unit": "USD"},
|
|
91
|
+
"low": {"value": "416.45", "unit": "USD"},
|
|
92
|
+
"close": {"value": "417.81", "unit": "USD"},
|
|
93
|
+
"volume": {"value": "1182229", "unit": "Shares"},
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
tool = GetPricesFromIdentifiers(kfinance_client=mock_client)
|
|
100
|
+
response = tool.run(
|
|
101
|
+
GetPricesFromIdentifiersArgs(identifiers=["SPGI"]).model_dump(mode="json")
|
|
102
|
+
)
|
|
103
|
+
assert response == expected_response
|
|
104
|
+
|
|
105
|
+
def test_most_recent_request(self, requests_mock: Mocker, mock_client: Client) -> None:
|
|
106
|
+
"""
|
|
107
|
+
GIVEN the GetPricesFromIdentifiers tool
|
|
108
|
+
WHEN we request most recent prices for multiple companies
|
|
109
|
+
THEN we only get back the most recent prices for each company
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
company_ids = [1, 2]
|
|
113
|
+
for trading_item_id in company_ids:
|
|
114
|
+
requests_mock.get(
|
|
115
|
+
url=f"https://kfinance.kensho.com/api/v1/pricing/{trading_item_id}/none/none/day/adjusted",
|
|
116
|
+
json=self.prices_resp,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
expected_single_company_response = {
|
|
120
|
+
"prices": [
|
|
121
|
+
{
|
|
122
|
+
"date": "2024-04-11",
|
|
123
|
+
"open": {"value": "424.26", "unit": "USD"},
|
|
124
|
+
"high": {"value": "425.99", "unit": "USD"},
|
|
125
|
+
"low": {"value": "422.04", "unit": "USD"},
|
|
126
|
+
"close": {"value": "422.92", "unit": "USD"},
|
|
127
|
+
"volume": {"value": "1129158", "unit": "Shares"},
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
expected_response = {
|
|
132
|
+
"C_1": expected_single_company_response,
|
|
133
|
+
"C_2": expected_single_company_response,
|
|
134
|
+
}
|
|
135
|
+
tool = GetPricesFromIdentifiers(kfinance_client=mock_client)
|
|
136
|
+
response = tool.run(
|
|
137
|
+
GetPricesFromIdentifiersArgs(
|
|
138
|
+
identifiers=[f"{COMPANY_ID_PREFIX}{company_id}" for company_id in company_ids]
|
|
139
|
+
).model_dump(mode="json")
|
|
140
|
+
)
|
|
141
|
+
assert response == expected_response
|
|
File without changes
|