kensho-kfinance 2.7.0__py3-none-any.whl → 2.8.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.7.0.dist-info → kensho_kfinance-2.8.0.dist-info}/METADATA +1 -1
- kensho_kfinance-2.8.0.dist-info/RECORD +70 -0
- kfinance/CHANGELOG.md +3 -0
- kfinance/decimal_with_unit.py +78 -0
- kfinance/fetch.py +15 -16
- kfinance/kfinance.py +40 -44
- kfinance/meta_classes.py +28 -27
- kfinance/models/business_relationship_models.py +26 -0
- kfinance/models/capitalization_models.py +90 -0
- kfinance/models/competitor_models.py +13 -0
- kfinance/models/currency_models.py +345 -0
- kfinance/models/date_and_period_models.py +48 -0
- kfinance/models/id_models.py +7 -0
- kfinance/models/industry_models.py +12 -0
- kfinance/{constants.py → models/line_item_models.py} +1 -165
- kfinance/models/permission_models.py +15 -0
- kfinance/models/price_models.py +70 -0
- kfinance/models/segment_models.py +8 -0
- kfinance/models/statement_models.py +9 -0
- kfinance/tests/test_batch_requests.py +61 -31
- kfinance/tests/test_client.py +1 -1
- kfinance/tests/test_decimal_with_unit.py +60 -0
- kfinance/tests/test_example_notebook.py +1 -0
- kfinance/tests/test_fetch.py +23 -12
- kfinance/tests/test_models/__init__.py +0 -0
- kfinance/tests/test_models/test_capitalization_models.py +83 -0
- kfinance/tests/test_models/test_price_models.py +58 -0
- kfinance/tests/test_objects.py +29 -23
- kfinance/tests/test_tools.py +35 -11
- kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +1 -1
- kfinance/tool_calling/get_business_relationship_from_identifier.py +2 -1
- kfinance/tool_calling/get_capitalization_from_identifier.py +4 -5
- kfinance/tool_calling/get_competitors_from_identifier.py +2 -1
- kfinance/tool_calling/get_cusip_from_ticker.py +1 -1
- kfinance/tool_calling/get_earnings.py +1 -1
- kfinance/tool_calling/get_financial_line_item_from_identifier.py +3 -1
- kfinance/tool_calling/get_financial_statement_from_identifier.py +3 -1
- kfinance/tool_calling/get_history_metadata_from_identifier.py +2 -1
- kfinance/tool_calling/get_info_from_identifier.py +1 -1
- kfinance/tool_calling/get_isin_from_ticker.py +1 -1
- kfinance/tool_calling/get_latest.py +2 -1
- kfinance/tool_calling/get_latest_earnings.py +1 -1
- kfinance/tool_calling/get_merger_info_from_transaction_id.py +1 -1
- kfinance/tool_calling/get_mergers_from_identifier.py +1 -1
- kfinance/tool_calling/get_n_quarters_ago.py +2 -1
- kfinance/tool_calling/get_next_earnings.py +1 -1
- kfinance/tool_calling/get_prices_from_identifier.py +4 -3
- kfinance/tool_calling/get_segments_from_identifier.py +3 -1
- kfinance/tool_calling/get_transcript.py +1 -1
- kfinance/tool_calling/resolve_identifier.py +1 -1
- kfinance/tool_calling/shared_models.py +1 -1
- kfinance/version.py +2 -2
- kensho_kfinance-2.7.0.dist-info/RECORD +0 -54
- {kensho_kfinance-2.7.0.dist-info → kensho_kfinance-2.8.0.dist-info}/WHEEL +0 -0
- {kensho_kfinance-2.7.0.dist-info → kensho_kfinance-2.8.0.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.7.0.dist-info → kensho_kfinance-2.8.0.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-2.7.0.dist-info → kensho_kfinance-2.8.0.dist-info}/top_level.txt +0 -0
- /kfinance/{tests/scratch.py → models/__init__.py} +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing import Any, TypedDict
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, model_validator
|
|
6
|
+
|
|
7
|
+
from kfinance.decimal_with_unit import Money, Shares
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HistoryMetadata(TypedDict):
|
|
11
|
+
currency: str
|
|
12
|
+
symbol: str
|
|
13
|
+
exchange_name: str
|
|
14
|
+
instrument_type: str
|
|
15
|
+
first_trade_date: date
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Prices(BaseModel):
|
|
19
|
+
"""Prices represents prices for a stock for a specific "date".
|
|
20
|
+
|
|
21
|
+
I'm putting "date" in quotes because dates can be daily ("2024-01-01"),
|
|
22
|
+
weekly ("2024 Week 2"), monthly ("January 2024"), or annual ("2024").
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
date: str
|
|
26
|
+
open: Money
|
|
27
|
+
high: Money
|
|
28
|
+
low: Money
|
|
29
|
+
close: Money
|
|
30
|
+
volume: Shares
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PriceHistory(BaseModel):
|
|
34
|
+
"""PriceHistory represents stock prices over a time range."""
|
|
35
|
+
|
|
36
|
+
prices: list[Prices]
|
|
37
|
+
|
|
38
|
+
@model_validator(mode="before")
|
|
39
|
+
@classmethod
|
|
40
|
+
def inject_currency_into_data(cls, data: Any) -> Any:
|
|
41
|
+
"""Inject the currency into each open/high/low/close price.
|
|
42
|
+
|
|
43
|
+
The price history response only includes the currency as a top level element.
|
|
44
|
+
However, the Prices model expects the unit to be included with each price.
|
|
45
|
+
Before:
|
|
46
|
+
{
|
|
47
|
+
"date": "2024-06-25",
|
|
48
|
+
"open": "445.790000",
|
|
49
|
+
"high": "449.240000",
|
|
50
|
+
...
|
|
51
|
+
}
|
|
52
|
+
After:
|
|
53
|
+
{
|
|
54
|
+
"date": "2024-06-25",
|
|
55
|
+
"open": {"value": "445.790000", "unit": "USD"},
|
|
56
|
+
"high": {"value": "449.240000", "unit": "USD"},
|
|
57
|
+
...
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Note: Volume does not need the unit injected because the Shares class
|
|
61
|
+
already has "Shares" encoded. However, currencies differ between companies,
|
|
62
|
+
so we need to inject that information.
|
|
63
|
+
"""
|
|
64
|
+
if isinstance(data, dict) and "currency" in data:
|
|
65
|
+
data = deepcopy(data)
|
|
66
|
+
currency = data["currency"]
|
|
67
|
+
for capitalization in data["prices"]:
|
|
68
|
+
for key in ["open", "high", "low", "close"]:
|
|
69
|
+
capitalization[key] = dict(unit=currency, value=capitalization[key])
|
|
70
|
+
return data
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
from concurrent.futures import ThreadPoolExecutor
|
|
2
|
+
from decimal import Decimal
|
|
2
3
|
import time
|
|
3
4
|
from typing import Any, Dict
|
|
4
5
|
from unittest import TestCase
|
|
5
6
|
from unittest.mock import PropertyMock, patch
|
|
6
7
|
|
|
7
|
-
import numpy as np
|
|
8
|
-
import pandas as pd
|
|
9
8
|
import pytest
|
|
10
9
|
import requests
|
|
11
10
|
import requests_mock
|
|
12
11
|
|
|
13
12
|
from kfinance.batch_request_handling import MAX_WORKERS_CAP
|
|
13
|
+
from kfinance.decimal_with_unit import Money, Shares
|
|
14
14
|
from kfinance.fetch import KFinanceApiClient
|
|
15
|
-
from kfinance.kfinance import Companies, Company, Ticker, TradingItems
|
|
15
|
+
from kfinance.kfinance import Companies, Company, Ticker, TradingItem, TradingItems
|
|
16
|
+
from kfinance.models.price_models import PriceHistory, Prices
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@pytest.fixture(autouse=True)
|
|
@@ -77,46 +78,75 @@ class TestTradingItem(TestCase):
|
|
|
77
78
|
m.get(
|
|
78
79
|
"https://kfinance.kensho.com/api/v1/pricing/2/none/none/day/adjusted",
|
|
79
80
|
json={
|
|
81
|
+
"currency": "USD",
|
|
80
82
|
"prices": [
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
{
|
|
84
|
+
"date": "2024-04-11",
|
|
85
|
+
"open": "1.0000",
|
|
86
|
+
"high": "2.0000",
|
|
87
|
+
"low": "3.0000",
|
|
88
|
+
"close": "4.0000",
|
|
89
|
+
"volume": "5",
|
|
90
|
+
},
|
|
91
|
+
],
|
|
84
92
|
},
|
|
85
93
|
)
|
|
86
94
|
m.get(
|
|
87
95
|
"https://kfinance.kensho.com/api/v1/pricing/3/none/none/day/adjusted",
|
|
88
96
|
json={
|
|
97
|
+
"currency": "USD",
|
|
89
98
|
"prices": [
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
{
|
|
100
|
+
"date": "2024-04-11",
|
|
101
|
+
"open": "5.0000",
|
|
102
|
+
"high": "6.0000",
|
|
103
|
+
"low": "7.0000",
|
|
104
|
+
"close": "8.0000",
|
|
105
|
+
"volume": "9",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
93
108
|
},
|
|
94
109
|
)
|
|
95
110
|
|
|
96
|
-
|
|
111
|
+
trading_item_2 = TradingItem(
|
|
112
|
+
kfinance_api_client=self.kfinance_api_client, trading_item_id=2
|
|
113
|
+
)
|
|
114
|
+
trading_item_3 = TradingItem(
|
|
115
|
+
kfinance_api_client=self.kfinance_api_client, trading_item_id=3
|
|
116
|
+
)
|
|
97
117
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
trading_items = TradingItems(
|
|
119
|
+
self.kfinance_api_client, trading_items=[trading_item_2, trading_item_3]
|
|
120
|
+
)
|
|
121
|
+
expected_result = {
|
|
122
|
+
trading_item_2: PriceHistory(
|
|
123
|
+
prices=[
|
|
124
|
+
Prices(
|
|
125
|
+
date="2024-04-11",
|
|
126
|
+
open=Money(value=Decimal("1.00"), unit="USD", conventional_decimals=2),
|
|
127
|
+
high=Money(value=Decimal("2.00"), unit="USD", conventional_decimals=2),
|
|
128
|
+
low=Money(value=Decimal("3.00"), unit="USD", conventional_decimals=2),
|
|
129
|
+
close=Money(value=Decimal("4.00"), unit="USD", conventional_decimals=2),
|
|
130
|
+
volume=Shares(value=Decimal("5"), unit="Shares", conventional_decimals=0),
|
|
131
|
+
)
|
|
132
|
+
]
|
|
133
|
+
),
|
|
134
|
+
trading_item_3: PriceHistory(
|
|
135
|
+
prices=[
|
|
136
|
+
Prices(
|
|
137
|
+
date="2024-04-11",
|
|
138
|
+
open=Money(value=Decimal("5.00"), unit="USD", conventional_decimals=2),
|
|
139
|
+
high=Money(value=Decimal("6.00"), unit="USD", conventional_decimals=2),
|
|
140
|
+
low=Money(value=Decimal("7.00"), unit="USD", conventional_decimals=2),
|
|
141
|
+
close=Money(value=Decimal("8.00"), unit="USD", conventional_decimals=2),
|
|
142
|
+
volume=Shares(value=Decimal("9"), unit="Shares", conventional_decimals=0),
|
|
143
|
+
)
|
|
144
|
+
]
|
|
145
|
+
),
|
|
108
146
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
trading_item_id = k.trading_item_id
|
|
113
|
-
pd.testing.assert_frame_equal(
|
|
114
|
-
v,
|
|
115
|
-
pd.DataFrame(expected_dictionary_based_result[trading_item_id])
|
|
116
|
-
.set_index("date")
|
|
117
|
-
.apply(pd.to_numeric)
|
|
118
|
-
.replace(np.nan, None),
|
|
119
|
-
)
|
|
147
|
+
|
|
148
|
+
result = trading_items.history()
|
|
149
|
+
assert result == expected_result
|
|
120
150
|
|
|
121
151
|
@requests_mock.Mocker()
|
|
122
152
|
def test_large_batch_request_property(self, m):
|
kfinance/tests/test_client.py
CHANGED
|
@@ -2,8 +2,8 @@ from unittest.mock import Mock
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from kfinance.constants import Permission
|
|
6
5
|
from kfinance.kfinance import Client
|
|
6
|
+
from kfinance.models.permission_models import Permission
|
|
7
7
|
from kfinance.tool_calling import (
|
|
8
8
|
GetBusinessRelationshipFromIdentifier,
|
|
9
9
|
GetEarnings,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from kfinance.decimal_with_unit import DecimalWithUnit, Money, Shares
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestDecimalWithUnit:
|
|
10
|
+
@pytest.mark.parametrize(
|
|
11
|
+
"conventional_decimals, input, expected_value",
|
|
12
|
+
[
|
|
13
|
+
pytest.param(2, "0.10", Decimal("0.10"), id="input matches conventional decimals"),
|
|
14
|
+
pytest.param(
|
|
15
|
+
1, "0.10", Decimal("0.1"), id="input more precise than conventional decimals"
|
|
16
|
+
),
|
|
17
|
+
pytest.param(
|
|
18
|
+
3, "0.10", Decimal("0.100"), id="input less precise than conventional decimals"
|
|
19
|
+
),
|
|
20
|
+
pytest.param(1, "0.11", Decimal("0.1"), id="input gets rounded if necessary"),
|
|
21
|
+
],
|
|
22
|
+
)
|
|
23
|
+
def test_quantize_value(
|
|
24
|
+
self, conventional_decimals: int, input: str, expected_value: Decimal
|
|
25
|
+
) -> None:
|
|
26
|
+
"""
|
|
27
|
+
WHEN a DecimalWithUnit gets deserialized
|
|
28
|
+
THEN the value gets quantized to the conventional_decimals
|
|
29
|
+
"""
|
|
30
|
+
dwu = DecimalWithUnit.model_validate(
|
|
31
|
+
dict(value=input, conventional_decimals=conventional_decimals, unit="foo")
|
|
32
|
+
)
|
|
33
|
+
assert dwu.value == expected_value
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestMoney:
|
|
37
|
+
@pytest.mark.parametrize("currency, expected_conventional_decimals", [("USD", 2), ("BIF", 0)])
|
|
38
|
+
def test_conventional_decimals_injection(
|
|
39
|
+
self, currency: str, expected_conventional_decimals: int
|
|
40
|
+
) -> None:
|
|
41
|
+
"""
|
|
42
|
+
GIVEN a value and unit dict
|
|
43
|
+
WHEN we deserialize the dict into a Money object
|
|
44
|
+
THEN the conventional decimals for the currency get injected.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
money = Money.model_validate({"value": 1, "unit": currency})
|
|
48
|
+
assert money.conventional_decimals == expected_conventional_decimals
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestShares:
|
|
52
|
+
@pytest.mark.parametrize("input", ["1", 1, Decimal(1), {"value": 1}])
|
|
53
|
+
def test_share_deserialization(self, input: Any):
|
|
54
|
+
"""
|
|
55
|
+
GIVEN a str, int, decimal, or dict with value key
|
|
56
|
+
WHEN we deserialize the input into a Shares object
|
|
57
|
+
THEN all of these inputs are accepted.
|
|
58
|
+
"""
|
|
59
|
+
shares = Shares.model_validate(input)
|
|
60
|
+
assert shares.value == Decimal(1)
|
kfinance/tests/test_fetch.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from unittest import TestCase
|
|
2
2
|
from unittest.mock import MagicMock
|
|
3
3
|
|
|
4
|
+
from pydantic import ValidationError
|
|
4
5
|
import pytest
|
|
5
6
|
from requests_mock import Mocker
|
|
6
7
|
|
|
7
|
-
from kfinance.constants import BusinessRelationshipType, Periodicity, PeriodType
|
|
8
8
|
from kfinance.fetch import KFinanceApiClient
|
|
9
9
|
from kfinance.kfinance import Client
|
|
10
|
+
from kfinance.models.business_relationship_models import BusinessRelationshipType
|
|
11
|
+
from kfinance.models.date_and_period_models import Periodicity, PeriodType
|
|
10
12
|
from kfinance.pydantic_models import (
|
|
11
13
|
CompanyIdAndName,
|
|
12
14
|
RelationshipResponse,
|
|
@@ -53,21 +55,28 @@ class TestFetchItem(TestCase):
|
|
|
53
55
|
expected_fetch_url = (
|
|
54
56
|
f"{self.kfinance_api_client.url_base}pricing/{trading_item_id}/none/none/none/adjusted"
|
|
55
57
|
)
|
|
56
|
-
|
|
58
|
+
# Validation error is ok, we only care that the function was called with the correct url
|
|
59
|
+
with pytest.raises(ValidationError):
|
|
60
|
+
self.kfinance_api_client.fetch_history(trading_item_id=trading_item_id)
|
|
57
61
|
self.kfinance_api_client.fetch.assert_called_with(expected_fetch_url)
|
|
58
62
|
|
|
63
|
+
def test_fetch_history_with_dates(self) -> None:
|
|
64
|
+
trading_item_id = 2629108
|
|
59
65
|
start_date = "2025-01-01"
|
|
60
66
|
end_date = "2025-01-31"
|
|
61
67
|
is_adjusted = False
|
|
62
68
|
periodicity = Periodicity.day
|
|
63
69
|
expected_fetch_url = f"{self.kfinance_api_client.url_base}pricing/{trading_item_id}/{start_date}/{end_date}/{periodicity.value}/unadjusted"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
|
|
71
|
+
# Validation error is ok, we only care that the function was called with the correct url
|
|
72
|
+
with pytest.raises(ValidationError):
|
|
73
|
+
self.kfinance_api_client.fetch_history(
|
|
74
|
+
trading_item_id=trading_item_id,
|
|
75
|
+
is_adjusted=is_adjusted,
|
|
76
|
+
start_date=start_date,
|
|
77
|
+
end_date=end_date,
|
|
78
|
+
periodicity=periodicity,
|
|
79
|
+
)
|
|
71
80
|
self.kfinance_api_client.fetch.assert_called_with(expected_fetch_url)
|
|
72
81
|
|
|
73
82
|
def test_fetch_history_metadata(self) -> None:
|
|
@@ -310,9 +319,11 @@ class TestMarketCap:
|
|
|
310
319
|
expected_fetch_url = (
|
|
311
320
|
f"{client.url_base}market_cap/{company_id}/{start_date_url}/{end_date_url}"
|
|
312
321
|
)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
322
|
+
# Validation error is ok, we only care that the function was called with the correct url
|
|
323
|
+
with pytest.raises(ValidationError):
|
|
324
|
+
client.fetch_market_caps_tevs_and_shares_outstanding(
|
|
325
|
+
company_id=company_id, start_date=start_date, end_date=end_date
|
|
326
|
+
)
|
|
316
327
|
client.fetch.assert_called_with(expected_fetch_url)
|
|
317
328
|
|
|
318
329
|
def test_fetch_permissions(self):
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
from kfinance.decimal_with_unit import Money, Shares
|
|
5
|
+
from kfinance.models.capitalization_models import (
|
|
6
|
+
Capitalization,
|
|
7
|
+
Capitalizations,
|
|
8
|
+
DailyCapitalization,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestCapitalizations:
|
|
13
|
+
api_resp = {
|
|
14
|
+
"currency": "USD",
|
|
15
|
+
"market_caps": [
|
|
16
|
+
{
|
|
17
|
+
"date": "2024-06-24",
|
|
18
|
+
"market_cap": "139231113000.000000",
|
|
19
|
+
"tev": "153942113000.000000",
|
|
20
|
+
"shares_outstanding": 312900000,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"date": "2024-06-25",
|
|
24
|
+
"market_cap": "140423262000.000000",
|
|
25
|
+
"tev": "155134262000.000000",
|
|
26
|
+
"shares_outstanding": 312900000,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def test_capitalizations_deserialization(self) -> None:
|
|
32
|
+
"""
|
|
33
|
+
GIVEN a capitalizations API response
|
|
34
|
+
WHEN we deserialize the response into a Capitalizations object
|
|
35
|
+
THEN the deserialization succeeds and returns the expected value.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
expected_capitalizations = Capitalizations.model_construct(
|
|
39
|
+
capitalizations=[
|
|
40
|
+
DailyCapitalization(
|
|
41
|
+
date=date(2024, 6, 24),
|
|
42
|
+
market_cap=Money(
|
|
43
|
+
value=Decimal("139231113000.00"), unit="USD", conventional_decimals=2
|
|
44
|
+
),
|
|
45
|
+
tev=Money(
|
|
46
|
+
value=Decimal("153942113000.00"), unit="USD", conventional_decimals=2
|
|
47
|
+
),
|
|
48
|
+
shares_outstanding=Shares(
|
|
49
|
+
value=Decimal("312900000"), unit="Shares", conventional_decimals=0
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
DailyCapitalization(
|
|
53
|
+
date=date(2024, 6, 25),
|
|
54
|
+
market_cap=Money(
|
|
55
|
+
value=Decimal("140423262000.00"), unit="USD", conventional_decimals=2
|
|
56
|
+
),
|
|
57
|
+
tev=Money(
|
|
58
|
+
value=Decimal("155134262000.00"), unit="USD", conventional_decimals=2
|
|
59
|
+
),
|
|
60
|
+
shares_outstanding=Shares(
|
|
61
|
+
value=Decimal("312900000"), unit="Shares", conventional_decimals=0
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
assert Capitalizations.model_validate(self.api_resp) == expected_capitalizations
|
|
68
|
+
|
|
69
|
+
def test_single_attribute_serialization(self):
|
|
70
|
+
"""
|
|
71
|
+
GIVEN a Capitalizations object
|
|
72
|
+
WHEN we only want to jsonify a single attribute like market_cap
|
|
73
|
+
THEN that attribute gets returned in an LLM-readable, jsonifyable dict format
|
|
74
|
+
"""
|
|
75
|
+
expected_serialization = {
|
|
76
|
+
"market_cap": [
|
|
77
|
+
{"2024-06-24": {"unit": "USD", "value": "139231113000.00"}},
|
|
78
|
+
{"2024-06-25": {"unit": "USD", "value": "140423262000.00"}},
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
capitalizations = Capitalizations.model_validate(self.api_resp)
|
|
82
|
+
serialized = capitalizations.jsonify_single_attribute(Capitalization.market_cap)
|
|
83
|
+
assert serialized == expected_serialization
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
from kfinance.decimal_with_unit import Money, Shares
|
|
4
|
+
from kfinance.models.price_models import PriceHistory, Prices
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestPriceHistory:
|
|
8
|
+
api_resp = {
|
|
9
|
+
"currency": "USD",
|
|
10
|
+
"prices": [
|
|
11
|
+
{
|
|
12
|
+
"date": "2024-06-25",
|
|
13
|
+
"open": "445.790000",
|
|
14
|
+
"high": "449.240000",
|
|
15
|
+
"low": "442.770000",
|
|
16
|
+
"close": "448.780000",
|
|
17
|
+
"volume": "999134",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"date": "2024-06-26",
|
|
21
|
+
"open": "446.320000",
|
|
22
|
+
"high": "449.120000",
|
|
23
|
+
"low": "443.560000",
|
|
24
|
+
"close": "448.360000",
|
|
25
|
+
"volume": "1630769",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def test_price_history_deserialization(self) -> None:
|
|
31
|
+
"""
|
|
32
|
+
GIVEN a price history API response
|
|
33
|
+
WHEN we deserialize the response into a PriceHistory object
|
|
34
|
+
THEN the deserialization succeeds and returns the expected value.
|
|
35
|
+
"""
|
|
36
|
+
expected_price_history = PriceHistory.model_construct(
|
|
37
|
+
prices=[
|
|
38
|
+
Prices(
|
|
39
|
+
date="2024-06-25",
|
|
40
|
+
open=Money(value=Decimal("445.79"), unit="USD", conventional_decimals=2),
|
|
41
|
+
high=Money(value=Decimal("449.24"), unit="USD", conventional_decimals=2),
|
|
42
|
+
low=Money(value=Decimal("442.77"), unit="USD", conventional_decimals=2),
|
|
43
|
+
close=Money(value=Decimal("448.78"), unit="USD", conventional_decimals=2),
|
|
44
|
+
volume=Shares(value=Decimal("999134"), unit="Shares", conventional_decimals=0),
|
|
45
|
+
),
|
|
46
|
+
Prices(
|
|
47
|
+
date="2024-06-26",
|
|
48
|
+
open=Money(value=Decimal("446.32"), unit="USD", conventional_decimals=2),
|
|
49
|
+
high=Money(value=Decimal("449.12"), unit="USD", conventional_decimals=2),
|
|
50
|
+
low=Money(value=Decimal("443.56"), unit="USD", conventional_decimals=2),
|
|
51
|
+
close=Money(value=Decimal("448.36"), unit="USD", conventional_decimals=2),
|
|
52
|
+
volume=Shares(value=Decimal("1630769"), unit="Shares", conventional_decimals=0),
|
|
53
|
+
),
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
price_history = PriceHistory.model_validate(self.api_resp)
|
|
58
|
+
assert price_history == expected_price_history
|
kfinance/tests/test_objects.py
CHANGED
|
@@ -10,7 +10,6 @@ import pandas as pd
|
|
|
10
10
|
from PIL.Image import open as image_open
|
|
11
11
|
import time_machine
|
|
12
12
|
|
|
13
|
-
from kfinance.constants import BusinessRelationshipType
|
|
14
13
|
from kfinance.kfinance import (
|
|
15
14
|
AdvisedCompany,
|
|
16
15
|
BusinessRelationships,
|
|
@@ -22,6 +21,8 @@ from kfinance.kfinance import (
|
|
|
22
21
|
TradingItem,
|
|
23
22
|
Transcript,
|
|
24
23
|
)
|
|
24
|
+
from kfinance.models.business_relationship_models import BusinessRelationshipType
|
|
25
|
+
from kfinance.models.capitalization_models import Capitalizations
|
|
25
26
|
from kfinance.pydantic_models import CompanyIdAndName, RelationshipResponse
|
|
26
27
|
|
|
27
28
|
|
|
@@ -329,23 +330,26 @@ class MockKFinanceApiClient:
|
|
|
329
330
|
company_id: int,
|
|
330
331
|
start_date: Optional[str] = None,
|
|
331
332
|
end_date: Optional[str] = None,
|
|
332
|
-
) ->
|
|
333
|
-
return
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
333
|
+
) -> Capitalizations:
|
|
334
|
+
return Capitalizations.model_validate(
|
|
335
|
+
{
|
|
336
|
+
"currency": "USD",
|
|
337
|
+
"market_caps": [
|
|
338
|
+
{
|
|
339
|
+
"date": "2025-01-01",
|
|
340
|
+
"market_cap": "3133802247084.000000",
|
|
341
|
+
"tev": "3152211247084.000000",
|
|
342
|
+
"shares_outstanding": 7434880776,
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"date": "2025-01-02",
|
|
346
|
+
"market_cap": "3112092395218.000000",
|
|
347
|
+
"tev": "3130501395218.000000",
|
|
348
|
+
"shares_outstanding": 7434880776,
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
}
|
|
352
|
+
)
|
|
349
353
|
|
|
350
354
|
def fetch_segments(
|
|
351
355
|
self,
|
|
@@ -843,12 +847,14 @@ class TestTicker(TestCase):
|
|
|
843
847
|
THEN the Ticker object can correctly extract market caps from the dict.
|
|
844
848
|
"""
|
|
845
849
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
+
expected_response = {
|
|
851
|
+
"market_cap": [
|
|
852
|
+
{"2025-01-01": {"unit": "USD", "value": "3133802247084.00"}},
|
|
853
|
+
{"2025-01-02": {"unit": "USD", "value": "3112092395218.00"}},
|
|
854
|
+
]
|
|
855
|
+
}
|
|
850
856
|
market_caps = self.msft_ticker_from_ticker.market_cap()
|
|
851
|
-
|
|
857
|
+
assert market_caps == expected_response
|
|
852
858
|
|
|
853
859
|
|
|
854
860
|
class TestTranscript(TestCase):
|