Exemple #1
0
def test_taxable_ledger_action_setting(accountant, expected_pnl):
    """Test that ledger actions respect the taxable setting"""
    ledger_actions_list = [
        LedgerAction(
            identifier=1,
            timestamp=1476979735,
            action_type=LedgerActionType.INCOME,
            location=Location.EXTERNAL,
            amount=FVal(1),  # 578.505 EUR from mocked prices
            asset=A_BTC,
            link='',
            notes='',
        ),
        LedgerAction(
            identifier=2,
            timestamp=1491062063,
            action_type=LedgerActionType.AIRDROP,
            location=Location.EXTERNAL,
            amount=FVal(10),  # 478.65 EUR from mocked prices
            asset=A_ETH,
            link='',
            notes='',
        ),
        LedgerAction(
            identifier=3,
            timestamp=1501062063,
            action_type=LedgerActionType.LOSS,
            location=Location.BLOCKCHAIN,
            amount=FVal(2),  # 350.88 EUR from mocked prices
            asset=A_ETH,
            link='',
            notes='',
        ),
    ]
    result = accounting_history_process(
        accountant,
        1436979735,
        1519693374,
        history_list=[],
        ledger_actions_list=ledger_actions_list,
    )
    assert FVal(
        result['overview']['total_taxable_profit_loss']).is_close(expected_pnl)
Exemple #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':
            action.asset.identifier,
            'amount':
            str(action.amount),
            f'profit_loss_in_{self.profit_currency.identifier}':
            profit_loss_in_profit_currency,
        })

        paid_asset: Union[EmptyStr, Asset]
        received_asset: Union[EmptyStr, Asset]
        if action.is_profitable():
            paid_in_profit_currency = ZERO
            paid_in_asset = ZERO
            paid_asset = S_EMPTYSTR
            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 = S_EMPTYSTR
            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,
        )
Exemple #3
0
def test_ledger_action_can_be_edited(database,
                                     function_scope_messages_aggregator):
    db = DBLedgerActions(database, function_scope_messages_aggregator)

    query = 'SELECT * FROM ledger_actions WHERE identifier=?'
    cursor = database.conn.cursor()

    # Add the entry that we want to edit
    identifier = db.add_ledger_action(
        timestamp=1,
        action_type=LedgerActionType.INCOME,
        location=Location.EXTERNAL,
        amount=FVal(1),
        asset=A_ETH,
        link='',
        notes='',
    )

    # Data for the new entry
    new_entry = LedgerAction(
        identifier=identifier,
        timestamp=2,
        action_type=LedgerActionType.GIFT,
        location=Location.EXTERNAL,
        amount=FVal(3),
        asset=A_ETH,
        link='',
        notes='updated',
    )

    assert db.edit_ledger_action(new_entry) is None

    # Check that changes have been committed
    cursor.execute(query, (identifier, ))
    updated_entry = LedgerAction(*cursor.fetchone())
    assert updated_entry.timestamp == new_entry.timestamp
    assert str(updated_entry.amount) == str(new_entry.amount)
    assert updated_entry.action_type == new_entry.action_type.serialize_for_db(
    )
Exemple #4
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,'
            '  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(
                    identifier=result[0],
                    timestamp=deserialize_timestamp(result[1]),
                    action_type=deserialize_ledger_action_type_from_db(result[2]),
                    location=deserialize_location_from_db(result[3]),
                    amount=deserialize_asset_amount(result[4]),
                    asset=Asset(result[5]),
                    link=result[6],
                    notes=result[7],
                )
            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
Exemple #5
0
    def add_ledger_action(self, action: LedgerAction) -> None:
        # should never happen, should be stopped at the main loop
        assert action.timestamp <= self.query_end_ts, (
            'Ledger action time > query_end_ts found in processing')
        rate = self.get_rate_in_profit_currency(action.asset, action.timestamp)
        profit_loss = action.amount * rate

        account_for_action = (action.timestamp > self.query_start_ts
                              and action.action_type
                              in self.taxable_ledger_actions)
        log.debug(
            'Processing LedgerAction',
            sensitive_log=True,
            action=action,
            account_for_action=account_for_action,
        )
        if account_for_action is False:
            profit_loss = ZERO

        if action.is_profitable():
            self.ledger_actions_profit_loss += profit_loss
            self.cost_basis.obtain_asset(
                location=action.location,
                timestamp=action.timestamp,
                description=f'{str(action.action_type)}',
                asset=action.asset,
                amount=action.amount,
                rate=rate,
                fee_in_profit_currency=ZERO,
            )
        else:
            self.ledger_actions_profit_loss -= profit_loss

            result = self.cost_basis.reduce_asset_amount(
                asset=action.asset,
                amount=action.amount,
            )
            if not result:
                log.critical(
                    f'No documented buy found for {action.asset} before '
                    f'{self.csv_exporter.timestamp_to_date(action.timestamp)}',
                )

        if action.timestamp > self.query_start_ts:
            self.csv_exporter.add_ledger_action(
                action=action,
                profit_loss_in_profit_currency=profit_loss,
            )
