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.

Files changed (134) hide show
  1. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/METADATA +1 -1
  2. kensho_kfinance-3.0.0.dist-info/RECORD +110 -0
  3. kfinance/CHANGELOG.md +3 -0
  4. kfinance/__init__.py +1 -0
  5. kfinance/client/README.md +9 -0
  6. kfinance/{batch_request_handling.py → client/batch_request_handling.py} +63 -27
  7. kfinance/{fetch.py → client/fetch.py} +23 -29
  8. kfinance/{kfinance.py → client/kfinance.py} +37 -33
  9. kfinance/{meta_classes.py → client/meta_classes.py} +26 -35
  10. kfinance/{decimal_with_unit.py → client/models/decimal_with_unit.py} +1 -1
  11. kfinance/{tests → client/models/tests}/test_decimal_with_unit.py +1 -1
  12. kfinance/client/tests/__init__.py +0 -0
  13. kfinance/{tests → client/tests}/test_batch_requests.py +8 -6
  14. kfinance/{tests → client/tests}/test_client.py +25 -19
  15. kfinance/{tests → client/tests}/test_fetch.py +11 -29
  16. kfinance/{tests → client/tests}/test_group_objects.py +1 -1
  17. kfinance/{tests → client/tests}/test_objects.py +33 -29
  18. kfinance/{tests/conftest.py → conftest.py} +14 -2
  19. kfinance/domains/README.md +14 -0
  20. kfinance/domains/__init__.py +0 -0
  21. kfinance/domains/business_relationships/__init__.py +0 -0
  22. kfinance/{models → domains/business_relationships}/business_relationship_models.py +10 -0
  23. kfinance/domains/business_relationships/business_relationship_tools.py +74 -0
  24. kfinance/domains/business_relationships/tests/__init__.py +0 -0
  25. kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +55 -0
  26. kfinance/domains/capitalizations/__init__.py +0 -0
  27. kfinance/{models → domains/capitalizations}/capitalization_models.py +24 -17
  28. kfinance/domains/capitalizations/capitalization_tools.py +89 -0
  29. kfinance/domains/capitalizations/tests/__init__.py +0 -0
  30. kfinance/{tests/test_models → domains/capitalizations/tests}/test_capitalization_models.py +8 -10
  31. kfinance/domains/capitalizations/tests/test_capitalization_tools.py +85 -0
  32. kfinance/domains/companies/__init__.py +0 -0
  33. kfinance/domains/companies/company_identifiers.py +175 -0
  34. kfinance/domains/companies/company_models.py +27 -0
  35. kfinance/domains/companies/company_tools.py +66 -0
  36. kfinance/domains/companies/tests/__init__.py +0 -0
  37. kfinance/domains/companies/tests/test_company_tools.py +26 -0
  38. kfinance/domains/competitors/__init__.py +0 -0
  39. kfinance/{models → domains/competitors}/competitor_models.py +7 -0
  40. kfinance/domains/competitors/competitor_tools.py +62 -0
  41. kfinance/domains/competitors/tests/__init__.py +0 -0
  42. kfinance/domains/competitors/tests/test_competitor_tools.py +45 -0
  43. kfinance/domains/cusip_and_isin/__init__.py +0 -0
  44. kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +80 -0
  45. kfinance/domains/cusip_and_isin/tests/__init__.py +0 -0
  46. kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +57 -0
  47. kfinance/domains/earnings/__init__.py +0 -0
  48. kfinance/domains/earnings/earning_models.py +41 -0
  49. kfinance/domains/earnings/earning_tools.py +174 -0
  50. kfinance/domains/earnings/tests/__init__.py +0 -0
  51. kfinance/domains/earnings/tests/test_earnings_tools.py +195 -0
  52. kfinance/domains/line_items/__init__.py +0 -0
  53. kfinance/domains/line_items/line_item_tools.py +114 -0
  54. kfinance/domains/line_items/tests/__init__.py +0 -0
  55. kfinance/domains/line_items/tests/test_line_item_tools.py +86 -0
  56. kfinance/domains/mergers_and_acquisitions/__init__.py +0 -0
  57. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +176 -0
  58. kfinance/domains/mergers_and_acquisitions/tests/__init__.py +0 -0
  59. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +124 -0
  60. kfinance/domains/prices/__init__.py +0 -0
  61. kfinance/{models → domains/prices}/price_models.py +1 -1
  62. kfinance/domains/prices/price_tools.py +165 -0
  63. kfinance/domains/prices/tests/__init__.py +0 -0
  64. kfinance/{tests/test_models → domains/prices/tests}/test_price_models.py +2 -2
  65. kfinance/domains/prices/tests/test_price_tools.py +141 -0
  66. kfinance/domains/segments/__init__.py +0 -0
  67. kfinance/domains/segments/segment_tools.py +91 -0
  68. kfinance/domains/segments/tests/__init__.py +0 -0
  69. kfinance/domains/segments/tests/test_segment_tools.py +80 -0
  70. kfinance/domains/statements/__init__.py +0 -0
  71. kfinance/domains/statements/statement_tools.py +113 -0
  72. kfinance/domains/statements/tests/__init__.py +0 -0
  73. kfinance/domains/statements/tests/test_statement_tools.py +73 -0
  74. kfinance/integrations/README.md +8 -0
  75. kfinance/integrations/__init__.py +0 -0
  76. kfinance/integrations/mcp/__init__.py +0 -0
  77. kfinance/{mcp.py → integrations/mcp/mcp.py} +2 -2
  78. kfinance/integrations/tests/__init__.py +0 -0
  79. kfinance/{tests → integrations/tests}/test_example_notebook.py +4 -4
  80. kfinance/{tool_calling → integrations/tool_calling}/README.md +2 -2
  81. kfinance/integrations/tool_calling/__init__.py +0 -0
  82. kfinance/integrations/tool_calling/all_tools.py +55 -0
  83. kfinance/{tool_calling → integrations/tool_calling}/prompts.py +3 -2
  84. kfinance/integrations/tool_calling/static_tools/README.md +4 -0
  85. kfinance/integrations/tool_calling/static_tools/__init__.py +0 -0
  86. kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_latest.py +3 -3
  87. kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_n_quarters_ago.py +3 -3
  88. kfinance/integrations/tool_calling/static_tools/tests/__init__.py +0 -0
  89. kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +30 -0
  90. kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +24 -0
  91. kfinance/integrations/tool_calling/tests/__init__.py +0 -0
  92. kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +69 -0
  93. kfinance/{tool_calling/shared_models.py → integrations/tool_calling/tool_calling_models.py} +37 -7
  94. kfinance/version.py +2 -2
  95. kensho_kfinance-2.9.0.dist-info/RECORD +0 -70
  96. kfinance/models/id_models.py +0 -7
  97. kfinance/prompt.py +0 -526
  98. kfinance/pydantic_models.py +0 -33
  99. kfinance/tests/test_tools.py +0 -804
  100. kfinance/tool_calling/__init__.py +0 -53
  101. kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +0 -42
  102. kfinance/tool_calling/get_business_relationship_from_identifier.py +0 -30
  103. kfinance/tool_calling/get_capitalization_from_identifier.py +0 -35
  104. kfinance/tool_calling/get_competitors_from_identifier.py +0 -25
  105. kfinance/tool_calling/get_cusip_from_ticker.py +0 -20
  106. kfinance/tool_calling/get_earnings.py +0 -33
  107. kfinance/tool_calling/get_financial_line_item_from_identifier.py +0 -48
  108. kfinance/tool_calling/get_financial_statement_from_identifier.py +0 -44
  109. kfinance/tool_calling/get_history_metadata_from_identifier.py +0 -17
  110. kfinance/tool_calling/get_info_from_identifier.py +0 -16
  111. kfinance/tool_calling/get_isin_from_ticker.py +0 -20
  112. kfinance/tool_calling/get_latest_earnings.py +0 -30
  113. kfinance/tool_calling/get_merger_info_from_transaction_id.py +0 -69
  114. kfinance/tool_calling/get_mergers_from_identifier.py +0 -44
  115. kfinance/tool_calling/get_next_earnings.py +0 -30
  116. kfinance/tool_calling/get_prices_from_identifier.py +0 -46
  117. kfinance/tool_calling/get_segments_from_identifier.py +0 -44
  118. kfinance/tool_calling/get_transcript.py +0 -23
  119. kfinance/tool_calling/resolve_identifier.py +0 -18
  120. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/WHEEL +0 -0
  121. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/AUTHORS.md +0 -0
  122. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/LICENSE +0 -0
  123. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.0.dist-info}/top_level.txt +0 -0
  124. /kfinance/{models → client}/__init__.py +0 -0
  125. /kfinance/{models → client}/industry_models.py +0 -0
  126. /kfinance/{tests → client/models}/__init__.py +0 -0
  127. /kfinance/{models → client/models}/currency_models.py +0 -0
  128. /kfinance/{models → client/models}/date_and_period_models.py +0 -0
  129. /kfinance/{tests/test_models → client/models/tests}/__init__.py +0 -0
  130. /kfinance/{models → client}/permission_models.py +0 -0
  131. /kfinance/{server_thread.py → client/server_thread.py} +0 -0
  132. /kfinance/{models → domains/line_items}/line_item_models.py +0 -0
  133. /kfinance/{models → domains/segments}/segment_models.py +0 -0
  134. /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 []
@@ -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
@@ -4,7 +4,7 @@ from typing import Any, TypedDict
4
4
 
5
5
  from pydantic import BaseModel, model_validator
6
6
 
7
- from kfinance.decimal_with_unit import Money, Shares
7
+ from kfinance.client.models.decimal_with_unit import Money, Shares
8
8
 
9
9
 
10
10
  class HistoryMetadata(TypedDict):
@@ -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.models.price_models import PriceHistory, Prices
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