caudal/programa/cartera/spot_btc.py
2026-10-13 08:01:06 -05:00

69 lines
2.4 KiB
Python

"""
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