Ejemplo n.º 1
0
def import_transaction(
    account: models.Account,
    fiat_record: pd.Series,
    token_record: pd.Series,
) -> Tuple[models.Transaction, bool]:
    raw_record = _to_raw_record((fiat_record, token_record))
    executed_at = _parse_utc_datetime(fiat_record["UTC_Time"])
    symbol = token_record["Coin"]
    fiat_currency = fiat_record["Coin"]
    raw_fiat_value = to_decimal(fiat_record["Change"])
    fiat_value = raw_fiat_value
    from_currency = models.currency_enum_from_string(fiat_currency)

    if fiat_currency != models.Currency(account.currency).label:
        to_currency = account.currency
        exchange_rate = prices.get_closest_exchange_rate(
            executed_at.date(), from_currency, to_currency
        )
        if exchange_rate is None:
            raise CurrencyMismatch(
                "Couldn't convert the fiat to account currency, missing exchange rate"
            )
        else:
            fiat_value *= exchange_rate.value
            raw_record += f" exchange rate: {exchange_rate.value}"
    if fiat_currency == "USD":
        fiat_value_usd = raw_fiat_value
    else:
        to_currency = models.Currency.USD
        exchange_rate = prices.get_closest_exchange_rate(
            executed_at.date(), from_currency, to_currency
        )
        if exchange_rate is None:
            raise CurrencyMismatch(
                "Couldn't convert the fiat to USD, missing exchange rate"
            )
        fiat_value_usd = raw_fiat_value * exchange_rate.value

    quantity = to_decimal(token_record["Change"])
    with decimal.localcontext() as c:
        c.prec = 10
        price = decimal.Decimal(-fiat_value_usd / quantity)

    return (
        *accounts.AccountRepository().add_transaction_crypto_asset(
            account,
            symbol,
            executed_at,
            quantity,
            price,
            fiat_value_usd,
            fiat_value,
            fiat_value,
        ),
        raw_record,
    )
Ejemplo n.º 2
0
 def compute_price(self, account, to_currency, arguments):
     price_in_account_currency = (
         arguments["value_in_account_currency"] / arguments["quantity"]
     )
     from_currency = account.currency
     if from_currency == to_currency:
         raise serializers.ValidationError(
             {
                 "price": [
                     "Price required if the asset is traded in the account currency."
                 ],
             }
         )
     date = arguments["executed_at"].date()
     exchange_rate = prices.get_closest_exchange_rate(
         date, from_currency, to_currency
     )
     if exchange_rate is None:
         raise serializers.ValidationError(
             {
                 "price": [
                     "Please provide the price, no suitable exchange rate available."
                 ],
             }
         )
     return price_in_account_currency * exchange_rate.value
Ejemplo n.º 3
0
    def delete_event(self, event: models.AccountEvent) -> None:
        account = event.account
        # This makes sense for all currently supported events,
        # but might not in the future.

        balance_change = event.amount
        if event.withheld_taxes:
            balance_change -= event.withheld_taxes

        if event.event_type == models.EventType.DIVIDEND and event.position:
            position_currency = event.position.asset.currency
            account_currency = account.currency
            if position_currency != account_currency:
                exchange_rate = prices.get_closest_exchange_rate(
                    date=event.executed_at.date(),
                    from_currency=position_currency,
                    to_currency=account_currency,
                )
                if exchange_rate is None:
                    raise ValueError(
                        f"Can't convert between currencies: "
                        f"{position_currency} and {account_currency}")
                balance_change *= exchange_rate.value
        account.balance -= balance_change

        account.save()
        transaction = event.transaction

        event.delete()

        if transaction:
            self.delete_transaction(transaction)
Ejemplo n.º 4
0
 def test_exchange_too_late(self):
     rate = prices.get_closest_exchange_rate(
         date=datetime.date.fromisoformat("2021-11-03"),
         from_currency=self.from_currency,
         to_currency=self.to_currency,
     )
     # Will use last date available.
     self.assertEqual(rate.date, datetime.date.fromisoformat("2021-05-01"))
     self.assertEqual(rate.from_currency, self.from_currency)
     self.assertEqual(rate.to_currency, self.to_currency)
     self.assertEqual(rate.value, decimal.Decimal("0.8"))
Ejemplo n.º 5
0
 def test_exchange_too_early(self):
     rate = prices.get_closest_exchange_rate(
         date=datetime.date.fromisoformat("2000-04-03"),
         from_currency=self.from_currency,
         to_currency=self.to_currency,
     )
     # Will use first date available.
     self.assertEqual(rate.date, datetime.date.fromisoformat("2020-03-02"))
     self.assertEqual(rate.from_currency, self.from_currency)
     self.assertEqual(rate.to_currency, self.to_currency)
     self.assertEqual(rate.value, decimal.Decimal("1.8"))
