Skip to content

Commit 2e1b09b

Browse files
committed
Sanitize priceToBook for mixed currencies
1 parent 3d92f04 commit 2e1b09b

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

tests/test_ticker.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,74 @@ def test_empty_info(self):
10521052
self.assertCountEqual(['quoteType', 'symbol', 'underlyingSymbol', 'uuid', 'maxAge', 'trailingPegRatio'], data.keys())
10531053
self.assertIn("trailingPegRatio", data.keys(), "Did not find expected key 'trailingPegRatio' in info dict")
10541054

1055+
def test_price_to_book_mixed_currencies(self):
1056+
# Test for Issue #2593: priceToBook should be None when currency and financialCurrency differ.
1057+
1058+
from unittest.mock import Mock, patch
1059+
from yfinance.scrapers.quote import Quote
1060+
1061+
# Test Case 1: Mixed currencies (INR price, USD book value) - the main issue
1062+
quote = Quote(Mock(), "TEST.NS")
1063+
quote._info = {
1064+
"symbol": "TEST.NS",
1065+
"currentPrice": 1531.4, # INR
1066+
"bookValue": 2.67, # USD
1067+
"priceToBook": 573.6, # Invalid: 1531.4 / 2.67 but mismatched currencies
1068+
"currency": "INR",
1069+
"financialCurrency": "USD"
1070+
}
1071+
quote._sanitize_price_to_book()
1072+
self.assertIsNone(quote._info["priceToBook"],
1073+
"priceToBook should be None when currencies don't match")
1074+
self.assertEqual(quote._info["currency"], "INR")
1075+
self.assertEqual(quote._info["financialCurrency"], "USD")
1076+
1077+
# Test Case 2: Matching currencies - priceToBook should be preserved
1078+
quote2 = Quote(Mock(), "TEST.US")
1079+
quote2._info = {
1080+
"symbol": "TEST.US",
1081+
"currentPrice": 150.0,
1082+
"bookValue": 50.0,
1083+
"priceToBook": 3.0,
1084+
"currency": "USD",
1085+
"financialCurrency": "USD"
1086+
}
1087+
quote2._sanitize_price_to_book()
1088+
self.assertEqual(quote2._info["priceToBook"], 3.0,
1089+
"priceToBook should be preserved when currencies match")
1090+
1091+
# Test Case 3: Missing priceToBook - should not raise error
1092+
quote3 = Quote(Mock(), "TEST.IN")
1093+
quote3._info = {
1094+
"symbol": "TEST.IN",
1095+
"currency": "INR",
1096+
"financialCurrency": "USD"
1097+
}
1098+
quote3._sanitize_price_to_book() # Should not raise error
1099+
self.assertNotIn("priceToBook", quote3._info)
1100+
1101+
# Test Case 4: Missing financialCurrency - should not clear priceToBook
1102+
quote4 = Quote(Mock(), "TEST.XX")
1103+
quote4._info = {
1104+
"symbol": "TEST.XX",
1105+
"priceToBook": 2.5,
1106+
"currency": "EUR"
1107+
}
1108+
quote4._sanitize_price_to_book()
1109+
self.assertEqual(quote4._info["priceToBook"], 2.5,
1110+
"priceToBook should be preserved when financialCurrency is missing")
1111+
1112+
# Test Case 5: Missing currency - should not clear priceToBook
1113+
quote5 = Quote(Mock(), "TEST.YY")
1114+
quote5._info = {
1115+
"symbol": "TEST.YY",
1116+
"priceToBook": 2.5,
1117+
"financialCurrency": "EUR"
1118+
}
1119+
quote5._sanitize_price_to_book()
1120+
self.assertEqual(quote5._info["priceToBook"], 2.5,
1121+
"priceToBook should be preserved when currency is missing")
1122+
10551123
# def test_fast_info_matches_info(self):
10561124
# fast_info_keys = set()
10571125
# for ticker in self.tickers:

yfinance/scrapers/quote.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,26 @@ def _fetch_additional_info(self):
611611
return None
612612
return result
613613

614+
def _sanitize_price_to_book(self):
615+
"""
616+
Sanitize priceToBook when currencies don't match.
617+
618+
Some tickers (e.g., INFY.NS) have mismatched currencies where currentPrice is in one
619+
currency (e.g., INR) while bookValue is in another (e.g., USD). This causes Yahoo's
620+
priceToBook calculation to be meaningless. This method detects such cases and sets
621+
priceToBook to None to prevent propagating invalid data.
622+
"""
623+
if self._info is None:
624+
return
625+
626+
currency = self._info.get("currency")
627+
financial_currency = self._info.get("financialCurrency")
628+
629+
# If both currencies are present and differ, clear the priceToBook
630+
if (currency is not None and financial_currency is not None and
631+
currency != financial_currency and "priceToBook" in self._info):
632+
self._info["priceToBook"] = None
633+
614634
def _fetch_info(self):
615635
if self._already_fetched:
616636
return
@@ -667,6 +687,9 @@ def _format(k, v):
667687
return v2
668688

669689
self._info = {k: _format(k, v) for k, v in query1_info.items()}
690+
691+
# Sanitize priceToBook if currencies don't match
692+
self._sanitize_price_to_book()
670693

671694
def _fetch_complementary(self):
672695
if self._already_fetched_complementary:

0 commit comments

Comments
 (0)