""" spot_btc.py — Precio de referencia del bitcoin en euros para un día dado. Sale de la serie empaquetada serie_btc_eur.csv (cierre diario desde 2011, ver herramientas/serie_btc_eur.py) y, para los días posteriores a la serie, de los precios que la propia cartera va guardando del activo. Es solo una REFERENCIA: sirve para valorar lo minado o lo recibido, proponer un importe cuando no lo tienes apuntado y avisar si una compra te salió muy cara o muy barata. El coste fiscal de una compra es lo que pagaste de verdad. """ from __future__ import annotations import csv import datetime as dt from functools import lru_cache from pathlib import Path SERIE = Path(__file__).resolve().parent / "serie_btc_eur.csv" MARGEN_DIAS = 10 # como mucho, un precio de hace 10 días para un día sin dato @lru_cache(maxsize=1) def _serie() -> tuple[list[str], list[float]]: fechas, valores = [], [] try: with SERIE.open(encoding="utf-8") as f: for fila in csv.DictReader(f): fechas.append(fila["fecha"]) valores.append(float(fila["cierre_eur"])) except (OSError, KeyError, ValueError): return [], [] return fechas, valores def ultimo_dia_serie() -> str | None: fechas, _ = _serie() return fechas[-1] if fechas else None def precio(fecha: str, otros: list[dict] | None = None) -> tuple[float | None, str | None]: """(precio en €, día del que es) o (None, None) si no hay nada cercano. otros: precios guardados en la cartera [{fecha, precio}], para los días que la serie empaquetada aún no cubre. """ candidatos: list[tuple[str, float]] = [] fechas, valores = _serie() if fechas: # búsqueda binaria del último día <= fecha lo, hi = 0, len(fechas) while lo < hi: medio = (lo + hi) // 2 if fechas[medio] <= fecha: lo = medio + 1 else: hi = medio if lo: candidatos.append((fechas[lo - 1], valores[lo - 1])) for r in otros or []: if r.get("precio") and r.get("fecha") and r["fecha"] <= fecha: candidatos.append((r["fecha"], float(r["precio"]))) if not candidatos: return None, None dia, valor = max(candidatos, key=lambda c: c[0]) if (dt.date.fromisoformat(fecha) - dt.date.fromisoformat(dia)).days > MARGEN_DIAS: return None, None return valor, dia