Exemple #6
0
    def add_ledger_action(self, action: LedgerAction) -> None:
        log.debug(
            'Accounting for LedgerAction',
            sensitive_log=True,
            action=action,
        )
        # should never happen, should be stopped at the main loop
        assert action.timestamp <= self.query_end_ts, (
            'Ledger action time > query_end_ts found in processing')
        rate = self.get_rate_in_profit_currency(action.asset, action.timestamp)
        profit_loss = action.amount * rate

        if action.asset not in self.events:
            self.events[action.asset] = Events([], [])
        if action.is_profitable():
            if action.timestamp > self.query_start_ts:
                self.ledger_actions_profit_loss += profit_loss
            self.events[action.asset].buys.append(
                BuyEvent(
                    amount=action.amount,
                    timestamp=action.timestamp,
                    rate=rate,
                    fee_rate=ZERO,
                ), )
        else:
            if action.timestamp > self.query_start_ts:
                self.ledger_actions_profit_loss -= profit_loss
            result = self.reduce_asset_amount(
                asset=action.asset,
                amount=action.amount,
            )
            if not result:
                log.critical(
                    f'No documented buy found for {action.asset} before '
                    f'{self.csv_exporter.timestamp_to_date(action.timestamp)}',
                )

        if action.timestamp > self.query_start_ts:
            self.csv_exporter.add_ledger_action(
                action=action,
                profit_loss_in_profit_currency=profit_loss,
            )
Exemple #7
0
def test_ledger_actions_accounting(accountant):
    """Test for accounting for ledger actions

    Makes sure that Ledger actions are processed in accounting, range is respected
    and that they contribute to the "bought" amount per asset
    """
    ledger_actions_history = [
        LedgerAction(  # before range - read only for amount not profit
            identifier=1,
            timestamp=1435979735,  # 0.1 EUR per ETH
            action_type=LedgerActionType.INCOME,
            location=Location.EXTERNAL,
            asset=A_ETH,
            amount=AssetAmount(FVal(1)),
            link='',
            notes='',
        ),
        LedgerAction(
            identifier=2,
            timestamp=1437279735,  # 250 EUR per BTC
            action_type=LedgerActionType.INCOME,
            location=Location.BLOCKCHAIN,
            asset=A_BTC,
            amount=AssetAmount(FVal(1)),
            link='',
            notes='',
        ),
        LedgerAction(
            identifier=3,
            timestamp=1447279735,  # 0.4 EUR per XMR
            action_type=LedgerActionType.DIVIDENDS_INCOME,
            location=Location.KRAKEN,
            asset=A_XMR,
            amount=AssetAmount(FVal(10)),
            link='',
            notes='',
        ),
        LedgerAction(
            identifier=4,
            timestamp=1457279735,  # 1 EUR per ETH
            action_type=LedgerActionType.EXPENSE,
            location=Location.EXTERNAL,
            asset=A_ETH,
            amount=AssetAmount(FVal('0.1')),
            link='',
            notes='',
        ),
        LedgerAction(
            identifier=5,
            timestamp=1467279735,  # 420 EUR per BTC
            action_type=LedgerActionType.LOSS,
            location=Location.EXTERNAL,
            asset=A_BTC,
            amount=AssetAmount(FVal('0.1')),
            link='',
            notes='',
        ),
        LedgerAction(  # after range and should be completely ignored
            identifier=6,
            timestamp=1529693374,
            action_type=LedgerActionType.EXPENSE,
            location=Location.EXTERNAL,
            asset=A_ETH,
            amount=AssetAmount(FVal('0.5')),
            link='',
            notes='',
        )
    ]

    result = accounting_history_process(
        accountant=accountant,
        start_ts=1436979735,
        end_ts=1519693374,
        history_list=[],
        ledger_actions_list=ledger_actions_history,
    )
    assert accountant.events.cost_basis.get_calculated_asset_amount(
        A_BTC).is_close('0.9')
    assert accountant.events.cost_basis.get_calculated_asset_amount(
        A_ETH).is_close('0.9')
    assert accountant.events.cost_basis.get_calculated_asset_amount(
        A_XMR).is_close('10')
    # 250 * 1 + 0.4 * 10 - 1 * 0.1  - 420 * 0.1 = 211.9
    assert FVal(
        result['overview']['ledger_actions_profit_loss']).is_close('211.9')
    assert FVal(result['overview']['total_profit_loss']).is_close('211.9')
    assert FVal(
        result['overview']['total_taxable_profit_loss']).is_close('211.9')
Exemple #8
0
 def make_ledger_action(  # pylint: disable=no-self-use
     self,
     data: Dict[str, Any],
     **_kwargs: Any,
 ) -> LedgerAction:
     return LedgerAction(**data)