kensho-kfinance 1.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-1.0.0.dist-info/AUTHORS.md +9 -0
- kensho_kfinance-1.0.0.dist-info/LICENSE +1 -0
- kensho_kfinance-1.0.0.dist-info/METADATA +53 -0
- kensho_kfinance-1.0.0.dist-info/RECORD +21 -0
- kensho_kfinance-1.0.0.dist-info/WHEEL +5 -0
- kensho_kfinance-1.0.0.dist-info/top_level.txt +1 -0
- kfinance/CHANGELOG.md +10 -0
- kfinance/__init__.py +0 -0
- kfinance/constants.py +1689 -0
- kfinance/fetch.py +374 -0
- kfinance/kfinance.py +1224 -0
- kfinance/llm_tools.py +688 -0
- kfinance/meta_classes.py +367 -0
- kfinance/prompt.py +526 -0
- kfinance/py.typed +0 -0
- kfinance/server_thread.py +62 -0
- kfinance/tests/__init__.py +0 -0
- kfinance/tests/test_fetch.py +241 -0
- kfinance/tests/test_objects.py +551 -0
- kfinance/tool_schemas.py +132 -0
- kfinance/version.py +21 -0
kfinance/meta_classes.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from functools import cache, cached_property
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from .constants import LINE_ITEMS, BusinessRelationshipType
|
|
10
|
+
from .fetch import KFinanceApiClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .kfinance import BusinessRelationships
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CompanyFunctionsMetaClass:
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
"""Init company functions"""
|
|
23
|
+
self.kfinance_api_client: KFinanceApiClient
|
|
24
|
+
|
|
25
|
+
@cached_property
|
|
26
|
+
def company_id(self) -> Any:
|
|
27
|
+
"""Set and return the company id for the object"""
|
|
28
|
+
raise NotImplementedError("child classes must implement company id property")
|
|
29
|
+
|
|
30
|
+
def validate_inputs(
|
|
31
|
+
self,
|
|
32
|
+
period_type: Optional[str] = None,
|
|
33
|
+
start_year: Optional[int] = None,
|
|
34
|
+
end_year: Optional[int] = None,
|
|
35
|
+
start_quarter: Optional[int] = None,
|
|
36
|
+
end_quarter: Optional[int] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Test the time inputs for validity."""
|
|
39
|
+
|
|
40
|
+
if period_type not in {"annual", "quarterly", "ytd", "ltm", None}:
|
|
41
|
+
raise RuntimeError(f"Period type {period_type} is not valid.")
|
|
42
|
+
|
|
43
|
+
if start_year and (start_year > datetime.now().year):
|
|
44
|
+
raise ValueError("start_year is in the future")
|
|
45
|
+
|
|
46
|
+
if end_year and not (1900 < end_year < 2100):
|
|
47
|
+
raise ValueError("end_year is not in range")
|
|
48
|
+
|
|
49
|
+
if start_quarter and not (1 <= start_quarter <= 4):
|
|
50
|
+
raise ValueError("start_qtr is out of range 1 to 4")
|
|
51
|
+
|
|
52
|
+
if end_quarter and not (1 <= end_quarter <= 4):
|
|
53
|
+
raise ValueError("end_qtr is out of range 1 to 4")
|
|
54
|
+
|
|
55
|
+
@cache
|
|
56
|
+
def statement(
|
|
57
|
+
self,
|
|
58
|
+
statement_type: str,
|
|
59
|
+
period_type: Optional[str] = None,
|
|
60
|
+
start_year: Optional[int] = None,
|
|
61
|
+
end_year: Optional[int] = None,
|
|
62
|
+
start_quarter: Optional[int] = None,
|
|
63
|
+
end_quarter: Optional[int] = None,
|
|
64
|
+
) -> pd.DataFrame:
|
|
65
|
+
"""Get the company's financial statement"""
|
|
66
|
+
try:
|
|
67
|
+
self.validate_inputs(
|
|
68
|
+
period_type=period_type,
|
|
69
|
+
start_year=start_year,
|
|
70
|
+
end_year=end_year,
|
|
71
|
+
start_quarter=start_quarter,
|
|
72
|
+
end_quarter=end_quarter,
|
|
73
|
+
)
|
|
74
|
+
except ValueError:
|
|
75
|
+
return pd.DataFrame()
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
pd.DataFrame(
|
|
79
|
+
self.kfinance_api_client.fetch_statement(
|
|
80
|
+
company_id=self.company_id,
|
|
81
|
+
statement_type=statement_type,
|
|
82
|
+
period_type=period_type,
|
|
83
|
+
start_year=start_year,
|
|
84
|
+
end_year=end_year,
|
|
85
|
+
start_quarter=start_quarter,
|
|
86
|
+
end_quarter=end_quarter,
|
|
87
|
+
)["statements"]
|
|
88
|
+
)
|
|
89
|
+
.apply(pd.to_numeric)
|
|
90
|
+
.replace(np.nan, None)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def income_statement(
|
|
94
|
+
self,
|
|
95
|
+
period_type: Optional[str] = None,
|
|
96
|
+
start_year: Optional[int] = None,
|
|
97
|
+
end_year: Optional[int] = None,
|
|
98
|
+
start_quarter: Optional[int] = None,
|
|
99
|
+
end_quarter: Optional[int] = None,
|
|
100
|
+
) -> pd.DataFrame:
|
|
101
|
+
"""The templated income statement"""
|
|
102
|
+
return self.statement(
|
|
103
|
+
statement_type="income_statement",
|
|
104
|
+
period_type=period_type,
|
|
105
|
+
start_year=start_year,
|
|
106
|
+
end_year=end_year,
|
|
107
|
+
start_quarter=start_quarter,
|
|
108
|
+
end_quarter=end_quarter,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def income_stmt(
|
|
112
|
+
self,
|
|
113
|
+
period_type: Optional[str] = None,
|
|
114
|
+
start_year: Optional[int] = None,
|
|
115
|
+
end_year: Optional[int] = None,
|
|
116
|
+
start_quarter: Optional[int] = None,
|
|
117
|
+
end_quarter: Optional[int] = None,
|
|
118
|
+
) -> pd.DataFrame:
|
|
119
|
+
"""The templated income statement"""
|
|
120
|
+
return self.statement(
|
|
121
|
+
statement_type="income_statement",
|
|
122
|
+
period_type=period_type,
|
|
123
|
+
start_year=start_year,
|
|
124
|
+
end_year=end_year,
|
|
125
|
+
start_quarter=start_quarter,
|
|
126
|
+
end_quarter=end_quarter,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def balance_sheet(
|
|
130
|
+
self,
|
|
131
|
+
period_type: Optional[str] = None,
|
|
132
|
+
start_year: Optional[int] = None,
|
|
133
|
+
end_year: Optional[int] = None,
|
|
134
|
+
start_quarter: Optional[int] = None,
|
|
135
|
+
end_quarter: Optional[int] = None,
|
|
136
|
+
) -> pd.DataFrame:
|
|
137
|
+
"""The templated balance sheet"""
|
|
138
|
+
return self.statement(
|
|
139
|
+
statement_type="balance_sheet",
|
|
140
|
+
period_type=period_type,
|
|
141
|
+
start_year=start_year,
|
|
142
|
+
end_year=end_year,
|
|
143
|
+
start_quarter=start_quarter,
|
|
144
|
+
end_quarter=end_quarter,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def cash_flow(
|
|
148
|
+
self,
|
|
149
|
+
period_type: Optional[str] = None,
|
|
150
|
+
start_year: Optional[int] = None,
|
|
151
|
+
end_year: Optional[int] = None,
|
|
152
|
+
start_quarter: Optional[int] = None,
|
|
153
|
+
end_quarter: Optional[int] = None,
|
|
154
|
+
) -> pd.DataFrame:
|
|
155
|
+
"""The templated cash flow statement"""
|
|
156
|
+
return self.statement(
|
|
157
|
+
statement_type="cash_flow",
|
|
158
|
+
period_type=period_type,
|
|
159
|
+
start_year=start_year,
|
|
160
|
+
end_year=end_year,
|
|
161
|
+
start_quarter=start_quarter,
|
|
162
|
+
end_quarter=end_quarter,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def cashflow(
|
|
166
|
+
self,
|
|
167
|
+
period_type: Optional[str] = None,
|
|
168
|
+
start_year: Optional[int] = None,
|
|
169
|
+
end_year: Optional[int] = None,
|
|
170
|
+
start_quarter: Optional[int] = None,
|
|
171
|
+
end_quarter: Optional[int] = None,
|
|
172
|
+
) -> pd.DataFrame:
|
|
173
|
+
"""The templated cash flow statement"""
|
|
174
|
+
return self.statement(
|
|
175
|
+
statement_type="cash_flow",
|
|
176
|
+
period_type=period_type,
|
|
177
|
+
start_year=start_year,
|
|
178
|
+
end_year=end_year,
|
|
179
|
+
start_quarter=start_quarter,
|
|
180
|
+
end_quarter=end_quarter,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@cache
|
|
184
|
+
def line_item(
|
|
185
|
+
self,
|
|
186
|
+
line_item: str,
|
|
187
|
+
period_type: Optional[str] = None,
|
|
188
|
+
start_year: Optional[int] = None,
|
|
189
|
+
end_year: Optional[int] = None,
|
|
190
|
+
start_quarter: Optional[int] = None,
|
|
191
|
+
end_quarter: Optional[int] = None,
|
|
192
|
+
) -> pd.DataFrame:
|
|
193
|
+
"""Get a DataFrame of a financial line item according to the date ranges."""
|
|
194
|
+
try:
|
|
195
|
+
self.validate_inputs(
|
|
196
|
+
period_type=period_type,
|
|
197
|
+
start_year=start_year,
|
|
198
|
+
end_year=end_year,
|
|
199
|
+
start_quarter=start_quarter,
|
|
200
|
+
end_quarter=end_quarter,
|
|
201
|
+
)
|
|
202
|
+
except ValueError:
|
|
203
|
+
return pd.DataFrame()
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
pd.DataFrame(
|
|
207
|
+
self.kfinance_api_client.fetch_line_item(
|
|
208
|
+
company_id=self.company_id,
|
|
209
|
+
line_item=line_item,
|
|
210
|
+
period_type=period_type,
|
|
211
|
+
start_year=start_year,
|
|
212
|
+
end_year=end_year,
|
|
213
|
+
start_quarter=start_quarter,
|
|
214
|
+
end_quarter=end_quarter,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
.transpose()
|
|
218
|
+
.apply(pd.to_numeric)
|
|
219
|
+
.replace(np.nan, None)
|
|
220
|
+
.set_index(pd.Index([line_item]))
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def relationships(self, relationship_type: BusinessRelationshipType) -> "BusinessRelationships":
|
|
224
|
+
"""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.
|
|
225
|
+
|
|
226
|
+
:param relationship_type: The type of relationship to filter by. Valid relationship types are defined in the BusinessRelationshipType class.
|
|
227
|
+
:type relationship_type: BusinessRelationshipType
|
|
228
|
+
:return: A BusinessRelationships object containing a tuple of Companies objects that lists current and previous company IDs that have the specified relationship with the given company_id.
|
|
229
|
+
:rtype: BusinessRelationships
|
|
230
|
+
"""
|
|
231
|
+
from .kfinance import BusinessRelationships, Companies
|
|
232
|
+
|
|
233
|
+
companies = self.kfinance_api_client.fetch_companies_from_business_relationship(
|
|
234
|
+
self.company_id,
|
|
235
|
+
relationship_type,
|
|
236
|
+
)
|
|
237
|
+
return BusinessRelationships(
|
|
238
|
+
Companies(self.kfinance_api_client, companies["current"]),
|
|
239
|
+
Companies(self.kfinance_api_client, companies["previous"]),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
for line_item in LINE_ITEMS:
|
|
244
|
+
line_item_name = line_item["name"]
|
|
245
|
+
|
|
246
|
+
def _line_item_outer_wrapper(line_item_name: str, alias_for: Optional[str] = None) -> Callable:
|
|
247
|
+
def line_item_inner_wrapper(
|
|
248
|
+
self: Any,
|
|
249
|
+
period_type: Optional[str] = None,
|
|
250
|
+
start_year: Optional[int] = None,
|
|
251
|
+
end_year: Optional[int] = None,
|
|
252
|
+
start_quarter: Optional[int] = None,
|
|
253
|
+
end_quarter: Optional[int] = None,
|
|
254
|
+
) -> pd.DataFrame:
|
|
255
|
+
return self.line_item(
|
|
256
|
+
line_item=line_item_name,
|
|
257
|
+
period_type=period_type,
|
|
258
|
+
start_year=start_year,
|
|
259
|
+
end_year=end_year,
|
|
260
|
+
start_quarter=start_quarter,
|
|
261
|
+
end_quarter=end_quarter,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
doc = "ciq data item " + str(line_item["dataitemid"])
|
|
265
|
+
TAB = " "
|
|
266
|
+
if alias_for is not None:
|
|
267
|
+
doc = f"alias for {alias_for}\n\n{TAB}{TAB}" + doc
|
|
268
|
+
line_item_inner_wrapper.__doc__ = doc
|
|
269
|
+
line_item_inner_wrapper.__name__ = line_item_name
|
|
270
|
+
return line_item_inner_wrapper
|
|
271
|
+
|
|
272
|
+
setattr(
|
|
273
|
+
CompanyFunctionsMetaClass,
|
|
274
|
+
line_item_name,
|
|
275
|
+
_line_item_outer_wrapper(line_item_name),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
for alias in line_item["aliases"]:
|
|
279
|
+
setattr(
|
|
280
|
+
CompanyFunctionsMetaClass,
|
|
281
|
+
alias,
|
|
282
|
+
_line_item_outer_wrapper(alias, line_item_name),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class DelegatedCompanyFunctionsMetaClass(CompanyFunctionsMetaClass):
|
|
287
|
+
"""all methods in CompanyFunctionsMetaClass delegated to company attribute"""
|
|
288
|
+
|
|
289
|
+
def __init__(self) -> None:
|
|
290
|
+
"""delegate CompanyFunctionsMetaClass methods to company attribute"""
|
|
291
|
+
super().__init__()
|
|
292
|
+
company_function_names = [
|
|
293
|
+
company_function
|
|
294
|
+
for company_function in dir(CompanyFunctionsMetaClass)
|
|
295
|
+
if not company_function.startswith("__")
|
|
296
|
+
and callable(getattr(CompanyFunctionsMetaClass, company_function))
|
|
297
|
+
]
|
|
298
|
+
for company_function_name in company_function_names:
|
|
299
|
+
|
|
300
|
+
def delegated_function(company_function_name: str) -> Callable:
|
|
301
|
+
# wrapper is necessary so that self.company is lazy loaded
|
|
302
|
+
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
|
|
303
|
+
fn = getattr(self.company, company_function_name)
|
|
304
|
+
response = fn(*args, **kwargs)
|
|
305
|
+
return response
|
|
306
|
+
|
|
307
|
+
company_function = getattr(
|
|
308
|
+
DelegatedCompanyFunctionsMetaClass, company_function_name
|
|
309
|
+
)
|
|
310
|
+
wrapper.__doc__ = company_function.__doc__
|
|
311
|
+
wrapper.__name__ = company_function.__name__
|
|
312
|
+
return wrapper
|
|
313
|
+
|
|
314
|
+
setattr(
|
|
315
|
+
DelegatedCompanyFunctionsMetaClass,
|
|
316
|
+
company_function_name,
|
|
317
|
+
delegated_function(company_function_name),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
@cached_property
|
|
321
|
+
def company(self) -> Any:
|
|
322
|
+
"""Set and return the company for the object"""
|
|
323
|
+
raise NotImplementedError("child classes must implement company property")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
for relationship in BusinessRelationshipType:
|
|
327
|
+
|
|
328
|
+
def _relationship_outer_wrapper(relationship_type: BusinessRelationshipType) -> cached_property:
|
|
329
|
+
"""Creates a cached property for a relationship type.
|
|
330
|
+
|
|
331
|
+
This function returns a property that retrieves the associated company's current and previous
|
|
332
|
+
relationships of the specified type.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
relationship_type (BusinessRelationshipType): The type of relationship to be wrapped.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
property: A cached property that calls the inner wrapper to retrieve the relationship data.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
def relationship_inner_wrapper(
|
|
342
|
+
self: Any,
|
|
343
|
+
) -> "BusinessRelationships":
|
|
344
|
+
"""Inner wrapper function for the relationship type.
|
|
345
|
+
|
|
346
|
+
This function retrieves the associated company's current and previous relationships
|
|
347
|
+
of the specified type.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
BusinessRelationships: A BusinessRelationships object containing the current and previous companies
|
|
351
|
+
associated with the relationship type.
|
|
352
|
+
"""
|
|
353
|
+
return self.relationships(relationship_type)
|
|
354
|
+
|
|
355
|
+
doc = f"Returns the associated company's current and previous {relationship_type}s"
|
|
356
|
+
relationship_inner_wrapper.__doc__ = doc
|
|
357
|
+
relationship_inner_wrapper.__name__ = relationship
|
|
358
|
+
|
|
359
|
+
return cached_property(relationship_inner_wrapper)
|
|
360
|
+
|
|
361
|
+
relationship_cached_property = _relationship_outer_wrapper(relationship)
|
|
362
|
+
relationship_cached_property.__set_name__(CompanyFunctionsMetaClass, relationship)
|
|
363
|
+
setattr(
|
|
364
|
+
CompanyFunctionsMetaClass,
|
|
365
|
+
relationship,
|
|
366
|
+
relationship_cached_property,
|
|
367
|
+
)
|