Ejemplo n.º 6
0
 def test_exchange_rate_sparse_range(self):
     rate = prices.get_closest_exchange_rate(
         date=datetime.date.fromisoformat("2020-04-03"),
         from_currency=self.from_currency,
         to_currency=self.to_currency,
     )
     # Should use first date before it.
     self.assertEqual(rate.date, datetime.date.fromisoformat("2020-04-01"))
     self.assertEqual(rate.from_currency, self.from_currency)
     self.assertEqual(rate.to_currency, self.to_currency)
     self.assertEqual(rate.value, decimal.Decimal("1.8"))
Ejemplo n.º 7
0
    def test_exchange_rate_present(self):

        rate = prices.get_closest_exchange_rate(
            date=datetime.date.fromisoformat("2021-04-03"),
            from_currency=self.from_currency,
            to_currency=self.to_currency,
        )
        self.assertEqual(rate.date, datetime.date.fromisoformat("2021-04-03"))
        self.assertEqual(rate.from_currency, self.from_currency)
        self.assertEqual(rate.to_currency, self.to_currency)
        self.assertEqual(rate.value, decimal.Decimal("1.1"))
Ejemplo n.º 8
0
def _convert_usd_to_account_currency(
    value: decimal.Decimal, account: models.Account, date: datetime.date
) -> decimal.Decimal:
    if account.currency == models.Currency.USD:
        return value

    from_currency = account.currency
    to_currency = models.Currency.USD
    exchange_rate = prices.get_closest_exchange_rate(date, from_currency, to_currency)
    if exchange_rate is None:
        raise CurrencyMismatch(
            "Couldn't convert USD to account currency, missing exchange rate"
        )
    return value * exchange_rate.value
Ejemplo n.º 9
0
    def add_event(
        self,
        account: models.Account,
        amount: decimal.Decimal,
        executed_at: datetime.datetime,
        event_type: models.EventType,
        position: Optional[models.Position] = None,
        withheld_taxes: Optional[decimal.Decimal] = None,
    ) -> Tuple[models.AccountEvent, bool]:

        if (event_type == models.EventType.DEPOSIT
                or event_type == models.EventType.DIVIDEND):
            assert amount > 0
        if event_type == models.EventType.WITHDRAWAL:
            assert amount < 0

        event, created = models.AccountEvent.objects.get_or_create(
            account=account,
            amount=amount,
            executed_at=executed_at,
            event_type=event_type,
            position=position,
            withheld_taxes=withheld_taxes or 0,
        )
        if created:
            balance_change = amount
            if withheld_taxes:
                balance_change -= withheld_taxes

            if event_type == models.EventType.DIVIDEND and position:
                position_currency = position.asset.currency
                account_currency = account.currency
                if position_currency != account_currency:
                    exchange_rate = prices.get_closest_exchange_rate(
                        date=executed_at.date(),
                        from_currency=position_currency,
                        to_currency=account_currency,
                    )
                    if exchange_rate is None:
                        raise ValueError(
                            f"Can't convert between currencies: "
                            f"{position_currency} and {account_currency}")
                    balance_change *= exchange_rate.value

            account.balance += balance_change
        account.save()
        return event, created
Ejemplo n.º 10
0
def import_fiat_transfers(account, records):
    account_repository = accounts.AccountRepository()
    successful_records = []

    for record in records.iloc:
        raw_record = record.to_csv()
        event_type = models.EventType.DEPOSIT
        if record["Operation"] == "Withdrawal":
            event_type = models.EventType.WITHDRAWAL
        executed_at = _parse_utc_datetime(record["UTC_Time"])

        fiat_currency = record["Coin"]
        fiat_value = to_decimal(record["Change"])

        if fiat_currency != models.Currency(account.currency).label:
            from_currency = models.currency_enum_from_string(fiat_currency)
            to_currency = account.currency
            exchange_rate = prices.get_closest_exchange_rate(
                executed_at.date(), from_currency, to_currency
            )
            if exchange_rate is None:
                raise CurrencyMismatch(
                    "Couldn't convert the fiat to account currency, missing exchange rate"
                )
            else:
                fiat_value *= exchange_rate.value
            raw_record += f", exchange rate: {exchange_rate.value}"

        event, created = account_repository.add_event(
            account,
            amount=fiat_value,
            executed_at=executed_at,
            event_type=event_type,
        )
        successful_records.append(
            {
                "record": raw_record,
                "event": event,
                "transaction": None,
                "created": created,
            }
        )
    return successful_records