def test_search_buys_calculate_profit_1_buy_consumed_by_1_sell(accountant): """ Assert bought_cost is correct when 1 buy is completely consumed by 1 sell Regression test for part of https://github.com/rotki/rotki/issues/223 """ asset = 'BTC' events = accountant.events.events events[asset] = Events(list(), list()) events[asset].buys.append( BuyEvent( amount=FVal(5), timestamp=1446979735, # 08/11/2015 rate=FVal(268.1), fee_rate=FVal(0.0001), ), ) ( taxable_amount, taxable_bought_cost, taxfree_bought_cost, ) = accountant.events.search_buys_calculate_profit( selling_amount=FVal(5), selling_asset=asset, timestamp=1467378304, # 31/06/2016 ) assert taxable_amount == 5, '5 out of 5 should be taxable (within a year)' assert taxfree_bought_cost.is_close(FVal('0')) assert taxable_bought_cost.is_close(FVal('1340.5005')) assert (len(accountant.events.events[asset].buys)) == 0, 'only buy should have been used'
def test_reduce_asset_amount(accountant): asset = 'BTC' events = accountant.events.events events[asset] = Events(list(), list()) events[asset].buys.append( BuyEvent( amount=FVal(1), timestamp=1446979735, # 08/11/2015 rate=FVal(268.1), fee_rate=FVal(0.0001), ), ) events['BTC'].buys.append( BuyEvent( amount=FVal(1), timestamp=1467378304, # 31/06/2016 rate=FVal(612.45), fee_rate=FVal(0.0019), ), ) events['BTC'].buys.append( BuyEvent( amount=FVal(3), # 25/10/2016 timestamp=1477378304, rate=FVal(603.415), fee_rate=FVal(0.0017), ), ) assert accountant.events.reduce_asset_amount(asset, FVal(1.5)) assert (len(accountant.events.events[asset].buys)) == 2, '1 buy should be used' remaining_amount = accountant.events.events[asset].buys[0].amount assert remaining_amount == FVal(0.5), '0.5 of 2nd buy should remain'
def add_loan_gain( self, location: Location, gained_asset: Asset, gained_amount: FVal, fee_in_asset: Fee, lent_amount: FVal, open_time: Timestamp, close_time: Timestamp, ) -> None: """Account for gains from the given loan May raise: - PriceQueryUnsupportedAsset if from/to asset is missing from price oracles - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from the external service. - RemoteError if there is a problem reaching the price oracle server or with reading the response returned by the server """ timestamp = close_time rate = self.get_rate_in_profit_currency(gained_asset, timestamp) if gained_asset not in self.events: self.events[gained_asset] = Events([], []) net_gain_amount = gained_amount - fee_in_asset gain_in_profit_currency = net_gain_amount * rate assert gain_in_profit_currency > 0, "Loan profit is negative. Should never happen" self.events[gained_asset].buys.append( BuyEvent( amount=net_gain_amount, timestamp=timestamp, rate=rate, fee_rate=ZERO, ), ) # count profits if we are inside the query period if timestamp >= self.query_start_ts: log.debug( 'Accounting for loan profit', sensitive_log=True, location=location, gained_asset=gained_asset, gained_amount=gained_amount, gain_in_profit_currency=gain_in_profit_currency, lent_amount=lent_amount, open_time=open_time, close_time=close_time, ) self.loan_profit += gain_in_profit_currency self.csv_exporter.add_loan_profit( location=location, gained_asset=gained_asset, gained_amount=gained_amount, gain_in_profit_currency=gain_in_profit_currency, lent_amount=lent_amount, open_time=open_time, close_time=close_time, )
def add_margin_position( self, gain_loss_asset: Asset, gain_loss_amount: FVal, fee_in_asset: Fee, margin_notes: str, timestamp: Timestamp, ) -> None: rate = self.get_rate_in_profit_currency(gain_loss_asset, timestamp) if gain_loss_asset not in self.events: self.events[gain_loss_asset] = Events(list(), list()) net_gain_loss_amount = gain_loss_amount - fee_in_asset gain_loss_in_profit_currency = net_gain_loss_amount * rate if net_gain_loss_amount > 0: self.events[gain_loss_asset].buys.append( BuyEvent( amount=net_gain_loss_amount, timestamp=timestamp, rate=rate, fee_rate=ZERO, ), ) elif net_gain_loss_amount < 0: result = self.reduce_asset_amount( asset=gain_loss_asset, amount=-gain_loss_amount, ) if not result: log.critical( f'No documented buy found for {gain_loss_asset} before ' f'{timestamp_to_date(timestamp, formatstr="%d/%m/%Y %H:%M:%S")}', ) # count profit/loss if we are inside the query period if timestamp >= self.query_start_ts: self.margin_positions_profit_loss += gain_loss_in_profit_currency log.debug( 'Accounting for margin position', sensitive_log=True, notes=margin_notes, gain_loss_asset=gain_loss_asset, net_gain_loss_amount=net_gain_loss_amount, gain_loss_in_profit_currency=gain_loss_in_profit_currency, timestamp=timestamp, ) self.csv_exporter.add_margin_position( margin_notes=margin_notes, gain_loss_asset=gain_loss_asset, net_gain_loss_amount=net_gain_loss_amount, gain_loss_in_profit_currency=gain_loss_in_profit_currency, timestamp=timestamp, )
def test_search_buys_calculate_profit_1_buy_used_by_2_sells_taxable( accountant): """ Make sure that when 1 buy is used by 2 sells bought cost is correct Regression test for taxable part of: https://github.com/rotkehlchenio/rotkehlchen/issues/223 """ asset = 'BTC' events = accountant.events.events events[asset] = Events(list(), list()) events[asset].buys.append( BuyEvent( amount=FVal(5), timestamp=1446979735, # 08/11/2015 rate=FVal(268.1), fee_rate=FVal(0.0001), ), ) ( taxable_amount, taxable_bought_cost, taxfree_bought_cost, ) = accountant.events.search_buys_calculate_profit( selling_amount=FVal(3), selling_asset=asset, timestamp=1467378304, # 31/06/2016 ) assert taxable_amount == 3, '3 out of 3 should be taxable (within a year)' assert taxfree_bought_cost.is_close(FVal('0')) assert taxable_bought_cost.is_close(FVal('804.3003')) assert (len( accountant.events.events[asset].buys)) == 1, 'whole buy was not used' remaining_amount = accountant.events.events[asset].buys[0].amount assert remaining_amount == FVal(2), '3 of 5 should have been consumed' # now eat up all the rest ( taxable_amount, taxable_bought_cost, taxfree_bought_cost, ) = accountant.events.search_buys_calculate_profit( selling_amount=FVal(2), selling_asset=asset, timestamp=1467378404, # bit after previous sell ) assert taxable_amount == 2, '2 out of 2 should be taxable (within a year)' assert taxfree_bought_cost.is_close(FVal('0')) assert taxable_bought_cost.is_close(FVal('536.2002')) assert (len(accountant.events.events[asset].buys) ) == 0, 'the buy should have been used'
def add_loan_gain( self, gained_asset: Asset, gained_amount: FVal, fee_in_asset: Fee, lent_amount: FVal, open_time: Timestamp, close_time: Timestamp, ) -> None: timestamp = close_time rate = self.get_rate_in_profit_currency(gained_asset, timestamp) if gained_asset not in self.events: self.events[gained_asset] = Events(list(), list()) net_gain_amount = gained_amount - fee_in_asset gain_in_profit_currency = net_gain_amount * rate assert gain_in_profit_currency > 0, "Loan profit is negative. Should never happen" self.events[gained_asset].buys.append( BuyEvent( amount=net_gain_amount, timestamp=timestamp, rate=rate, fee_rate=ZERO, ), ) # count profits if we are inside the query period if timestamp >= self.query_start_ts: log.debug( 'Accounting for loan profit', sensitive_log=True, gained_asset=gained_asset, gained_amount=gained_amount, gain_in_profit_currency=gain_in_profit_currency, lent_amount=lent_amount, open_time=open_time, close_time=close_time, ) self.loan_profit += gain_in_profit_currency self.csv_exporter.add_loan_profit( gained_asset=gained_asset, gained_amount=gained_amount, gain_in_profit_currency=gain_in_profit_currency, lent_amount=lent_amount, open_time=open_time, close_time=close_time, )
def test_search_buys_calculate_profit_after_year(accountant): asset = 'BTC' events = accountant.events.events events[asset] = Events(list(), list()) events[asset].buys.append( BuyEvent( amount=FVal(5), timestamp=1446979735, # 08/11/2015 rate=FVal(268.1), fee_rate=FVal(0.0001), ), ) events['BTC'].buys.append( BuyEvent( amount=FVal(15), timestamp=1467378304, # 31/06/2016 rate=FVal(612.45), fee_rate=FVal(0.0019), ), ) events['BTC'].buys.append( BuyEvent( amount=FVal(3), # 25/10/2016 timestamp=1477378304, rate=FVal(603.415), fee_rate=FVal(0.0017), ), ) ( taxable_amount, taxable_bought_cost, taxfree_bought_cost, ) = accountant.events.search_buys_calculate_profit( selling_amount=FVal(8), selling_asset=asset, timestamp=1480683904, # 02/12/2016 ) assert taxable_amount == 3, '3 out of 8 should be taxable (within a year)' assert taxfree_bought_cost.is_close(FVal('1340.5005')) assert taxable_bought_cost.is_close(FVal('1837.3557')) assert (len(accountant.events.events[asset].buys)) == 2, 'first buy should have been used' remaining_amount = accountant.events.events[asset].buys[0].amount assert remaining_amount == FVal(12), '3 of 15 should have been consumed'
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_reduce_asset_amount_more_that_bought(accountant): asset = 'BTC' events = accountant.events.events events[asset] = Events(list(), list()) events[asset].buys.append( BuyEvent( amount=FVal(1), timestamp=1446979735, # 08/11/2015 rate=FVal(268.1), fee_rate=FVal(0.0001), ), ) events['BTC'].buys.append( BuyEvent( amount=FVal(1), timestamp=1467378304, # 31/06/2016 rate=FVal(612.45), fee_rate=FVal(0.0019), ), ) assert not accountant.events.reduce_asset_amount(asset, FVal(3)) assert (len( accountant.events.events[asset].buys)) == 0, 'all buys should be used'
def test_search_buys_calculate_profit_sell_more_than_bought_after_year(accountant): asset = 'BTC' events = accountant.events.events events[asset] = Events(list(), list()) events[asset].buys.append( BuyEvent( amount=FVal(1), timestamp=1446979735, # 08/11/2015 rate=FVal(268.1), fee_rate=FVal(0.0001), ), ) events['BTC'].buys.append( BuyEvent( amount=FVal(1), timestamp=1467378304, # 31/06/2016 rate=FVal(612.45), fee_rate=FVal(0.0019), ), ) ( taxable_amount, taxable_bought_cost, taxfree_bought_cost, ) = accountant.events.search_buys_calculate_profit( selling_amount=FVal(3), selling_asset=asset, timestamp=1523399409, # 10/04/2018 ) assert taxable_amount == 1, '1 out of 3 should be taxable (after a year)' assert taxfree_bought_cost.is_close(FVal('880.552')) assert taxable_bought_cost.is_close(FVal('0')) assert (len(accountant.events.events[asset].buys)) == 0, 'only buy should have been used'
def add_margin_position(self, margin: MarginPosition) -> None: """Account for the given margin position May raise: - PriceQueryUnknownFromAsset if the from asset is known to miss from cryptocompare - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from the external service. - RemoteError if there is a problem reaching the price oracle server or with reading the response returned by the server """ if margin.pl_currency not in self.events: self.events[margin.pl_currency] = Events([], []) if margin.fee_currency not in self.events: self.events[margin.fee_currency] = Events([], []) pl_currency_rate = self.get_rate_in_profit_currency( margin.pl_currency, margin.close_time) fee_currency_rate = self.get_rate_in_profit_currency( margin.pl_currency, margin.close_time) net_gain_loss_in_profit_currency = ( margin.profit_loss * pl_currency_rate - margin.fee * fee_currency_rate) # Add or remove to the pl_currency asset if margin.profit_loss > 0: self.events[margin.pl_currency].buys.append( BuyEvent( amount=margin.profit_loss, timestamp=margin.close_time, rate=pl_currency_rate, fee_rate=ZERO, ), ) elif margin.profit_loss < 0: result = self.reduce_asset_amount( asset=margin.pl_currency, amount=-margin.profit_loss, ) if not result: log.critical( f'No documented buy found for {margin.pl_currency} before ' f'{timestamp_to_date(margin.close_time, formatstr="%d/%m/%Y %H:%M:%S")}', ) # Reduce the fee_currency asset result = self.reduce_asset_amount(asset=margin.fee_currency, amount=margin.fee) if not result: log.critical( f'No documented buy found for {margin.fee_currency} before ' f'{timestamp_to_date(margin.close_time, formatstr="%d/%m/%Y %H:%M:%S")}', ) # count profit/loss if we are inside the query period if margin.close_time >= self.query_start_ts: self.margin_positions_profit_loss += net_gain_loss_in_profit_currency log.debug( 'Accounting for margin position', sensitive_log=True, notes=margin.notes, gain_loss_asset=margin.pl_currency, gain_loss_amount=margin.profit_loss, net_gain_loss_in_profit_currency= net_gain_loss_in_profit_currency, timestamp=margin.close_time, ) self.csv_exporter.add_margin_position( margin_notes=margin.notes, gain_loss_asset=margin.pl_currency, gain_loss_amount=margin.profit_loss, gain_loss_in_profit_currency=net_gain_loss_in_profit_currency, timestamp=margin.close_time, )
def add_sell( self, selling_asset: Asset, selling_amount: FVal, receiving_asset: Optional[Asset], receiving_amount: Optional[FVal], gain_in_profit_currency: FVal, total_fee_in_profit_currency: Fee, trade_rate: FVal, rate_in_profit_currency: FVal, timestamp: Timestamp, loan_settlement: bool = False, is_virtual: bool = False, ) -> None: """Account for the given sell action May raise: - PriceQueryUnknownFromAsset if the from asset is known to miss from cryptocompare - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from cryptocompare - RemoteError if there is a problem reaching the price oracle server or with reading the response returned by the server """ if selling_asset not in self.events: self.events[selling_asset] = Events([], []) self.events[selling_asset].sells.append( SellEvent( amount=selling_amount, timestamp=timestamp, rate=rate_in_profit_currency, fee_rate=total_fee_in_profit_currency / selling_amount, gain=gain_in_profit_currency, ), ) self.handle_prefork_asset_sells(selling_asset, selling_amount, timestamp) if loan_settlement: log.debug( 'Loan Settlement Selling Event', sensitive_log=True, selling_amount=selling_amount, selling_asset=selling_asset, gain_in_profit_currency=gain_in_profit_currency, profit_currency=self.profit_currency, timestamp=timestamp, ) else: log.debug( 'Selling Event', sensitive_log=True, selling_amount=selling_amount, selling_asset=selling_asset, receiving_amount=receiving_amount, receiving_asset=receiving_asset, rate=trade_rate, rate_in_profit_currency=rate_in_profit_currency, profit_currency=self.profit_currency, gain_in_profit_currency=gain_in_profit_currency, fee_in_profit_currency=total_fee_in_profit_currency, timestamp=timestamp, ) # now search the buys for `paid_with_asset` and calculate profit/loss ( taxable_amount, taxable_bought_cost, taxfree_bought_cost, ) = self.search_buys_calculate_profit( selling_amount, selling_asset, timestamp, ) general_profit_loss = ZERO taxable_profit_loss = ZERO # If we don't include crypto2crypto and we sell for crypto, stop here if receiving_asset and not receiving_asset.is_fiat( ) and not self.include_crypto2crypto: return # calculate profit/loss if not loan_settlement or (loan_settlement and self.count_profit_for_settlements): taxable_gain = taxable_gain_for_sell( taxable_amount=taxable_amount, rate_in_profit_currency=rate_in_profit_currency, total_fee_in_profit_currency=total_fee_in_profit_currency, selling_amount=selling_amount, ) general_profit_loss = gain_in_profit_currency - ( taxfree_bought_cost + taxable_bought_cost + total_fee_in_profit_currency) taxable_profit_loss = taxable_gain - taxable_bought_cost # should never happen, should be stopped at the main loop assert timestamp <= self.query_end_ts, ( "Trade time > query_end_ts found in adding to sell event") # count profit/losses if we are inside the query period if timestamp >= self.query_start_ts: if loan_settlement: # If it's a loan settlement we are charged both the fee and the gain settlement_loss = gain_in_profit_currency + total_fee_in_profit_currency expected = rate_in_profit_currency * selling_amount + total_fee_in_profit_currency msg = ( f'Expected settlement loss mismatch. rate_in_profit_currency' f' ({rate_in_profit_currency}) * selling_amount' f' ({selling_amount}) + total_fee_in_profit_currency' f' ({total_fee_in_profit_currency}) != settlement_loss ' f'({settlement_loss})') assert expected == settlement_loss, msg self.settlement_losses += settlement_loss log.debug( 'Loan Settlement Loss', sensitive_log=True, settlement_loss=settlement_loss, profit_currency=self.profit_currency, ) else: log.debug( "After Sell Profit/Loss", sensitive_log=True, taxable_profit_loss=taxable_profit_loss, general_profit_loss=general_profit_loss, profit_currency=self.profit_currency, ) self.general_trade_profit_loss += general_profit_loss self.taxable_trade_profit_loss += taxable_profit_loss if loan_settlement: self.csv_exporter.add_loan_settlement( asset=selling_asset, amount=selling_amount, rate_in_profit_currency=rate_in_profit_currency, total_fee_in_profit_currency=total_fee_in_profit_currency, timestamp=timestamp, ) else: assert receiving_asset, 'Here receiving asset should have a value' self.csv_exporter.add_sell( selling_asset=selling_asset, rate_in_profit_currency=rate_in_profit_currency, total_fee_in_profit_currency=total_fee_in_profit_currency, gain_in_profit_currency=gain_in_profit_currency, selling_amount=selling_amount, receiving_asset=receiving_asset, receiving_amount=receiving_amount, receiving_asset_rate_in_profit_currency=self. get_rate_in_profit_currency( receiving_asset, timestamp, ), taxable_amount=taxable_amount, taxable_bought_cost=taxable_bought_cost, timestamp=timestamp, is_virtual=is_virtual, )
def add_buy( self, bought_asset: Asset, bought_amount: FVal, paid_with_asset: Asset, trade_rate: FVal, fee_in_profit_currency: Fee, fee_currency: Asset, timestamp: Timestamp, is_virtual: bool = False, ) -> None: """ Account for the given buy May raise: - PriceQueryUnknownFromAsset if the from asset is known to miss from cryptocompare - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from cryptocompare - RemoteError if there is a problem reaching the price oracle server or with reading the response returned by the server """ paid_with_asset_rate = self.get_rate_in_profit_currency( paid_with_asset, timestamp) buy_rate = paid_with_asset_rate * trade_rate self.handle_prefork_asset_buys( bought_asset=bought_asset, bought_amount=bought_amount, paid_with_asset=paid_with_asset, trade_rate=trade_rate, fee_in_profit_currency=fee_in_profit_currency, fee_currency=fee_currency, timestamp=timestamp, ) if bought_asset not in self.events: self.events[bought_asset] = Events([], []) gross_cost = bought_amount * buy_rate cost_in_profit_currency = gross_cost + fee_in_profit_currency self.events[bought_asset].buys.append( BuyEvent( amount=bought_amount, timestamp=timestamp, rate=buy_rate, fee_rate=fee_in_profit_currency / bought_amount, ), ) log.debug( 'Buy Event', sensitive_log=True, bought_amount=bought_amount, bought_asset=bought_asset, paid_with_asset=paid_with_asset, rate=trade_rate, rate_in_profit_currency=buy_rate, profit_currency=self.profit_currency, timestamp=timestamp, ) if timestamp >= self.query_start_ts: self.csv_exporter.add_buy( bought_asset=bought_asset, rate=buy_rate, fee_cost=fee_in_profit_currency, amount=bought_amount, cost=cost_in_profit_currency, paid_with_asset=paid_with_asset, paid_with_asset_rate=paid_with_asset_rate, timestamp=timestamp, is_virtual=is_virtual, )
def add_buy( self, location: Location, bought_asset: Asset, bought_amount: FVal, paid_with_asset: Asset, trade_rate: FVal, fee_in_profit_currency: Fee, fee_currency: Asset, timestamp: Timestamp, is_virtual: bool = False, ) -> None: """ Account for the given buy May raise: - PriceQueryUnsupportedAsset if from/to asset is missing from all price oracles - NoPriceForGivenTimestamp if we can't find a price for the asset in the given timestamp from cryptocompare - RemoteError if there is a problem reaching the price oracle server or with reading the response returned by the server """ skip_trade = (not self.include_crypto2crypto and not bought_asset.is_fiat() and not paid_with_asset.is_fiat()) if skip_trade: return logger.debug( f'Processing buy trade of {bought_asset.identifier} with ' f'{paid_with_asset.identifier} at {timestamp}', ) paid_with_asset_rate = self.get_rate_in_profit_currency( paid_with_asset, timestamp) buy_rate = paid_with_asset_rate * trade_rate self.handle_prefork_asset_buys( location=location, bought_asset=bought_asset, bought_amount=bought_amount, paid_with_asset=paid_with_asset, trade_rate=trade_rate, fee_in_profit_currency=fee_in_profit_currency, fee_currency=fee_currency, timestamp=timestamp, ) if bought_asset not in self.events: self.events[bought_asset] = Events([], []) gross_cost = bought_amount * buy_rate cost_in_profit_currency = gross_cost + fee_in_profit_currency self.events[bought_asset].buys.append( BuyEvent( amount=bought_amount, timestamp=timestamp, rate=buy_rate, fee_rate=fee_in_profit_currency / bought_amount, ), ) log.debug( 'Buy Event', sensitive_log=True, location=str(location), bought_amount=bought_amount, bought_asset=bought_asset, paid_with_asset=paid_with_asset, rate=trade_rate, rate_in_profit_currency=buy_rate, profit_currency=self.profit_currency, timestamp=timestamp, ) if timestamp >= self.query_start_ts: self.csv_exporter.add_buy( location=location, bought_asset=bought_asset, rate=buy_rate, fee_cost=fee_in_profit_currency, amount=bought_amount, cost=cost_in_profit_currency, paid_with_asset=paid_with_asset, paid_with_asset_rate=paid_with_asset_rate, timestamp=timestamp, is_virtual=is_virtual, )
def add_buy( self, bought_asset: Asset, bought_amount: FVal, paid_with_asset: Asset, trade_rate: FVal, fee_in_profit_currency: Fee, fee_currency: Asset, timestamp: Timestamp, is_virtual: bool = False, ) -> None: paid_with_asset_rate = self.get_rate_in_profit_currency( paid_with_asset, timestamp) buy_rate = paid_with_asset_rate * trade_rate self.handle_prefork_asset_buys( bought_asset=bought_asset, bought_amount=bought_amount, paid_with_asset=paid_with_asset, trade_rate=trade_rate, fee_in_profit_currency=fee_in_profit_currency, fee_currency=fee_currency, timestamp=timestamp, ) if bought_asset not in self.events: self.events[bought_asset] = Events(list(), list()) gross_cost = bought_amount * buy_rate cost_in_profit_currency = gross_cost + fee_in_profit_currency self.events[bought_asset].buys.append( BuyEvent( amount=bought_amount, timestamp=timestamp, rate=buy_rate, fee_rate=fee_in_profit_currency / bought_amount, ), ) log.debug( 'Buy Event', sensitive_log=True, bought_amount=bought_amount, bought_asset=bought_asset, paid_with_asset=paid_with_asset, rate=trade_rate, rate_in_profit_currency=buy_rate, profit_currency=self.profit_currency, timestamp=timestamp, ) if timestamp >= self.query_start_ts: self.csv_exporter.add_buy( bought_asset=bought_asset, rate=buy_rate, fee_cost=fee_in_profit_currency, amount=bought_amount, cost=cost_in_profit_currency, paid_with_asset=paid_with_asset, paid_with_asset_rate=paid_with_asset_rate, timestamp=timestamp, is_virtual=is_virtual, )
def add_margin_position(self, margin: MarginPosition) -> None: if margin.pl_currency not in self.events: self.events[margin.pl_currency] = Events(list(), list()) if margin.fee_currency not in self.events: self.events[margin.fee_currency] = Events(list(), list()) pl_currency_rate = self.get_rate_in_profit_currency( margin.pl_currency, margin.close_time) fee_currency_rate = self.get_rate_in_profit_currency( margin.pl_currency, margin.close_time) net_gain_loss_in_profit_currency = ( margin.profit_loss * pl_currency_rate - margin.fee * fee_currency_rate) # Add or remove to the pl_currency asset if margin.profit_loss > 0: self.events[margin.pl_currency].buys.append( BuyEvent( amount=margin.profit_loss, timestamp=margin.close_time, rate=pl_currency_rate, fee_rate=ZERO, ), ) elif margin.profit_loss < 0: result = self.reduce_asset_amount( asset=margin.pl_currency, amount=-margin.profit_loss, ) if not result: log.critical( f'No documented buy found for {margin.pl_currency} before ' f'{timestamp_to_date(margin.close_time, formatstr="%d/%m/%Y %H:%M:%S")}', ) # Reduce the fee_currency asset result = self.reduce_asset_amount(asset=margin.fee_currency, amount=margin.fee) if not result: log.critical( f'No documented buy found for {margin.fee_currency} before ' f'{timestamp_to_date(margin.close_time, formatstr="%d/%m/%Y %H:%M:%S")}', ) # count profit/loss if we are inside the query period if margin.close_time >= self.query_start_ts: self.margin_positions_profit_loss += net_gain_loss_in_profit_currency log.debug( 'Accounting for margin position', sensitive_log=True, notes=margin.notes, gain_loss_asset=margin.pl_currency, gain_loss_amount=margin.profit_loss, net_gain_loss_in_profit_currency= net_gain_loss_in_profit_currency, timestamp=margin.close_time, ) self.csv_exporter.add_margin_position( margin_notes=margin.notes, gain_loss_asset=margin.pl_currency, gain_loss_amount=margin.profit_loss, gain_loss_in_profit_currency=net_gain_loss_in_profit_currency, timestamp=margin.close_time, )