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)
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, )
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( )
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
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, )
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, )
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')
def make_ledger_action( # pylint: disable=no-self-use self, data: Dict[str, Any], **_kwargs: Any, ) -> LedgerAction: return LedgerAction(**data)