kensho-kfinance 1.2.0__py3-none-any.whl → 2.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 (35) hide show
  1. {kensho_kfinance-1.2.0.dist-info → kensho_kfinance-2.0.0.dist-info}/METADATA +13 -9
  2. kensho_kfinance-2.0.0.dist-info/RECORD +40 -0
  3. {kensho_kfinance-1.2.0.dist-info → kensho_kfinance-2.0.0.dist-info}/WHEEL +1 -1
  4. kfinance/CHANGELOG.md +10 -0
  5. kfinance/constants.py +51 -8
  6. kfinance/fetch.py +68 -29
  7. kfinance/kfinance.py +128 -38
  8. kfinance/meta_classes.py +3 -9
  9. kfinance/tests/test_fetch.py +7 -26
  10. kfinance/tests/test_tools.py +430 -0
  11. kfinance/tool_calling/README.md +38 -0
  12. kfinance/tool_calling/__init__.py +47 -0
  13. kfinance/tool_calling/get_business_relationship_from_identifier.py +28 -0
  14. kfinance/tool_calling/get_capitalization_from_identifier.py +35 -0
  15. kfinance/tool_calling/get_company_id_from_identifier.py +14 -0
  16. kfinance/tool_calling/get_cusip_from_ticker.py +18 -0
  17. kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +17 -0
  18. kfinance/tool_calling/get_financial_line_item_from_identifier.py +45 -0
  19. kfinance/tool_calling/get_financial_statement_from_identifier.py +41 -0
  20. kfinance/tool_calling/get_history_metadata_from_identifier.py +15 -0
  21. kfinance/tool_calling/get_info_from_identifier.py +14 -0
  22. kfinance/tool_calling/get_isin_from_ticker.py +18 -0
  23. kfinance/tool_calling/get_latest.py +21 -0
  24. kfinance/tool_calling/get_n_quarters_ago.py +21 -0
  25. kfinance/tool_calling/get_prices_from_identifier.py +44 -0
  26. kfinance/tool_calling/get_security_id_from_identifier.py +14 -0
  27. kfinance/tool_calling/get_trading_item_id_from_identifier.py +14 -0
  28. kfinance/tool_calling/shared_models.py +53 -0
  29. kfinance/version.py +2 -2
  30. kensho_kfinance-1.2.0.dist-info/RECORD +0 -23
  31. kfinance/llm_tools.py +0 -747
  32. kfinance/tool_schemas.py +0 -148
  33. {kensho_kfinance-1.2.0.dist-info → kensho_kfinance-2.0.0.dist-info}/licenses/AUTHORS.md +0 -0
  34. {kensho_kfinance-1.2.0.dist-info → kensho_kfinance-2.0.0.dist-info}/licenses/LICENSE +0 -0
  35. {kensho_kfinance-1.2.0.dist-info → kensho_kfinance-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kensho-kfinance
3
- Version: 1.2.0
3
+ Version: 2.0.0
4
4
  Summary: Python CLI for kFinance
5
5
  Author-email: Luke Brown <luke.brown@kensho.com>, Michelle Keoy <michelle.keoy@kensho.com>, Keith Page <keith.page@kensho.com>, Matthew Rosen <matthew.rosen@kensho.com>, Nick Roshdieh <nick.roshdieh@kensho.com>
6
6
  Project-URL: source, https://github.com/kensho-technologies/kfinance
@@ -12,30 +12,34 @@ Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.md
15
- Requires-Dist: python-dateutil<2.9,>=2.8.2
16
- Requires-Dist: requests<3,>=2.22.0
17
- Requires-Dist: urllib3>=1.21.1
18
- Requires-Dist: pyjwt>=2.8.0
15
+ Requires-Dist: langchain-core>=0.3.15
16
+ Requires-Dist: langchain-google-genai<3,>=2.1.0
19
17
  Requires-Dist: numpy>=1.22.4
20
18
  Requires-Dist: pandas>=2.0.0
21
- Requires-Dist: types-requests<3,>=2.22.0
22
19
  Requires-Dist: pillow>=10
23
- Requires-Dist: langchain-core>=0.3.15
20
+ Requires-Dist: pydantic<3,>=2.10.0
21
+ Requires-Dist: pyjwt>=2.8.0
22
+ Requires-Dist: python-dateutil<2.9,>=2.8.2
24
23
  Requires-Dist: strenum>=0.4.15
24
+ Requires-Dist: tabulate>=0.9.0
25
+ Requires-Dist: types-requests<3,>=2.22.0
26
+ Requires-Dist: requests<3,>=2.22.0
27
+ Requires-Dist: urllib3>=1.21.1
25
28
  Provides-Extra: dev
26
29
  Requires-Dist: coverage<8,>=7.6.10; extra == "dev"
27
30
  Requires-Dist: mypy<2,>=1.15.0; extra == "dev"
28
31
  Requires-Dist: pytest<7,>=6.1.2; extra == "dev"
29
32
  Requires-Dist: pytest-cov<7,>=6.0.0; extra == "dev"
33
+ Requires-Dist: requests_mock<2,>=1.12; extra == "dev"
30
34
  Requires-Dist: ruff<1,>=0.9.4; extra == "dev"
31
- Requires-Dist: requests_mock<1.2,>=1.1; extra == "dev"
35
+ Requires-Dist: time_machine<3,>=2.1; extra == "dev"
32
36
  Dynamic: license-file
33
37
 
34
38
  # kFinance
35
39
 
36
40
  The kFinance Python library provides a simple interface for the LLM-ready API, streamlining API requests and response handling. It can be used on its own, with LLMs, or integrated into applications.
37
41
 
38
- For a complete overview of the functions, usage, and features of the kFinance Python library, please refer to documentation [here](https://kfinance.kensho.com/docs).
42
+ For a complete overview of the functions, usage, and features of the kFinance Python library, please refer to documentation [here](https://kensho-kfinance.readthedocs.io/en/stable/).
39
43
 
40
44
  Any questions or suggestions can be sent to the [kFinance Maintainers](kfinance-maintainers@kensho.com).
41
45
 
@@ -0,0 +1,40 @@
1
+ kensho_kfinance-2.0.0.dist-info/licenses/AUTHORS.md,sha256=0h9ClbI0pu1oKj1M28ROUsaxrbZg-6ukQGl6X4y9noI,68
2
+ kensho_kfinance-2.0.0.dist-info/licenses/LICENSE,sha256=bsY4blvSgq6o0FMQ3RXa2NCgco--nHCCchLXzxr6kms,83
3
+ kfinance/CHANGELOG.md,sha256=Gjau3iwqbJoOw-ReXpn9H3mkRhsWNTttBljc7D2-i4Y,641
4
+ kfinance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ kfinance/batch_request_handling.py,sha256=s0uXRY4CC-GnShI0i8PLPgLddRdqLyQ8jnqDKdmaZzs,5531
6
+ kfinance/constants.py,sha256=HrTh2QuhbA70xxcJ6N8WfTOJs6YiM30P59GbSDu_3AY,48312
7
+ kfinance/fetch.py,sha256=ygvp8q1o8mqPYY4ctQwch37dmprO0UMDteDumk8A-Gw,20172
8
+ kfinance/kfinance.py,sha256=nw2SPkixFht3PkcH4tZ1FnLhVxmyZm1_gbOqnmC6PBQ,49219
9
+ kfinance/meta_classes.py,sha256=sNezG7uHAQOWj2Xdjao6qc3z_gHppgJat7O9J2k_yqI,15730
10
+ kfinance/prompt.py,sha256=PtVB8c_FcSlVdyGgByAnIFGzuUuBaEjciCqnBJl1hSQ,25133
11
+ kfinance/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ kfinance/server_thread.py,sha256=jUnt1YGoYDkqqz1MbCwd44zJs1T_Z2BCgvj75bdtLgA,2574
13
+ kfinance/version.py,sha256=2thmcF9DS_Zp1zHI3N0kjBeMAuCP9mdDGnL0clqQpS8,511
14
+ kfinance/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ kfinance/tests/test_batch_requests.py,sha256=NsldoZqjjQpv0QKM2th80F1nru2tIF4LJeXU-RamQvA,9836
16
+ kfinance/tests/test_fetch.py,sha256=iJLOPBOb8xdAGF6Jel5jPhWb4iW6yPpt9f5tW4uZEyo,12189
17
+ kfinance/tests/test_objects.py,sha256=egXkhfoK2MepZdXtrBxzWxWni7f-zVCefUbnyDnWDOE,22554
18
+ kfinance/tests/test_tools.py,sha256=YG5UVWnR4EyHGsL4bBwUp2auSd55lcvUEEncXcMXw1Q,16941
19
+ kfinance/tool_calling/README.md,sha256=omJq7Us6r4U45QB7hRpLjRJ5BMalCkZkh4uXBjTbJXc,2022
20
+ kfinance/tool_calling/__init__.py,sha256=GDEeK2zM2tOfYam6ejwYACXecoRdlkRve_ZC5VTKugo,2038
21
+ kfinance/tool_calling/get_business_relationship_from_identifier.py,sha256=S_-nCsppi9tvwC81GMKvJd-eZSwRejT96PZhlOf5Wv4,1381
22
+ kfinance/tool_calling/get_capitalization_from_identifier.py,sha256=LmH5yEWSRJfAP4kRYHREdbMtrmxcZfgfbNB4dGQwyb4,1435
23
+ kfinance/tool_calling/get_company_id_from_identifier.py,sha256=18XAmWxTXRw1qaqw4WuA-yQ4d7kI2HnbMi32d-xNGzE,485
24
+ kfinance/tool_calling/get_cusip_from_ticker.py,sha256=uzVMbtcV4eKtWO4bYn-cicCo8tfAEGk-v95ZOON_cbA,533
25
+ kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py,sha256=3v73ZZz9n-Rdp5vp1RPmFD41nJ0Z3EgTjFSbnm30xdA,665
26
+ kfinance/tool_calling/get_financial_line_item_from_identifier.py,sha256=zJ-kcr_B9N3ZIXK1W-2TEUlF6tj4rhzmLSsW8EUhJn4,2064
27
+ kfinance/tool_calling/get_financial_statement_from_identifier.py,sha256=LyGmh81-_yljOhMVpsXa1M4u6hNJNP69Ms1M1NsxwPM,1817
28
+ kfinance/tool_calling/get_history_metadata_from_identifier.py,sha256=0h7xdhrIJg3pJJrrCgcsiZd3ft1GpConhNZe2pcOREA,666
29
+ kfinance/tool_calling/get_info_from_identifier.py,sha256=bv4lRhXWG9N7U8XEli9v44QEXNgf7-ZTuTDmE6XIeEg,659
30
+ kfinance/tool_calling/get_isin_from_ticker.py,sha256=yz7SOCFgwL03Zd8zG3pIfa3McB_tdRU-_oXYfSfqWoo,527
31
+ kfinance/tool_calling/get_latest.py,sha256=U_5SuGlD0G-u7YreiNkwEtx2zkUhZzUvwUwdL_tmqFk,724
32
+ kfinance/tool_calling/get_n_quarters_ago.py,sha256=VVKKyUNbmw7fS7e-vi4YXSdd_OO2bo8k50Grt5F_Mvo,651
33
+ kfinance/tool_calling/get_prices_from_identifier.py,sha256=b-cK9dWXbVBjppTORlC4HK9zd_qe8V0n98s823v0dCc,1796
34
+ kfinance/tool_calling/get_security_id_from_identifier.py,sha256=vPFYuFydUGxQ0b23CWkU3YxWVeoW-nzsrPbB9ZH8U_w,489
35
+ kfinance/tool_calling/get_trading_item_id_from_identifier.py,sha256=fB8QBFzhn-kgUL8dSF6w_rtO3zG5yLbm5s29Xz6U1AY,504
36
+ kfinance/tool_calling/shared_models.py,sha256=6y74yJCORHDVBxhhBJhTUOWCF9D11Lgg9eYQeDIT3aY,2047
37
+ kensho_kfinance-2.0.0.dist-info/METADATA,sha256=g5E_LG0HZWOwX6V8Bd7OE4Eeu7HoD69scUh5QqH3hSs,3051
38
+ kensho_kfinance-2.0.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
39
+ kensho_kfinance-2.0.0.dist-info/top_level.txt,sha256=kT_kNwVhfQoOAecY8W7uYah5xaHMoHoAdBIvXh6DaKM,9
40
+ kensho_kfinance-2.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
kfinance/CHANGELOG.md CHANGED
@@ -1,4 +1,14 @@
1
1
  # Changelog
2
+
3
+ ## v2.0.0
4
+ - Refactor llm tools to use langchain `BaseTool`.
5
+
6
+ ## v1.2.2
7
+ - Add tabulate and pydantic as dependencies
8
+
9
+ ## v1.2.1
10
+ - Add fetch for industry_code, industry_classification and gics_code.
11
+
2
12
  ## v1.2.0
3
13
  - Add batch requests for iterable classes
4
14
 
kfinance/constants.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from datetime import date
2
+ from enum import Enum
2
3
  from itertools import chain
3
4
  from typing import TypedDict
4
5
 
@@ -26,7 +27,45 @@ class IdentificationTriple(TypedDict):
26
27
  company_id: int
27
28
 
28
29
 
30
+ class Capitalization(Enum):
31
+ """The capitalization type"""
32
+
33
+ market_cap = "market_cap"
34
+ tev = "tev"
35
+ shares_outstanding = "shares_outstanding"
36
+
37
+
38
+ class PeriodType(Enum):
39
+ """The period type"""
40
+
41
+ annual = "annual"
42
+ quarterly = "quarterly"
43
+ ltm = "ltm"
44
+ ytd = "ytd"
45
+
46
+
47
+ class Periodicity(Enum):
48
+ """The frequency or interval at which the historical data points are sampled or aggregated. Periodicity is not the same as the date range. The date range specifies the time span over which the data is retrieved, while periodicity determines how the data within that date range is aggregated."""
49
+
50
+ day = "day"
51
+ week = "week"
52
+ month = "month"
53
+ year = "year"
54
+
55
+
56
+ class StatementType(Enum):
57
+ """The type of financial statement"""
58
+
59
+ balance_sheet = "balance_sheet"
60
+ bs = "balance_sheet"
61
+ income_statement = "income_statement"
62
+ cashflow = "cashflow"
63
+ cf = "cashflow"
64
+
65
+
29
66
  class BusinessRelationshipType(StrEnum):
67
+ """The type of business relationship"""
68
+
30
69
  supplier = "supplier"
31
70
  customer = "customer"
32
71
  distributor = "distributor"
@@ -76,6 +115,17 @@ class LatestPeriods(TypedDict):
76
115
  now: CurrentPeriod
77
116
 
78
117
 
118
+ class IndustryClassification(StrEnum):
119
+ sic = "sic"
120
+ naics = "naics"
121
+ nace = "nace"
122
+ anzsic = "anzsic"
123
+ spcapiqetf = "spcapiqetf"
124
+ spratings = "spratings"
125
+ gics = "gics"
126
+ simple = "simple"
127
+
128
+
79
129
  # all of these values must be lower case keys
80
130
  LINE_ITEMS: list[LineItemType] = [
81
131
  {
@@ -1661,18 +1711,11 @@ LINE_ITEMS: list[LineItemType] = [
1661
1711
  },
1662
1712
  ]
1663
1713
 
1664
- LINE_ITEMS_TO_DATA_ITEM_ID = {
1665
- line_item["name"]: line_item["dataitemid"] for line_item in LINE_ITEMS
1666
- }
1667
1714
 
1668
- LINE_ITEM_NAMES_AND_ALIASES = list(
1715
+ LINE_ITEM_NAMES_AND_ALIASES: list[str] = list(
1669
1716
  chain(*[[line_item["name"]] + list(line_item["aliases"]) for line_item in LINE_ITEMS])
1670
1717
  )
1671
1718
 
1672
- LINE_ITEM_SYNONYMS = {
1673
- alias: line_item["dataitemid"] for line_item in LINE_ITEMS for alias in line_item["aliases"]
1674
- }
1675
-
1676
1719
 
1677
1720
  FINANCIAL_STATEMENTS: dict[str, int] = {
1678
1721
  "income_statement": 1,
kfinance/fetch.py CHANGED
@@ -7,7 +7,13 @@ from uuid import uuid4
7
7
  import jwt
8
8
  import requests
9
9
 
10
- from .constants import BusinessRelationshipType, IdentificationTriple
10
+ from .constants import (
11
+ BusinessRelationshipType,
12
+ IdentificationTriple,
13
+ IndustryClassification,
14
+ Periodicity,
15
+ PeriodType,
16
+ )
11
17
 
12
18
 
13
19
  # version.py gets autogenerated by setuptools-scm and is not available
@@ -228,14 +234,14 @@ class KFinanceApiClient:
228
234
  is_adjusted: bool = True,
229
235
  start_date: Optional[str] = None,
230
236
  end_date: Optional[str] = None,
231
- periodicity: Optional[str] = None,
237
+ periodicity: Optional[Periodicity] = None,
232
238
  ) -> dict:
233
239
  """Get the pricing history."""
234
240
  url = (
235
241
  f"{self.url_base}pricing/{trading_item_id}/"
236
242
  f"{start_date if start_date is not None else 'none'}/"
237
243
  f"{end_date if end_date is not None else 'none'}/"
238
- f"{periodicity if periodicity is not None else 'none'}/"
244
+ f"{periodicity.value if periodicity else 'none'}/"
239
245
  f"{'adjusted' if is_adjusted else 'unadjusted'}"
240
246
  )
241
247
  return self.fetch(url)
@@ -265,14 +271,14 @@ class KFinanceApiClient:
265
271
  is_adjusted: bool = True,
266
272
  start_date: Optional[str] = None,
267
273
  end_date: Optional[str] = None,
268
- periodicity: Optional[str] = "",
274
+ periodicity: Optional[Periodicity] = None,
269
275
  ) -> bytes:
270
276
  """Get the price chart."""
271
277
  url = (
272
278
  f"{self.url_base}price_chart/{trading_item_id}/"
273
279
  f"{start_date if start_date is not None else 'none'}/"
274
280
  f"{end_date if end_date is not None else 'none'}/"
275
- f"{periodicity if periodicity is not None else 'none'}/"
281
+ f"{periodicity.value if periodicity else 'none'}/"
276
282
  f"{'adjusted' if is_adjusted else 'unadjusted'}"
277
283
  )
278
284
 
@@ -291,7 +297,7 @@ class KFinanceApiClient:
291
297
  self,
292
298
  company_id: int,
293
299
  statement_type: str,
294
- period_type: Optional[str] = None,
300
+ period_type: Optional[PeriodType] = None,
295
301
  start_year: Optional[int] = None,
296
302
  end_year: Optional[int] = None,
297
303
  start_quarter: Optional[int] = None,
@@ -300,7 +306,7 @@ class KFinanceApiClient:
300
306
  """Get a specified financial statement for a specified duration."""
301
307
  url = (
302
308
  f"{self.url_base}statements/{company_id}/{statement_type}/"
303
- f"{period_type if period_type is not None else 'none'}/"
309
+ f"{period_type.value if period_type else 'none'}/"
304
310
  f"{start_year if start_year is not None else 'none'}/"
305
311
  f"{end_year if end_year is not None else 'none'}/"
306
312
  f"{start_quarter if start_quarter is not None else 'none'}/"
@@ -312,7 +318,7 @@ class KFinanceApiClient:
312
318
  self,
313
319
  company_id: int,
314
320
  line_item: str,
315
- period_type: Optional[str] = None,
321
+ period_type: Optional[PeriodType] = None,
316
322
  start_year: Optional[int] = None,
317
323
  end_year: Optional[int] = None,
318
324
  start_quarter: Optional[int] = None,
@@ -321,7 +327,7 @@ class KFinanceApiClient:
321
327
  """Get a specified financial line item for a specified duration."""
322
328
  url = (
323
329
  f"{self.url_base}line_item/{company_id}/{line_item}/"
324
- f"{period_type if period_type is not None else 'none'}/"
330
+ f"{period_type.value if period_type else 'none'}/"
325
331
  f"{start_year if start_year is not None else 'none'}/"
326
332
  f"{end_year if end_year is not None else 'none'}/"
327
333
  f"{start_quarter if start_quarter is not None else 'none'}/"
@@ -368,26 +374,6 @@ class KFinanceApiClient:
368
374
  country_iso_code=country_iso_code, state_iso_code=state_iso_code, fetch_ticker=False
369
375
  )
370
376
 
371
- def fetch_simple_industry_groups(
372
- self, simple_industry: str, fetch_ticker: bool = True
373
- ) -> dict[str, list]:
374
- """Fetch simple industry groups"""
375
- url = f"{self.url_base}{'ticker_groups' if fetch_ticker else 'company_groups'}/industry/simple/{simple_industry}"
376
- return self.fetch(url)
377
-
378
- def fetch_ticker_simple_industry_groups(
379
- self, simple_industry: str
380
- ) -> dict[str, list[IdentificationTriple]]:
381
- """Fetch ticker simple industry groups"""
382
- return self.fetch_simple_industry_groups(simple_industry=simple_industry, fetch_ticker=True)
383
-
384
- def fetch_company_simple_industry_groups(self, simple_industry: str) -> dict[str, list[int]]:
385
- """Fetch company simple industry groups"""
386
- return self.fetch_simple_industry_groups(
387
- simple_industry=simple_industry,
388
- fetch_ticker=False,
389
- )
390
-
391
377
  def fetch_exchange_groups(
392
378
  self, exchange_code: str, fetch_ticker: bool = True
393
379
  ) -> dict[str, list]:
@@ -456,3 +442,56 @@ class KFinanceApiClient:
456
442
  """
457
443
  url = f"{self.url_base}relationship/{company_id}/{relationship_type}"
458
444
  return self.fetch(url)
445
+
446
+ def fetch_from_industry_code(
447
+ self,
448
+ industry_code: str,
449
+ industry_classification: IndustryClassification,
450
+ fetch_ticker: bool = True,
451
+ ) -> dict[str, list]:
452
+ """Fetches a list of companies or identification triples that are classified in the given industry_code and industry_classification."""
453
+
454
+ url = f"{self.url_base}{'ticker_groups' if fetch_ticker else 'company_groups'}/industry/{industry_classification}/{industry_code}"
455
+ return self.fetch(url)
456
+
457
+ def fetch_ticker_from_industry_code(
458
+ self,
459
+ industry_code: str,
460
+ industry_classification: IndustryClassification,
461
+ ) -> dict[str, list[IdentificationTriple]]:
462
+ """Fetches a list of identification triples that are classified in the given industry_code and industry_classification.
463
+
464
+ Returns a dictionary of shape {"tickers": List[{“company_id”: <company_id>, “security_id”: <security_id>, “trading_item_id”: <trading_item_id>}]}.
465
+ :param industry_code: The industry_code to filter on. The industry_code is a string corresponding to the Industry classifications ontology.
466
+ :type industry_code: str
467
+ :param industry_classification: The type of industry_classification to filter on.
468
+ :type industry_classification: IndustryClassification
469
+ :return: A dictionary containing the list of identification triple [company_id, security_id, trading_item_id] that are classified in the given industry_code and industry_classification.
470
+ :rtype: dict[str, list[IdentificationTriple]]
471
+ """
472
+ return self.fetch_from_industry_code(
473
+ industry_code=industry_code,
474
+ industry_classification=industry_classification,
475
+ fetch_ticker=True,
476
+ )
477
+
478
+ def fetch_company_from_industry_code(
479
+ self,
480
+ industry_code: str,
481
+ industry_classification: IndustryClassification,
482
+ ) -> dict[str, list[int]]:
483
+ """Fetches a list of companies that are classified in the given industry_code and industry_classification.
484
+
485
+ Returns a dictionary of shape {"companies": List[<company_id>]}.
486
+ :param industry_code: The industry_code to filter on. The industry_code is a string corresponding to the Industry classifications ontology.
487
+ :type industry_code: str
488
+ :param industry_classification: The type of industry_classification to filter on.
489
+ :type industry_classification: IndustryClassification
490
+ :return: A dictionary containing the list of companies that are classified in the given industry_code and industry_classification.
491
+ :rtype: dict[str, list[int]]
492
+ """
493
+ return self.fetch_from_industry_code(
494
+ industry_code=industry_code,
495
+ industry_classification=industry_classification,
496
+ fetch_ticker=False,
497
+ )
kfinance/kfinance.py CHANGED
@@ -7,16 +7,25 @@ from io import BytesIO
7
7
  import logging
8
8
  import re
9
9
  from sys import stdout
10
- from typing import Iterable, NamedTuple, Optional
10
+ from typing import TYPE_CHECKING, Any, Callable, Iterable, NamedTuple, Optional
11
11
  from urllib.parse import urljoin
12
12
  import webbrowser
13
13
 
14
+ import google.ai.generativelanguage_v1beta.types as gapic
15
+ from langchain_core.utils.function_calling import convert_to_openai_tool
16
+ from langchain_google_genai._function_utils import convert_to_genai_function_declarations
14
17
  import numpy as np
15
18
  import pandas as pd
16
19
  from PIL.Image import Image, open as image_open
17
20
 
18
21
  from .batch_request_handling import add_methods_of_singular_class_to_iterable_class
19
- from .constants import HistoryMetadata, IdentificationTriple, LatestPeriods, YearAndQuarter
22
+ from .constants import (
23
+ HistoryMetadata,
24
+ IdentificationTriple,
25
+ LatestPeriods,
26
+ Periodicity,
27
+ YearAndQuarter,
28
+ )
20
29
  from .fetch import (
21
30
  DEFAULT_API_HOST,
22
31
  DEFAULT_API_VERSION,
@@ -24,15 +33,6 @@ from .fetch import (
24
33
  DEFAULT_OKTA_HOST,
25
34
  KFinanceApiClient,
26
35
  )
27
- from .llm_tools import (
28
- _llm_tools,
29
- anthropic_tool_descriptions,
30
- gemini_tool_descriptions,
31
- get_latest,
32
- get_n_quarters_ago,
33
- langchain_tools,
34
- openai_tool_descriptions,
35
- )
36
36
  from .meta_classes import (
37
37
  CompanyFunctionsMetaClass,
38
38
  DelegatedCompanyFunctionsMetaClass,
@@ -41,6 +41,9 @@ from .prompt import PROMPT
41
41
  from .server_thread import ServerThread
42
42
 
43
43
 
44
+ if TYPE_CHECKING:
45
+ from kfinance.tool_calling.shared_models import KfinanceTool
46
+
44
47
  logger = logging.getLogger(__name__)
45
48
 
46
49
 
@@ -124,23 +127,20 @@ class TradingItem:
124
127
 
125
128
  def history(
126
129
  self,
127
- periodicity: str = "day",
130
+ periodicity: Periodicity = Periodicity.day,
128
131
  adjusted: bool = True,
129
132
  start_date: Optional[str] = None,
130
133
  end_date: Optional[str] = None,
131
134
  ) -> pd.DataFrame:
132
135
  """Retrieves the historical price data for a given asset over a specified date range.
133
136
 
134
- :param str periodicity: Determines the frequency of the historical data returned. Options are "day", "week", "month" and "year". This default to "day"
137
+ :param periodicity: Determines the frequency of the historical data returned. Defaults to Periodicity.day.
135
138
  :param Optional[bool] adjusted: Whether to retrieve adjusted prices that account for corporate actions such as dividends and splits, it defaults True
136
139
  :param Optional[str] start_date: The start date for historical price retrieval in format "YYYY-MM-DD", default to None
137
140
  :param Optional[str] end_date: The end date for historical price retrieval in format "YYYY-MM-DD", default to None
138
- :return: A pd.DataFrame containing historical price data with columns corresponding to the specified periodicity, with Date as the index, and columns "open", "high", "low", "close", "volume" in type decimal. The Date index is a string that depends on the periodicity. If periodicity="day", the Date index is the day in format "YYYY-MM-DD", eg "2024-05-13" If periodicity="week", the Date index is the week number of the year in format "YYYY Week ##", eg "2024 Week 2" If periodicity="month", the Date index is the month name of the year in format "<Month> YYYY", eg "January 2024". If periodicity="year", the Date index is the year in format "YYYY", eg "2024".
141
+ :return: A pd.DataFrame containing historical price data with columns corresponding to the specified periodicity, with Date as the index, and columns "open", "high", "low", "close", "volume" in type decimal. The Date index is a string that depends on the periodicity. If Periodicity.day, the Date index is the day in format "YYYY-MM-DD", eg "2024-05-13" If Periodicity.week, the Date index is the week number of the year in format "YYYY Week ##", eg "2024 Week 2" If Periodicity.month, the Date index is the month name of the year in format "<Month> YYYY", eg "January 2024". If Periodicity.year, the Date index is the year in format "YYYY", eg "2024".
139
142
  :rtype: pd.DataFrame
140
143
  """
141
- if periodicity not in {"day", "week", "month", "year"}:
142
- raise RuntimeError(f"Periodicity type {periodicity} is not valid.")
143
-
144
144
  if start_date and end_date:
145
145
  if (
146
146
  datetime.strptime(start_date, "%Y-%m-%d").date()
@@ -165,14 +165,14 @@ class TradingItem:
165
165
 
166
166
  def price_chart(
167
167
  self,
168
- periodicity: str = "day",
168
+ periodicity: Periodicity = Periodicity.day,
169
169
  adjusted: bool = True,
170
170
  start_date: Optional[str] = None,
171
171
  end_date: Optional[str] = None,
172
172
  ) -> Image:
173
173
  """Get the price chart.
174
174
 
175
- :param str periodicity: Determines the frequency of the historical data returned. Options are "day", "week", "month" and "year". This default to "day"
175
+ :param str periodicity: Determines the frequency of the historical data returned. Defaults to Periodicity.day.
176
176
  :param Optional[bool] adjusted: Whether to retrieve adjusted prices that account for corporate actions such as dividends and splits, it defaults True
177
177
  :param Optional[str] start_date: The start date for historical price retrieval in format "YYYY-MM-DD", default to None
178
178
  :param Optional[str] end_date: The end date for historical price retrieval in format "YYYY-MM-DD", default to None
@@ -180,9 +180,6 @@ class TradingItem:
180
180
  :rtype: Image
181
181
  """
182
182
 
183
- if periodicity not in {"day", "week", "month", "year"}:
184
- raise RuntimeError(f"Periodicity type {periodicity} is not valid.")
185
-
186
183
  if start_date and end_date:
187
184
  if (
188
185
  datetime.strptime(start_date, "%Y-%m-%d").date()
@@ -855,22 +852,22 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
855
852
 
856
853
  def history(
857
854
  self,
858
- periodicity: str = "day",
855
+ periodicity: Periodicity = Periodicity.day,
859
856
  adjusted: bool = True,
860
857
  start_date: Optional[str] = None,
861
858
  end_date: Optional[str] = None,
862
859
  ) -> pd.DataFrame:
863
860
  """Retrieves the historical price data for a given asset over a specified date range.
864
861
 
865
- :param periodicity: Determines the frequency of the historical data returned. Options are "day", "week", "month" and "year". This default to "day"
866
- :type periodicity: str
862
+ :param periodicity: Determines the frequency of the historical data returned. Defaults to Periodicity.day.
863
+ :type periodicity: Periodicity
867
864
  :param adjusted: Whether to retrieve adjusted prices that account for corporate actions such as dividends and splits, it defaults True
868
865
  :type adjusted: bool, optional
869
866
  :param start_date: The start date for historical price retrieval in format "YYYY-MM-DD", default to None
870
867
  :type start_date: str, optional
871
868
  :param end_date: The end date for historical price retrieval in format "YYYY-MM-DD", default to None
872
869
  :type end_date: str, optional
873
- :return: A pd.DataFrame containing historical price data with columns corresponding to the specified periodicity, with Date as the index, and columns "open", "high", "low", "close", "volume" in type decimal. The Date index is a string that depends on the periodicity. If periodicity="day", the Date index is the day in format "YYYY-MM-DD", eg "2024-05-13" If periodicity="week", the Date index is the week number of the year in format "YYYY Week ##", eg "2024 Week 2" If periodicity="month", the Date index is the month name of the year in format "<Month> YYYY", eg "January 2024". If periodicity="year", the Date index is the year in format "YYYY", eg "2024".
870
+ :return: A pd.DataFrame containing historical price data with columns corresponding to the specified periodicity, with Date as the index, and columns "open", "high", "low", "close", "volume" in type decimal. The Date index is a string that depends on the periodicity. If Periodicity.day, the Date index is the day in format "YYYY-MM-DD", eg "2024-05-13" If Periodicity.week, the Date index is the week number of the year in format "YYYY Week ##", eg "2024 Week 2" If Periodicity.month, the Date index is the month name of the year in format "<Month> YYYY", eg "January 2024". If Periodicity.year, the Date index is the year in format "YYYY", eg "2024".
874
871
  :rtype: pd.DataFrame
875
872
  """
876
873
  return self.primary_trading_item.history(
@@ -882,15 +879,15 @@ class Ticker(DelegatedCompanyFunctionsMetaClass):
882
879
 
883
880
  def price_chart(
884
881
  self,
885
- periodicity: str = "day",
882
+ periodicity: Periodicity = Periodicity.day,
886
883
  adjusted: bool = True,
887
884
  start_date: Optional[str] = None,
888
885
  end_date: Optional[str] = None,
889
886
  ) -> Image:
890
887
  """Get the price chart.
891
888
 
892
- :param periodicity: Determines the frequency of the historical data returned. Options are "day", "week", "month" and "year". This default to "day"
893
- :type periodicity: str
889
+ :param str periodicity: Determines the frequency of the historical data returned. Defaults to Periodicity.day.
890
+ :type periodicity: Periodicity
894
891
  :param adjusted: Whether to retrieve adjusted prices that account for corporate actions such as dividends and splits, it defaults True
895
892
  :type adjusted: bool, optional
896
893
  :param start_date: The start date for historical price retrieval in format "YYYY-MM-DD", default to None
@@ -1121,11 +1118,63 @@ class Client:
1121
1118
  )
1122
1119
  stdout.write("Login credentials received.\n")
1123
1120
 
1124
- self.tools = _llm_tools(self)
1125
- self.langchain_tools = langchain_tools(self.tools)
1126
- self.anthropic_tool_descriptions = anthropic_tool_descriptions
1127
- self.gemini_tool_descriptions = gemini_tool_descriptions
1128
- self.openai_tool_descriptions = openai_tool_descriptions
1121
+ self._tools: list[KfinanceTool] | None = None
1122
+
1123
+ @property
1124
+ def langchain_tools(self) -> list["KfinanceTool"]:
1125
+ """Return a list of all Kfinance tools for tool calling."""
1126
+ if self._tools is None:
1127
+ from kfinance.tool_calling import ALL_TOOLS
1128
+
1129
+ self._tools = [t(kfinance_client=self) for t in ALL_TOOLS] # type: ignore[call-arg]
1130
+ return self._tools
1131
+
1132
+ @property
1133
+ def tools(self) -> dict[str, Callable]:
1134
+ """Return a mapping of tool calling function names to the corresponding functions.
1135
+
1136
+ `tools` is intended for running without langchain. When running with langchain,
1137
+ use `langchain_tools`.
1138
+ """
1139
+ return {t.name: t.run_without_langchain for t in self.langchain_tools}
1140
+
1141
+ @property
1142
+ def anthropic_tool_descriptions(self) -> list[dict[str, Any]]:
1143
+ """Return tool descriptions for anthropic"""
1144
+
1145
+ anthropic_tool_descriptions = []
1146
+
1147
+ for tool in self.langchain_tools:
1148
+ # Copied from https://python.langchain.com/api_reference/_modules/langchain_anthropic/chat_models.html#convert_to_anthropic_tool
1149
+ # to avoid adding a langchain-anthropic dependency.
1150
+ oai_formatted = convert_to_openai_tool(tool)["function"]
1151
+ anthropic_tool_descriptions.append(
1152
+ dict(
1153
+ name=oai_formatted["name"],
1154
+ description=oai_formatted["description"],
1155
+ input_schema=oai_formatted["parameters"],
1156
+ )
1157
+ )
1158
+
1159
+ return anthropic_tool_descriptions
1160
+
1161
+ @property
1162
+ def gemini_tool_descriptions(self) -> gapic.Tool:
1163
+ """Return tool descriptions for gemini.
1164
+
1165
+ The conversion from BaseTool -> openai tool description -> google tool mirrors the
1166
+ langchain implementation.
1167
+ """
1168
+ openai_tool_descriptions = [
1169
+ convert_to_openai_tool(t)["function"] for t in self.langchain_tools
1170
+ ]
1171
+ return convert_to_genai_function_declarations(openai_tool_descriptions)
1172
+
1173
+ @property
1174
+ def openai_tool_descriptions(self) -> list[dict[str, Any]]:
1175
+ """Return tool descriptions for gemini"""
1176
+ openai_tool_descriptions = [convert_to_openai_tool(t) for t in self.langchain_tools]
1177
+ return openai_tool_descriptions
1129
1178
 
1130
1179
  @property
1131
1180
  def access_token(self) -> str:
@@ -1154,7 +1203,7 @@ class Client:
1154
1203
  :rtype: Ticker
1155
1204
  """
1156
1205
  if function_called:
1157
- self.kfinance_api_client.user_agent_source = "function_calling"
1206
+ self.kfinance_api_client.user_agent_source = "tool_calling"
1158
1207
  return Ticker(self.kfinance_api_client, str(identifier), exchange_code)
1159
1208
 
1160
1209
  def tickers(
@@ -1222,14 +1271,42 @@ class Client:
1222
1271
  )
1223
1272
 
1224
1273
  @staticmethod
1225
- def get_latest() -> LatestPeriods:
1274
+ def get_latest(use_local_timezone: bool = True) -> LatestPeriods:
1226
1275
  """Get the latest annual reporting year, latest quarterly reporting quarter and year, and current date.
1227
1276
 
1277
+ :param use_local_timezone: whether to use the local timezone of the user
1278
+ :type use_local_timezone: bool
1228
1279
  :return: A dict in the form of {"annual": {"latest_year": int}, "quarterly": {"latest_quarter": int, "latest_year": int}, "now": {"current_year": int, "current_quarter": int, "current_month": int, "current_date": str of Y-m-d}}
1229
1280
  :rtype: Latest
1230
1281
  """
1231
1282
 
1232
- return get_latest()
1283
+ datetime_now = datetime.now() if use_local_timezone else datetime.now(timezone.utc)
1284
+ current_year = datetime_now.year
1285
+ current_qtr = (datetime_now.month - 1) // 3 + 1
1286
+
1287
+ # Quarterly data. Get most recent year and quarter
1288
+ if current_qtr == 1:
1289
+ most_recent_year_qtrly = current_year - 1
1290
+ most_recent_qtr = 4
1291
+ else:
1292
+ most_recent_year_qtrly = current_year
1293
+ most_recent_qtr = current_qtr - 1
1294
+
1295
+ # Annual data. Get most recent year
1296
+ most_recent_year_annual = current_year - 1
1297
+
1298
+ current_month = datetime_now.month
1299
+ latest: LatestPeriods = {
1300
+ "annual": {"latest_year": most_recent_year_annual},
1301
+ "quarterly": {"latest_quarter": most_recent_qtr, "latest_year": most_recent_year_qtrly},
1302
+ "now": {
1303
+ "current_year": current_year,
1304
+ "current_quarter": current_qtr,
1305
+ "current_month": current_month,
1306
+ "current_date": datetime_now.date().isoformat(),
1307
+ },
1308
+ }
1309
+ return latest
1233
1310
 
1234
1311
  @staticmethod
1235
1312
  def get_n_quarters_ago(n: int) -> YearAndQuarter:
@@ -1240,4 +1317,17 @@ class Client:
1240
1317
  :rtype: YearAndQuarter
1241
1318
  """
1242
1319
 
1243
- return get_n_quarters_ago(n)
1320
+ datetime_now = datetime.now()
1321
+ current_qtr = (datetime_now.month - 1) // 3 + 1
1322
+ total_quarters_completed = datetime_now.year * 4 + current_qtr - 1
1323
+ total_quarters_completed_n_quarters_ago = total_quarters_completed - n
1324
+
1325
+ year_n_quarters_ago = total_quarters_completed_n_quarters_ago // 4
1326
+ quarter_n_quarters_ago = total_quarters_completed_n_quarters_ago % 4 + 1
1327
+
1328
+ year_quarter_n_quarters_ago: YearAndQuarter = {
1329
+ "year": year_n_quarters_ago,
1330
+ "quarter": quarter_n_quarters_ago,
1331
+ }
1332
+
1333
+ return year_quarter_n_quarters_ago