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_margin_position(self, gained_asset, gained_amount, fee_in_asset, margin_notes, timestamp): 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, ( 'Margin profit is negative. Should never happen for the hacky way I use em now' ) self.events[gained_asset].buys.append( BuyEvent(amount=net_gain_amount, timestamp=timestamp, rate=rate, fee_rate=0, cost=0)) # count profits if we are inside the query period if timestamp >= self.query_start_ts: self.margin_positions_profit += gain_in_profit_currency self.csv_exporter.add_margin_position( margin_notes=margin_notes, gained_asset=gained_asset, net_gain_amount=net_gain_amount, gain_in_profit_currency=gain_in_profit_currency, timestamp=timestamp, )
def add_loan_gain(self, gained_asset, gained_amount, fee_in_asset, lent_amount, open_time, close_time): 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=0, cost=0)) # count profits if we are inside the query period if timestamp >= self.query_start_ts: 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 add_buy(self, bought_asset, bought_amount, paid_with_asset, trade_rate, trade_fee, fee_currency, timestamp, is_virtual=False): paid_with_asset_rate = self.get_rate_in_profit_currency( paid_with_asset, timestamp) buy_rate = paid_with_asset_rate * trade_rate fee_price_in_profit_currency = 0 if trade_fee != 0: fee_price_in_profit_currency = self.price_historian.query_historical_price( fee_currency, self.profit_currency, timestamp) self.handle_prefork_acquisitions(bought_asset=bought_asset, bought_amount=bought_amount, paid_with_asset=paid_with_asset, trade_rate=trade_rate, trade_fee=trade_fee, fee_currency=fee_currency, timestamp=timestamp) if bought_asset not in self.events: self.events[bought_asset] = Events(list(), list()) fee_cost = fee_price_in_profit_currency * trade_fee gross_cost = bought_amount * buy_rate cost = gross_cost + fee_cost self.events[bought_asset].buys.append( BuyEvent(amount=bought_amount, timestamp=timestamp, rate=buy_rate, fee_rate=fee_cost / bought_amount, cost=cost)) if logger.isEnabledFor(logging.DEBUG): logger.debug( 'Buying {} "{}" for {} "{}" ({} "{}" per "{}" or {} "{}" per ' '"{}") at {}'.format( bought_amount, bought_asset, bought_amount * trade_rate, paid_with_asset, trade_rate, paid_with_asset, bought_asset, buy_rate, self.profit_currency, bought_asset, tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'))) self.csv_exporter.add_buy( bought_asset=bought_asset, rate=buy_rate, fee_cost=fee_cost, amount=bought_amount, gross_cost=gross_cost, cost=cost, paid_with_asset=paid_with_asset, paid_with_asset_rate=paid_with_asset_rate, timestamp=timestamp, is_virtual=is_virtual, )
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/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(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_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_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 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_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 add_margin_position( self, gain_loss_asset: Asset, gain_loss_amount: FVal, fee_in_asset: FVal, margin_notes: str, timestamp: Timestamp, ): 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=0, cost=0)) # 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 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_buy(self, bought_asset, bought_amount, paid_with_asset, trade_rate, trade_fee, fee_currency, timestamp, is_virtual=False): paid_with_asset_rate = self.get_rate_in_profit_currency( paid_with_asset, timestamp) buy_rate = paid_with_asset_rate * trade_rate fee_price_in_profit_currency = 0 if trade_fee != 0: fee_price_in_profit_currency = self.price_historian.query_historical_price( fee_currency, self.profit_currency, timestamp) self.handle_prefork_acquisitions(bought_asset=bought_asset, bought_amount=bought_amount, paid_with_asset=paid_with_asset, trade_rate=trade_rate, trade_fee=trade_fee, fee_currency=fee_currency, timestamp=timestamp) if bought_asset not in self.events: self.events[bought_asset] = Events(list(), list()) fee_cost = fee_price_in_profit_currency * trade_fee gross_cost = bought_amount * buy_rate cost = gross_cost + fee_cost self.events[bought_asset].buys.append( BuyEvent(amount=bought_amount, timestamp=timestamp, rate=buy_rate, fee_rate=fee_cost / bought_amount, cost=cost)) 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, ) self.csv_exporter.add_buy( bought_asset=bought_asset, rate=buy_rate, fee_cost=fee_cost, amount=bought_amount, gross_cost=gross_cost, cost=cost, paid_with_asset=paid_with_asset, paid_with_asset_rate=paid_with_asset_rate, timestamp=timestamp, is_virtual=is_virtual, )