69 lines
2.4 KiB
Python
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
|