| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- from decimal import Decimal
- from django.db import models
- class Divisa(models.Model):
- """Moneda de cotización y su tipo de cambio a euros."""
- codigo = models.CharField(
- max_length=3, unique=True,
- help_text='Código ISO 4217; p. ej. EUR, USD, CHF',
- )
- nombre = models.CharField(max_length=50)
- cambio_eur = models.DecimalField(
- max_digits=12, decimal_places=6, default=Decimal('1'),
- help_text='Euros que vale 1 unidad de esta divisa',
- )
- actualizado_en = models.DateTimeField(null=True, blank=True, editable=False)
- class Meta:
- verbose_name = 'divisa'
- verbose_name_plural = 'divisas'
- ordering = ['codigo']
- def __str__(self):
- return self.codigo
- class Valor(models.Model):
- """Un valor bursátil (acción, ETF...) de la cartera."""
- ticker = models.CharField(
- max_length=20, unique=True,
- help_text='Ticker según Yahoo Finance; en Bolsa española con sufijo .MC',
- )
- nombre = models.CharField(max_length=100)
- mercado = models.CharField(max_length=30, blank=True, help_text='p. ej. SIBE, NASDAQ')
- isin = models.CharField(max_length=12, blank=True)
- divisa = models.ForeignKey(
- Divisa, on_delete=models.PROTECT, related_name='valores',
- help_text='Moneda en la que cotiza y se opera este valor',
- )
- precio_actual = models.DecimalField(
- max_digits=12, decimal_places=4, null=True, blank=True,
- editable=False,
- )
- actualizado_en = models.DateTimeField(null=True, blank=True, editable=False)
- class Meta:
- verbose_name = 'valor'
- verbose_name_plural = 'valores'
- ordering = ['ticker']
- def __str__(self):
- return f'{self.ticker} - {self.nombre}'
- class Operacion(models.Model):
- COMPRA = 'C'
- VENTA = 'V'
- TIPOS = [
- (COMPRA, 'Compra'),
- (VENTA, 'Venta'),
- ]
- valor = models.ForeignKey(Valor, on_delete=models.CASCADE, related_name='operaciones')
- tipo = models.CharField('tipo de operación', max_length=1, choices=TIPOS)
- fecha = models.DateField()
- cantidad = models.DecimalField(max_digits=14, decimal_places=4)
- precio = models.DecimalField(
- max_digits=12, decimal_places=4,
- help_text='Precio unitario por acción',
- )
- comisiones = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0'))
- cambio_eur = models.DecimalField(
- max_digits=12, decimal_places=6, null=True, blank=True,
- editable=False,
- help_text='Cambio a euros congelado al registrar la operación',
- )
- class Meta:
- ordering = ['-fecha', '-pk']
- verbose_name = 'operación'
- def __str__(self):
- return f'{self.get_tipo_display()} {self.cantidad} {self.valor.ticker} ({self.fecha})'
- def save(self, *args, **kwargs):
- # congela el tipo de cambio vigente si aún no tiene uno:
- # así las posiciones cerradas no se alteran al actualizar cotizaciones
- if self.cambio_eur is None and self.valor_id:
- self.cambio_eur = self.valor.divisa.cambio_eur
- super().save(*args, **kwargs)
- @property
- def importe(self):
- """Importe total de la operación, comisiones incluidas."""
- return (self.cantidad * self.precio).quantize(Decimal('0.01')) + self.comisiones
- class Dividendo(models.Model):
- valor = models.ForeignKey(Valor, on_delete=models.CASCADE, related_name='dividendos')
- fecha = models.DateField()
- bruto = models.DecimalField(max_digits=10, decimal_places=2)
- retencion = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0'))
- cambio_eur = models.DecimalField(
- max_digits=12, decimal_places=6, null=True, blank=True,
- editable=False,
- help_text='Cambio a euros congelado al registrar el dividendo',
- )
- class Meta:
- ordering = ['-fecha', '-pk']
- def __str__(self):
- return f'Dividendo {self.bruto} € {self.valor.ticker} ({self.fecha})'
- def save(self, *args, **kwargs):
- if self.cambio_eur is None and self.valor_id:
- self.cambio_eur = self.valor.divisa.cambio_eur
- super().save(*args, **kwargs)
- @property
- def neto(self):
- return self.bruto - self.retencion
|