mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
97 lines
3.4 KiB
Python
97 lines
3.4 KiB
Python
"""
|
|
Background tasks for the invest app.
|
|
"""
|
|
import logging
|
|
from decimal import Decimal
|
|
from datetime import datetime
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def snapshot_all_portfolios():
|
|
"""
|
|
Capture a PortfolioSnapshot for every portfolio.
|
|
Scheduled every Saturday at 08:00. Also callable manually for backfill.
|
|
"""
|
|
from django.utils import timezone
|
|
from .models import Portfolio, PortfolioSnapshot
|
|
from .services import get_portfolio_value
|
|
|
|
now = timezone.now()
|
|
today = now.date()
|
|
logger.info("invest: starting portfolio snapshot at %s", now)
|
|
|
|
count = 0
|
|
for portfolio in Portfolio.objects.prefetch_related('stocks').all():
|
|
try:
|
|
data = get_portfolio_value(portfolio)
|
|
total_value = Decimal(str(data['total_value']))
|
|
|
|
# One snapshot per portfolio per day — overwrite if run twice
|
|
PortfolioSnapshot.objects.filter(
|
|
portfolio=portfolio,
|
|
captured_at__date=today,
|
|
).delete()
|
|
|
|
PortfolioSnapshot.objects.create(
|
|
portfolio=portfolio,
|
|
captured_at=now,
|
|
total_value=total_value,
|
|
)
|
|
count += 1
|
|
logger.info("invest: snapshot %s = $%.2f", portfolio.name, total_value)
|
|
except Exception as exc:
|
|
logger.error("invest: snapshot failed for %s: %s", portfolio.name, exc, exc_info=True)
|
|
|
|
logger.info("invest: snapshot complete — %d portfolios", count)
|
|
# Also refresh benchmark prices so the chart has up-to-date SPY/QQQ data
|
|
refresh_benchmark_prices()
|
|
|
|
|
|
def refresh_benchmark_prices(tickers=None):
|
|
"""
|
|
Fetch and cache daily closing prices for benchmark tickers (SPY, QQQ).
|
|
Skips the fetch if data is already fresh (latest date within last 7 days).
|
|
Called weekly alongside snapshot_all_portfolios.
|
|
"""
|
|
import datetime as dt
|
|
import yfinance as yf
|
|
from .models import BenchmarkPrice
|
|
|
|
if tickers is None:
|
|
tickers = ['SPY', 'QQQ']
|
|
|
|
today = dt.date.today()
|
|
start = (today - dt.timedelta(days=365 * 2)).isoformat() # 2 years of history
|
|
end = (today + dt.timedelta(days=1)).isoformat()
|
|
|
|
for ticker in tickers:
|
|
latest = (
|
|
BenchmarkPrice.objects.filter(ticker=ticker)
|
|
.order_by('-date')
|
|
.values_list('date', flat=True)
|
|
.first()
|
|
)
|
|
if latest and (today - latest).days <= 7:
|
|
logger.info("invest: benchmark %s is fresh (latest=%s), skipping", ticker, latest)
|
|
continue
|
|
|
|
try:
|
|
hist = yf.Ticker(ticker).history(start=start, end=end)
|
|
if hist.empty:
|
|
logger.warning("invest: no data for benchmark %s", ticker)
|
|
continue
|
|
rows = []
|
|
for d, v in hist['Close'].items():
|
|
date_val = d.date() if hasattr(d, 'date') else d
|
|
rows.append(BenchmarkPrice(ticker=ticker, date=date_val, close=round(float(v), 4)))
|
|
BenchmarkPrice.objects.bulk_create(
|
|
rows,
|
|
update_conflicts=True,
|
|
unique_fields=['ticker', 'date'],
|
|
update_fields=['close'],
|
|
)
|
|
logger.info("invest: refreshed %d prices for %s (latest=%s)", len(rows), ticker, today)
|
|
except Exception as exc:
|
|
logger.error("invest: benchmark refresh failed for %s: %s", ticker, exc, exc_info=True)
|