3idZddlZddlmZmZddlmZmZmZddlZ ddl m Z m Z dZ ejeZGd d Ze r Gd d e ZyGd d Zy#e$r(dZ ejeZej%dYawxYw)z Price Fetcher for Portfolio Manager. Fetches stock/ETF prices from yfinance with smart caching. Includes PyQt6 threading wrapper for GUI integration. N)datetime timedelta)DictOptionalList)QThread pyqtSignalTFz5PyQt6 not available, PriceFetcherThread will not workc peZdZdZdZd dededeefdZ d de edede eeffdZ d ded e defd Zy ) PriceFetcherz6Fetches stock prices from yfinance with smart caching.c||_y)zz Initialize price fetcher. Args: db_manager: DBManager instance for cache operations N) db_manager)selfr s D/mnt/ssd/data/python-lab/portfolio-manager/src/data/price_fetcher.py__init__zPriceFetcher.__init__s %ticker force_refreshreturnc |sV|jj|}|r9|j|dr&|\}}tj d|d|d|d|S tj d|dt j |}d } t|d r,t|jd r|jj}|1 |j}|jd xs|jd }|1 |jd}|js|djd}|G|dkDrB|jj!||tjd|d|t#|Stj%d|d|y #t$rYwxYw#t$rYwxYw#t$rYwxYw#t$r%} tj'd|d| Yd } ~ y d } ~ wwxYw)z Fetch price for a single ticker. Args: ticker: Stock ticker symbol (e.g., 'VWCE.MI') force_refresh: Bypass cache and fetch fresh price Returns: Current price or None if fetch failed ) max_age_hourszUsing cached price for z: z (cached at )zFetching price for z from yfinance...N fast_info last_price currentPriceregularMarketPrice1d)periodCloserzFetched price for zInvalid price for zFailed to fetch price for )r get_cached_priceis_cache_validloggerdebugyfTickerhasattrrr Exceptioninfogethistoryemptyilocupdate_price_cachefloatwarningerror) rrrcachedprice last_updatedstockr)histes r fetch_pricezPriceFetcher.fetch_price$s__55f=F$--fA-F&,#| 6vhb|T`Saabcd *  LL.vh6GH IIIf%EE 5+.75??L3Y!OO66E } ::D HH^4VAU8VE } ===5D:: $W 2 22 6 UQY2265A 05'BCU|#!3F82eWEF7  !!  LL5fXRsC D s0F; 8FF;0F6F;90F,)AF;2F; FF;FF; F)&F;(F))F;, F85F;7F88F;; G)G$$G)tickersci}|D]}|j||}||||<tjdt|dt|d|S)z Fetch prices for multiple tickers. Args: tickers: List of ticker symbols force_refresh: Bypass cache and fetch fresh prices Returns: Dictionary mapping ticker -> price rzFetched /z prices successfully)r8r#r)len)rr9rresultsrr3s rfetch_prices_batchzPriceFetcher.fetch_prices_batchdsg (F$$V=$IE "' (  hs7|nAc'l^;OPQrrc|jj|}|sy|\}}tj|z }|t |k}|st j d|d|d|S)z Check if cached price is still valid. Args: ticker: Ticker to check max_age_hours: Maximum age of cache in hours Returns: True if cache is valid, False otherwise F)hoursz Cache for z is stale (age: r)r r!rnowrr#r$)rrrr2r3r4ageis_valids rr"zPriceFetcher.is_cache_valid}sk11&9$|lln|+77 LL:fX-=cU!D ErNF)r)__name__ __module__ __qualname____doc__rstrboolrr/r8rrr?intr"rrr r sw@%>#>d>xPU>F$c c5j  2STrr czeZdZdZeeeZeeZee Z dde de e de ffd Zd dZd dZxZS) PriceFetcherThreada PyQt6 thread for fetching prices in background. Signals: progress(int, int): Current ticker index and total count finished(dict): Dictionary of ticker -> price mapping error(str): Error message price_fetcherr9rcZt|||_||_||_d|_y)z Initialize thread. Args: price_fetcher: PriceFetcher instance tickers: List of tickers to fetch force_refresh: Bypass cache TN)superrrPr9r _is_running)rrPr9r __class__s rrzPriceFetcherThread.__init__s/ G  !.D "DL!.D #D rc i}t|j}t|jD]v\}}|jstj dnP|j j|dz||jj||j}|r|||<x|jrA|jj|tj dt|d|dyy#t$rJ}dt|}tj|d |jj|Yd}~yd}~wwxYw) z(Run price fetching in background thread.z Price fetching cancelled by userr;Nz!Price fetching thread completed: r<z priceszPrice fetching failed: T)exc_info)r=r9 enumeraterSr#r)progressemitrPr8rfinishedr(rJr1)rr>totalirr3r7 error_msgs rrunzPriceFetcherThread.runs' +DLL)!*4<QRSXRYY` ab$ +5c!fX>  Y 6  ** +s BC5 AC55 E>AEEc<d|_tjdy)zStop the thread gracefully.Fz Stopping price fetcher thread...N)rSr#r))rs rstopzPriceFetcherThread.stops$D  KK: ;rrE)rN)rFrGrHrIr rLrYdictr[rJr1r rrKrr_ra __classcell__)rTs@rrOrOsR c3'd#3 $, $c $[_ $ +: rps(''L0N   8 ${{|=