-
-
Notifications
You must be signed in to change notification settings - Fork 631
Implement Yahoo Finance API for Stock/ETF Price Oracle #9227 #9587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Implement Yahoo Finance API for Stock/ETF Price Oracle #9227 #9587
Conversation
…ustom assets / manual balances
Have a bunch of work stuff piling on but will be free around Thursday/Friday if anyone would like to review and provide some feedback so I can take a look and do some cleanup there then! |
Hey @Kernlog thanks a lot for this work! This is an over 3k PR. We will try to take a look but it may take some time and will most probably requires lots of rounds of back and forth between us to get merged. Just mentioning to set the expectations straight. And also responding to make sure you don't think we forgot. |
Also in general don't merge develop on the branch but rebase the branch on develop. Though most likely we would end up squashing in the end. |
Sounds good thanks for the note on rebasing instead @LefterisJP ! Totally understand aswell 3k PR is a good chunk to go through especially when it's not too important atm. Just wanted to mention the timings I have available this week incase someone wanted that back and fourth so they knew if I would be able to work on it or not 😆 . Ty for the response appreciate it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I gave it a quick look and I think it needs a lot of changes and should also be split in multiple PRs. The major things I noticed are:
Asset type
You seem to dynamically try to figure out the yahoo_finance
thing each time from the asset by looking at the asset's notes. I think this is a hacky way to make it work with our current codebase, but the point is to do it properly.
A proper way would be to add a new asset type for stocks and ETFs. It would be very much like the other assets but would just need to be an asset or an ETF whose price can be determined automatically.
That asset would need to have as symbol the unique ticker of the ETF or stock. Is it the ISIN symbol that is checked by yahoofinance? Then at addition time the api would check it exists and has a price in yahoofinance and if yes would accept and create the asset.
This alone can be one PR.
Manual balances and price queries.
At that point you won't need any changes in the manual balances. What you did in this PR is extremely hacky and would not go in the codebase. To check the attributes dynamically and store it like this.
I understand from our earlier discussions that the reason you did it like this is that sometimes yahoo finance borks. And then may return no price. At that point you want to re-use the last valid price we have for the asset.
There is no need to have it in this hacky way. All this special logic you added in the inquirer needs to go away. It's way too hacky and unmaintainable.
What needs to happen is a more general adjustment for price queries.
That also should be its own separate PR
The query prices endpoitn should try to see if there is a cached price in the DB for the asset within the last X
hours/days and return that. For each price we should probably also return its age in the prices response. So perhaps an int depending on how old the price (in seconds) is. 0
for current.
) | ||
price_value = entry.amount | ||
|
||
from rotkehlchen.types import Price |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
never do imports in the middle of the code. Always at top of the file
@@ -308,6 +314,11 @@ class Inquirer: | |||
special_tokens: set[str] | |||
weth: EvmToken | |||
usd: FiatAsset | |||
|
|||
# Persistent storage for custom asset prices to avoid them being wiped out | |||
_custom_asset_prices: dict[str, Price] = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is no need for all that. The cache is the DB itself. You just have to properly use it.
manualcurrent: Optional['ManualCurrentOracle'] = None, | ||
msg_aggregator: Optional['MessagesAggregator'] = None, | ||
) -> 'Inquirer': | ||
"""Create or get the already existing singleton. | ||
|
||
In case of multi processing initialization avoid using `clear` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We never add code in production that is useful only for tests
asset=from_asset.identifier, | ||
error=str(e), | ||
) | ||
except Exception: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should never do naked except catches in python code. Always know what exceptions your code raises.
for from_asset in from_assets: | ||
# Special handling for custom assets (stock/ETF) | ||
try: | ||
if hasattr(from_asset, 'get_asset_type') and from_asset.get_asset_type() == AssetType.CUSTOM_ASSET: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to recognize what type it is we have resolve_asset_to_class
. No need to be doing hacky hasattr
everywhere
@@ -692,7 +781,60 @@ def _preprocess_assets_to_query( | |||
and a list of assets that still need their prices queried. | |||
""" | |||
found_prices, replaced_assets, unpriced_assets = {}, {}, [] | |||
|
|||
# Special handling for custom assets to ensure we use cached prices |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all this needs to go away. As i wrote in the review comment.
log.error(f"Background stock/ETF refresh process failed: {e}") | ||
|
||
# Start the background thread | ||
thread = threading.Thread(target=refresh_all_stocks_bg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no no no don't do that. Do not use threading in rotki. Anyway as i said all of these changes need to go away
Yahoo Finance API Integration
This PR implements a Yahoo Finance API integration to provide price data for stocks and ETFs, addressing issue #9227. This integration enhances Rotki's ability to track and display prices for traditional financial assets alongside crypto assets.
Implementation Overview
The Yahoo Finance integration is implemented as a new price oracle that follows the existing oracle interfaces in Rotki. It provides both current and historical price data for stocks and ETFs. The implementation:
yfinance
Python library for reliable access to Yahoo Finance dataMajor Files Updated
New Files
rotkehlchen/externalapis/yahoofinance.py
YahooFinance
class that inherits fromExternalServiceWithApiKeyOptionalDB
,HistoricalPriceOracleWithCoinListInterface
, andPenalizablePriceOracleMixin
rotkehlchen/tests/external_apis/test_yahoofinance.py
rotkehlchen/tests/external_apis/test_yahoofinance_integration.py
Modified Files
rotkehlchen/assets/asset.py
yahoo_finance
field toAssetWithOracles
classto_yahoo_finance()
method to extract ticker symbolsrotkehlchen/inquirer.py
_yahoofinance
instance to theInquirer
class_stock_etf_prices
dictionary to track stock/ETF prices with timestamps_should_refresh_stock_price
method to determine when to refresh pricesforce_refresh_stock_etf_prices
method to manually refresh all stock/ETF pricesrotkehlchen/balances/manual.py
add_manually_tracked_balances
to handle custom assets of type stock/etfrotkehlchen/tests/unit/test_manual_balances.py
rotkehlchen/tests/api/test_custom_assets.py
rotkehlchen/oracles/structures.py
YAHOOFINANCE
toCurrentPriceOracle
enumrotkehlchen/types.py
YAHOO_FINANCE
toExternalService
enumrotkehlchen/rotkehlchen.py
rotkehlchen/history/price.py
frontend/app/src/locales/en.json
frontend/app/src/components/helper/PrioritizedListEntry.vue
Integration Details
Yahoo Finance API Integration
The Yahoo Finance API integration is designed to work seamlessly with Rotki's existing price oracle system. The implementation:
Follows Oracle Interface: Implements the required interfaces (
HistoricalPriceOracleWithCoinListInterface
,PenalizablePriceOracleMixin
) to ensure compatibility with the existing system.Robust Error Handling: Implements comprehensive error handling with detailed logging to help diagnose issues.
Rate Limiting Protection: Uses exponential backoff for retry logic when rate limiting is encountered.
Caching: Implements an in-memory cache to reduce API calls and improve performance.
Custom Asset Integration
The integration with custom assets is a key feature of this implementation:
Ticker Extraction: Implements multiple methods to extract ticker symbols from custom assets:
yahoo_finance
attributePrice Storage: Enhances the manual balance system to store and retrieve prices for custom stock/ETF assets.
Price Refresh Logic: Implements logic to determine when to refresh stock/ETF prices based on time elapsed.
Inquirer Integration
The integration with the Inquirer system is designed to be non-intrusive:
Special Handling: Adds special handling for custom assets in the price inquirer to ensure Yahoo Finance is used for stock/ETF assets.
Price Caching: Implements a dedicated caching system for stock/ETF prices to reduce API calls.
Background Refresh: Adds background refresh capability for stock/ETF prices to ensure up-to-date data.
Manual Balances Integration
The integration with manual balances enhances the user experience for tracking stock/ETF assets:
Price Extraction: Extracts prices from manual balance entries for custom stock/ETF assets.
Price Storage: Stores prices in the custom asset price system for future use.
Price Updates: Updates prices when manual balances are edited.
Testing
The implementation includes comprehensive tests:
Unit Tests: Tests individual components of the Yahoo Finance implementation:
Integration Tests: Tests the integration with the Rotki system:
Rate Limiting Protection: Tests are designed to avoid excessive API calls:
Mock Responses: Uses mock responses for tests that don't need actual API calls.
Future Improvements
Potential future enhancements to the Yahoo Finance integration:
Expanded Asset Support: Add support for more asset types beyond stocks and ETFs.
Symbol Mapping Database: Implement a more comprehensive symbol mapping database for better ticker resolution.
Additional Data Points: Add support for dividends, corporate actions, and other financial data.
UI Enhancements: Improve the UI for displaying stock/ETF information.
Batch Queries: Implement batch queries to reduce API calls further.
Advanced Caching: Implement more sophisticated caching strategies with persistence.
Historical Data: Enhance historical data retrieval for better performance analysis.
Related Issues
Closes #9227