Exemplo n.º 1
0
def test_kfee_price_in_accounting(accountant):
    """
    Test that KFEEs are correctly handled during accounting
    """
    history = [
        {
            'timestamp': 1609537953,
            'base_asset': 'ETH',
            'quote_asset': A_USDT.identifier,
            'trade_type': 'sell',
            'rate': 1000,
            'fee': '30',
            'fee_currency': A_KFEE.identifier,
            'amount': 0.02,
            'location': 'kraken',
        },
    ]
    ledger_actions_list = [
        LedgerAction(
            identifier=0,
            timestamp=Timestamp(1539713238),
            action_type=LedgerActionType.INCOME,
            location=Location.KRAKEN,
            amount=FVal(1),
            asset=A_ETH,
            rate=None,
            rate_asset=None,
            link=None,
            notes='',
        ),
        LedgerAction(
            identifier=0,
            timestamp=Timestamp(1539713238),
            action_type=LedgerActionType.INCOME,
            location=Location.KRAKEN,
            amount=FVal(1000),
            asset=A_KFEE,
            rate=None,
            rate_asset=None,
            link=None,
            notes='',
        ),
    ]
    report, _ = accounting_history_process(
        accountant,
        start_ts=1539713238,
        end_ts=1624395187,
        history_list=history,
        ledger_actions_list=ledger_actions_list,
    )
    warnings = accountant.msg_aggregator.consume_warnings()
    assert len(warnings) == 0
    errors = accountant.msg_aggregator.consume_errors()
    assert len(errors) == 0
    # The ledger actions income doesn't count for the income as the 1
    # year rule is applied. Only the sell is computed.
    # The expected PnL without the fee is 14.2277000
    # counting the fee is 14.2277000 - 30 * 0.01 * 0.82411
    assert FVal(report['total_profit_loss']) == FVal(13.980467)
Exemplo n.º 2
0
    def add_ledger_action(
        self,
        action: LedgerAction,
        profit_loss_in_profit_currency: FVal,
    ) -> None:
        if not self.create_csv:
            return

        self.ledger_actions_csv.append({
            'time':
            self.timestamp_to_date(action.timestamp),
            'type':
            str(action.action_type),
            'location':
            str(action.location),
            'asset':
            str(action.asset),
            'amount':
            str(action.amount),
            f'profit_loss_in_{self.profit_currency.symbol}':
            profit_loss_in_profit_currency,
        })

        paid_asset: Optional[Asset]
        received_asset: Optional[Asset]
        if action.is_profitable():
            paid_in_profit_currency = ZERO
            paid_in_asset = ZERO
            paid_asset = None
            received_asset = action.asset
            received_in_asset = action.amount
            received_in_profit_currency = profit_loss_in_profit_currency
        else:
            paid_in_profit_currency = profit_loss_in_profit_currency
            paid_in_asset = action.amount
            paid_asset = action.asset
            received_asset = None
            received_in_asset = AssetAmount(ZERO)
            received_in_profit_currency = ZERO

        self.add_to_allevents(
            event_type=EV_LEDGER_ACTION,
            location=action.location,
            paid_in_profit_currency=paid_in_profit_currency,
            paid_asset=paid_asset,
            paid_in_asset=paid_in_asset,
            received_asset=received_asset,
            received_in_asset=received_in_asset,
            taxable_received_in_profit_currency=received_in_profit_currency,
            total_received_in_profit_currency=received_in_profit_currency,
            timestamp=action.timestamp,
            link=action.link,
            notes=action.notes,
        )
Exemplo n.º 3
0
 def add_ledger_action(self, action: LedgerAction) -> int:
     """Adds a new ledger action to the DB and returns its identifier for success"""
     cursor = self.db.conn.cursor()
     query = """
     INSERT INTO ledger_actions(
         timestamp, type, location, amount, asset, rate, rate_asset, link, notes
     )
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);"""
     cursor.execute(query, action.serialize_for_db())
     identifier = cursor.lastrowid
     self.db.conn.commit()
     return identifier
