06/01 Optimize porfolio page
This commit is contained in:
@@ -111,13 +111,81 @@ TIMEFRAME_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def _extract_day_change_from_meta(meta):
|
||||
"""
|
||||
Extract open-to-current day change from a Yahoo Finance v8 meta block.
|
||||
Returns (open_price, current_price, day_change, day_change_pct) or (None,)*4.
|
||||
|
||||
Yahoo Finance always includes regularMarketOpen (session open) and
|
||||
regularMarketPrice (latest trade), so this gives the true intraday move
|
||||
rather than the previous-close-to-latest approximation.
|
||||
"""
|
||||
try:
|
||||
open_price = float(meta['regularMarketOpen'])
|
||||
current_price = float(meta['regularMarketPrice'])
|
||||
day_change = round(current_price - open_price, 4)
|
||||
day_change_pct = round(day_change / open_price * 100, 2) if open_price != 0 else 0
|
||||
return open_price, current_price, day_change, day_change_pct
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return None, None, None, None
|
||||
|
||||
|
||||
def fetch_day_change(ticker):
|
||||
"""
|
||||
Lightweight call: fetch today's open price and current price only.
|
||||
Uses meta.regularMarketOpen / meta.regularMarketPrice from Yahoo Finance.
|
||||
|
||||
Returns dict: {ticker, open, current, day_change, day_change_pct}
|
||||
or None on failure.
|
||||
"""
|
||||
if not ticker:
|
||||
return None
|
||||
ticker = ticker.upper().strip()
|
||||
|
||||
for subdomain in ('query1', 'query2'):
|
||||
url = (
|
||||
f'https://{subdomain}.finance.yahoo.com/v8/finance/chart/{ticker}'
|
||||
f'?range=1d&interval=1d&includePrePost=false'
|
||||
)
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=10)
|
||||
if resp.status_code != 200:
|
||||
continue
|
||||
result = resp.json().get('chart', {}).get('result')
|
||||
if not result:
|
||||
return None
|
||||
|
||||
meta = result[0].get('meta', {})
|
||||
open_p, curr, chg, chg_pct = _extract_day_change_from_meta(meta)
|
||||
if open_p is None:
|
||||
return None
|
||||
|
||||
log.info('[investment] %s day change: open=%.4f current=%.4f chg=%.4f (%.2f%%)',
|
||||
ticker, open_p, curr, chg, chg_pct)
|
||||
return {
|
||||
'ticker': ticker,
|
||||
'open': open_p,
|
||||
'current': curr,
|
||||
'day_change': chg,
|
||||
'day_change_pct': chg_pct,
|
||||
}
|
||||
except Exception as exc:
|
||||
log.warning('[investment] %s day-change fetch failed (%s): %s', ticker, subdomain, exc)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def fetch_price_history(ticker, timeframe='1M'):
|
||||
"""
|
||||
Fetch historical closing prices for a ticker via Yahoo Finance v8 API.
|
||||
timeframe: '1W' | '1M' | '3M' | '6M' | '1Y'
|
||||
|
||||
day_change / day_change_pct reflect the true intraday move
|
||||
(regularMarketOpen → regularMarketPrice) from the response meta,
|
||||
not the close-to-close approximation.
|
||||
|
||||
Returns dict:
|
||||
ticker, current, prev_close, day_change, day_change_pct,
|
||||
ticker, current, open_price, day_change, day_change_pct,
|
||||
period_change, period_change_pct, dates, closes, timeframe
|
||||
Returns None on failure.
|
||||
"""
|
||||
@@ -141,6 +209,7 @@ def fetch_price_history(ticker, timeframe='1M'):
|
||||
if not result:
|
||||
return None
|
||||
|
||||
meta = result[0].get('meta', {})
|
||||
timestamps = result[0].get('timestamp', [])
|
||||
closes_raw = result[0]['indicators']['quote'][0].get('close', [])
|
||||
pairs = [(t, c) for t, c in zip(timestamps, closes_raw) if c is not None]
|
||||
@@ -150,18 +219,26 @@ def fetch_price_history(ticker, timeframe='1M'):
|
||||
dates = [datetime.utcfromtimestamp(t).strftime('%Y-%m-%d') for t, _ in pairs]
|
||||
closes = [round(float(c), 4) for _, c in pairs]
|
||||
|
||||
current = closes[-1]
|
||||
prev = closes[-2] if len(closes) > 1 else current
|
||||
day_change = round(current - prev, 4)
|
||||
day_change_pct = round(day_change / prev * 100, 2) if prev != 0 else 0
|
||||
# Use meta for accurate day change (open → current), fall back to
|
||||
# close-to-close only when meta fields are absent.
|
||||
open_p, curr, day_change, day_change_pct = _extract_day_change_from_meta(meta)
|
||||
if open_p is None:
|
||||
curr = closes[-1]
|
||||
prev = closes[-2] if len(closes) > 1 else curr
|
||||
day_change = round(curr - prev, 4)
|
||||
day_change_pct = round(day_change / prev * 100, 2) if prev != 0 else 0
|
||||
open_p = prev
|
||||
|
||||
current = curr if curr is not None else closes[-1]
|
||||
period_change = round(current - closes[0], 4)
|
||||
period_change_pct = round(period_change / closes[0] * 100, 2) if closes[0] != 0 else 0
|
||||
|
||||
log.info('[investment] %s history: %d points (%s)', ticker, len(closes), timeframe)
|
||||
log.info('[investment] %s history: %d points (%s) day_chg=%.2f%%',
|
||||
ticker, len(closes), timeframe, day_change_pct)
|
||||
return {
|
||||
'ticker': ticker,
|
||||
'current': current,
|
||||
'prev_close': prev,
|
||||
'open_price': open_p,
|
||||
'day_change': day_change,
|
||||
'day_change_pct': day_change_pct,
|
||||
'period_change': period_change,
|
||||
|
||||
Reference in New Issue
Block a user