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

59 lines
2.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
prima.py — Cuánto se aleja del mercado lo que pagaste (o cobraste) por tu bitcoin.
En una compra entre particulares (P2P) se paga algo más que el precio de
mercado: esa diferencia es la prima. Aquí se compara el precio de cada compra
o venta de bitcoin apuntada con el cierre de ese día (spot_btc.py):
prima = importe / (cantidad × cierre del día) − 1
Sirve para dos cosas: ver de un vistazo lo que te costó cada compra frente al
mercado, y cazar erratas (un importe con un cero de más, una cantidad mal
copiada, una fecha equivocada). No cambia ninguna cifra de la cartera: el
coste de una compra es lo que pagaste de verdad, con su prima.
Se avisa de una operación cuando queda «lejos»: te salió más de un 15 % peor
que el mercado, o más de un 5 % MEJOR (comprar por debajo del mercado, o
vender por encima, es raro). Si la cifra es buena, se marca como revisada
(«prima_ok» en la operación) y deja de avisar.
Una compra «estimada» tiene por importe el propio cierre del día, así que su
prima es cero: de esas avisa «Para revisar», no esto.
"""
from __future__ import annotations
from . import spot_btc
EN_CONTRA = 0.15 # más de un 15 % peor que el mercado
A_FAVOR = 0.05 # más de un 5 % mejor que el mercado
TIPOS = ("Compra", "Venta")
def es_bitcoin(activo: dict) -> bool:
return activo.get("tipo") == "Cripto" and (activo.get("codigo") or "").lower() == "bitcoin"
def de_movimientos(activos: list[dict], movimientos: list[dict], precios: list[dict]) -> dict[str, dict]:
"""{id de operación: {ref, dia, prima, lejos}} de las compras y ventas de
bitcoin que tienen cantidad e importe y un cierre cercano con que comparar."""
bitcoins = {a["id"] for a in activos if es_bitcoin(a)}
guardados: dict[str, list[dict]] = {}
for p in precios:
if p.get("activo") in bitcoins:
guardados.setdefault(p["activo"], []).append(p)
salida = {}
for m in movimientos:
if m["activo"] not in bitcoins or m["tipo"] not in TIPOS:
continue
cantidad, importe = float(m.get("cantidad") or 0), float(m.get("importe") or 0)
if cantidad <= 0 or importe <= 0:
continue
ref, dia = spot_btc.precio(m["fecha"], guardados.get(m["activo"]))
if not ref:
continue
prima = importe / (cantidad * ref) - 1
contra = prima if m["tipo"] == "Compra" else -prima # > 0: te salió peor que el mercado
salida[m["id"]] = {"ref": ref, "dia": dia, "prima": round(prima, 4),
"lejos": contra > EN_CONTRA or contra < -A_FAVOR}
return salida