Exemplo n.º 4
0
def _deserialize_transaction(grant_id: int, rawtx: Dict[str,
                                                        Any]) -> LedgerAction:
    """May raise:
    - DeserializationError
    - KeyError
    - UnknownAsset
    """
    timestamp = deserialize_timestamp_from_date(
        date=rawtx['timestamp'],
        formatstr='%Y-%m-%dT%H:%M:%S',
        location='Gitcoin API',
        skip_milliseconds=True,
    )
    asset = get_gitcoin_asset(symbol=rawtx['asset'],
                              token_address=rawtx['token_address'])
    raw_amount = deserialize_int_from_str(symbol=rawtx['amount'],
                                          location='gitcoin api')
    amount = asset_normalized_value(raw_amount, asset)
    if amount == ZERO:
        raise ZeroGitcoinAmount()

    # let's use gitcoin's calculated rate for now since they include it in the response
    usd_value = Price(
        ZERO) if rawtx['usd_value'] is None else deserialize_price(
            rawtx['usd_value'])  # noqa: E501
    rate = Price(ZERO) if usd_value == ZERO else Price(usd_value / amount)
    raw_txid = rawtx['tx_hash']
    tx_type, tx_id = process_gitcoin_txid(key='tx_hash', entry=rawtx)
    # until we figure out if we can use it https://github.com/gitcoinco/web/issues/9255#issuecomment-874537144  # noqa: E501
    clr_round = _calculate_clr_round(timestamp, rawtx)
    notes = f'Gitcoin grant {grant_id} event' if not clr_round else f'Gitcoin grant {grant_id} event in clr_round {clr_round}'  # noqa: E501
    return LedgerAction(
        identifier=1,  # whatever -- does not end up in the DB
        timestamp=timestamp,
        action_type=LedgerActionType.DONATION_RECEIVED,
        location=Location.GITCOIN,
        amount=AssetAmount(amount),
        asset=asset,
        rate=rate,
        rate_asset=A_USD,
        link=raw_txid,
        notes=notes,
        extra_data=GitcoinEventData(
            tx_id=tx_id,
            grant_id=grant_id,
            clr_round=clr_round,
            tx_type=tx_type,
        ),
    )
Exemplo n.º 5
0
def test_fees_in_received_asset(accountant):
    """
    Test the sell trade where the fee is nominated in the asset received. We had a bug
    where the PnL report said that there was no documented adquisition.
    """
    history = [
        {
            'timestamp': 1609537953,
            'base_asset': 'ETH',
            'quote_asset': A_USDT.identifier,
            'trade_type': 'sell',
            'rate': 1000,
            'fee': '0.10',
            'fee_currency': A_USDT.identifier,
            'amount': 0.02,
            'location': 'binance',
        },
    ]
    ledger_actions_list = [
        LedgerAction(
            identifier=0,
            timestamp=Timestamp(1539713238),
            action_type=LedgerActionType.INCOME,
            location=Location.BINANCE,
            amount=FVal(1),
            asset=A_ETH,
            rate=None,
            rate_asset=None,
            link=None,
            notes='',
        ),
    ]
    report, _ = accounting_history_process(
        accountant,
        start_ts=1539713238,
        end_ts=1624395187,
        history_list=history,
        ledger_actions_list=ledger_actions_list,
    )
    warnings = accountant.msg_aggregator.consume_warnings()
    assert len(warnings) == 0
    errors = accountant.msg_aggregator.consume_errors()
    assert len(errors) == 0
    assert accountant.events.cost_basis.get_calculated_asset_amount(
        A_USDT.identifier).is_close('19.90')  # noqa: E501
    # The ethereum income doesn't count for the income as the 1
    # year rule is applied. Only the sell is computed.
    assert FVal(report['total_profit_loss']) == FVal(14.13870)
Exemplo n.º 6
0
    def edit_ledger_action(self, action: LedgerAction) -> Optional[str]:
        """Edits a ledger action from the DB by identifier

        Returns None for success or an error message for error
        """
        error_msg = None
        cursor = self.db.conn.cursor()
        query = """
        UPDATE ledger_actions SET timestamp=?, type=?, location=?, amount=?,
        asset=?, rate=?, rate_asset=?, link=?, notes=? WHERE identifier=?"""
        db_action_tuple = action.serialize_for_db()
        cursor.execute(query, (*db_action_tuple, action.identifier))
        if cursor.rowcount != 1:
            error_msg = (
                f'Tried to edit ledger action with identifier {action.identifier} '
                f'but it was not found in the DB')
        self.db.conn.commit()
        return error_msg
Exemplo n.º 7
0
    def get_ledger_actions(
        self,
        from_ts: Optional[Timestamp],
        to_ts: Optional[Timestamp],
        location: Optional[Location],
    ) -> List[LedgerAction]:
        cursor = self.db.conn.cursor()
        query = ('SELECT identifier,'
                 '  timestamp,'
                 '  type,'
                 '  location,'
                 '  amount,'
                 '  asset,'
                 '  rate,'
                 '  rate_asset,'
                 '  link,'
                 '  notes FROM ledger_actions ')
        if location is not None:
            query += f'WHERE location="{location.serialize_for_db()}" '
        query, bindings = form_query_to_filter_timestamps(
            query, 'timestamp', from_ts, to_ts)
        results = cursor.execute(query, bindings)
        actions = []
        for result in results:
            try:
                action = LedgerAction.deserialize_from_db(result)
            except DeserializationError as e:
                self.msg_aggregator.add_error(
                    f'Error deserializing Ledger Action from the DB. Skipping it.'
                    f'Error was: {str(e)}', )
                continue
            except UnknownAsset as e:
                self.msg_aggregator.add_error(
                    f'Error deserializing Ledger Action from the DB. Skipping it. '
                    f'Unknown asset {e.asset_name} found', )
                continue
            actions.append(action)

        return actions