Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RP2ValueError("Total in-transaction value < total taxable entries") from None #24

Closed
jameskupke opened this issue Apr 3, 2022 · 13 comments

Comments

@jameskupke
Copy link

RP2

Version: RP2 0.9.26 (https://pypi.org/project/rp2/)
Command: rp2_us -m fifo -o output/ -p rp2_ output/crypto_data.config output/crypto_data.ods

Ran DaLI with Coinbase REST API for output ODS file and config file. Logs show:

2022-04-03 07:34:43,384/rp2/INFO: Country: us
2022-04-03 07:34:43,384/rp2/INFO: Accounting Method: fifo
2022-04-03 07:34:43,396/rp2/INFO: Configuration file: output/crypto_data.config
2022-04-03 07:34:43,396/rp2/INFO: Input file: output/crypto_data.ods
2022-04-03 07:34:43,513/rp2/INFO: Processing ALGO
2022-04-03 07:34:43,649/rp2/INFO: Processing AMP
2022-04-03 07:34:43,713/rp2/INFO: Processing ANKR
2022-04-03 07:34:43,778/rp2/INFO: Processing AUCTION
2022-04-03 07:34:43,843/rp2/INFO: Processing BOND
2022-04-03 07:34:43,908/rp2/INFO: Processing BTC
2022-04-03 07:34:43,975/rp2/INFO: Processing CGLD
2022-04-03 07:34:44,039/rp2/INFO: Processing CHZ
2022-04-03 07:34:44,103/rp2/INFO: Processing CLV
2022-04-03 07:34:44,168/rp2/INFO: Processing COMP
2022-04-03 07:34:44,233/rp2/INFO: Processing CTSI
2022-04-03 07:34:44,297/rp2/INFO: Processing DAI
2022-04-03 07:34:44,431/rp2/INFO: Processing ETH
2022-04-03 07:34:44,493/rp2/INFO: Processing ETH2
2022-04-03 07:34:44,556/rp2/INFO: Processing FET
2022-04-03 07:34:44,621/rp2/INFO: Processing FORTH
2022-04-03 07:34:44,685/rp2/INFO: Processing GRT
2022-04-03 07:34:44,749/rp2/INFO: Processing IOTX
2022-04-03 07:34:44,813/rp2/INFO: Processing LRC
2022-04-03 07:34:44,877/rp2/ERROR: Fatal exception occurred:
Traceback (most recent call last):
  File "/home/james/.local/lib/python3.10/site-packages/rp2/rp2_main.py", line 96, in _rp2_main_internal
    computed_data: ComputedData = compute_tax(configuration=configuration, accounting_method=accounting_method, input_data=input_data)
  File "/home/james/.local/lib/python3.10/site-packages/rp2/tax_engine.py", line 44, in compute_tax
    unfiltered_gain_loss_set: GainLossSet = _create_unfiltered_gain_and_loss_set(configuration, accounting_method, input_data, unfiltered_taxable_event_set)
  File "/home/james/.local/lib/python3.10/site-packages/rp2/tax_engine.py", line 155, in _create_unfiltered_gain_and_loss_set
    raise RP2ValueError("Total in-transaction value < total taxable entries") from None
rp2.rp2_error.RP2ValueError: Total in-transaction value < total taxable entries
2022-04-03 07:34:44,877/rp2/INFO: Log file: ./log/rp2_2022_04_03_07_34_43_362639.log
2022-04-03 07:34:44,877/rp2/INFO: Generated output directory: output/
2022-04-03 07:34:44,877/rp2/INFO: Done

Looking at LRC table:

IN                          
Timestamp Asset Exchange Holder Transaction Type   Spot Price Crypto In Crypto Fee USD In No Fee USD In With Fee USD Fee Unique ID Notes
2021-11-04 00:49:31 +0000 LRC Coinbase (name) Income   $1.15 0.43489605   $0.50 $0.50 $0.00 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Coinbase EARN; Received Loopring
2021-11-04 00:50:20 +0000 LRC Coinbase (name) Income   $1.15 0.43378302   $0.50 $0.50 $0.00 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Coinbase EARN; Received Loopring
2021-11-04 00:50:50 +0000 LRC Coinbase (name) Income   $1.15 0.43449923   $0.50 $0.50 $0.00 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Coinbase EARN; Received Loopring
TABLE END                          
                           
OUT                          
Timestamp Asset Exchange Holder Transaction Type   Spot Price Crypto Out No Fee Crypto Fee Crypto Out With Fee USD Out No Fee USD Fee Unique ID Notes
2021-11-04 00:51:21 +0000 LRC Coinbase (name) Sell   $1.15 1.30317832 0.00000000 1.30317832 $1.50 $0.00 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Sell side of conversion of 1.30317832 LRC
TABLE END                          

From what I see, it's $1.50 in and $1.50 out. I'm not sure if there's something else I should be looking at.

@eprbell
Copy link
Owner

eprbell commented Apr 3, 2022

The error is saying that total crypto in < total crypto out. If you sum the 3 crypto in entries you get 1.3031783, which is less than the amount disposed of in the out transaction (1.30317832). Could you check on the Coinbase-generated CSV files and verify the following:

  • do you have a total of 4 LRC transactions?
  • do the LRC crypto in/out values match with those in the ODS file?

@jameskupke
Copy link
Author

Here's the 4 raw transactions.

XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Reward 2021-11-04T00:49:31Z LRC 0.434896059841698 0.5005436087475 Coinbase
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Reward 2021-11-04T00:50:20Z LRC 0.433783021732529 0.499110742812 Coinbase
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Reward 2021-11-04T00:50:50Z LRC 0.434499239626331 0.499934814038 Coinbase
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Converted from 2021-11-04T00:51:21Z         LRC 1.30317832 1.499827928488
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Converted to 2021-11-04T00:51:22Z XTZ 0.235629 1.499827928488 Coinbase      

So, it looks like there's a few more digits in the raw transactions vs. what the ODS outputted, so it seems like a rounding issue?

@eprbell
Copy link
Owner

eprbell commented Apr 3, 2022

This is from the Coinbase CSV, correct? The next step is to look for these 4 transactions in the DaLI debug log: this will show us what numbers Coinbase is putting out via the REST API. Definitely looks like a rounding issue, but looking at the REST output from the debug log will tell us who's rounding incorrectly (DaLI or Coinbase).

@jameskupke
Copy link
Author

Yes, the above post was the Coinbase CSV.

Here's the DEBUG logs:

2022-04-03 07:34:25,455/dali/DEBUG: Self-contained transaction: InTransaction:
  plugin=Coinbase
  unique_id=AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA
  raw_data={"id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "type": "send", "status": "completed", "amount": {"amount": "0.43449923", "currency": "LRC"}, "native_amount": {"amount": "0.50", "currency": "USD"}, "description": "Earn Task", "created_at": "2021-11-04T00:50:50Z", "updated_at": "2021-11-04T00:50:50Z", "resource": "transaction", "resource_path": "/v2/accounts/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/transactions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "instant_exchange": false, "off_chain_status": "completed", "network": {"status": "off_blockchain", "status_description": null}, "from": {"id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "resource": "user", "resource_path": "/v2/users/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "currency": "LRC"}, "details": {"title": "Received Loopring", "subtitle": "From Coinbase Earn", "header": "Received 0.43449923 LRC ($0.50)", "health": "positive"}, "hide_native_amount": false}
  timestamp=2021-11-04 00:50:50 +0000
  asset=LRC
  exchange=Coinbase
  holder=(name)
  transaction_type=Income
  spot_price=1.150750025494866814838774283
  crypto_in=0.43449923
  fiat_fee=0
  fiat_in_no_fee=0.50
  fiat_in_with_fee=0.50
  notes=Coinbase EARN; Received Loopring
2022-04-03 07:34:25,455/dali/DEBUG: Self-contained transaction: InTransaction:
  plugin=Coinbase
  unique_id=AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA
  raw_data={"id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "type": "send", "status": "completed", "amount": {"amount": "0.43378302", "currency": "LRC"}, "native_amount": {"amount": "0.50", "currency": "USD"}, "description": "Earn Task", "created_at": "2021-11-04T00:50:20Z", "updated_at": "2021-11-04T00:50:20Z", "resource": "transaction", "resource_path": "/v2/accounts/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/transactions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "instant_exchange": false, "off_chain_status": "completed", "network": {"status": "off_blockchain", "status_description": null}, "from": {"id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "resource": "user", "resource_path": "/v2/users/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "currency": "LRC"}, "details": {"title": "Received Loopring", "subtitle": "From Coinbase Earn", "header": "Received 0.43378302 LRC ($0.50)", "health": "positive"}, "hide_native_amount": false}
  timestamp=2021-11-04 00:50:20 +0000
  asset=LRC
  exchange=Coinbase
  holder=(name)
  transaction_type=Income
  spot_price=1.152650004603684118387114369
  crypto_in=0.43378302
  fiat_fee=0
  fiat_in_no_fee=0.50
  fiat_in_with_fee=0.50
  notes=Coinbase EARN; Received Loopring
2022-04-03 07:34:25,455/dali/DEBUG: Self-contained transaction: InTransaction:
  plugin=Coinbase
  unique_id=AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA
  raw_data={"id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "type": "send", "status": "completed", "amount": {"amount": "0.43489605", "currency": "LRC"}, "native_amount": {"amount": "0.50", "currency": "USD"}, "description": "Earn Task", "created_at": "2021-11-04T00:49:31Z", "updated_at": "2021-11-04T00:49:31Z", "resource": "transaction", "resource_path": "/v2/accounts/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/transactions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "instant_exchange": false, "off_chain_status": "completed", "network": {"status": "off_blockchain", "status_description": null}, "from": {"id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "resource": "user", "resource_path": "/v2/users/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "currency": "LRC"}, "details": {"title": "Received Loopring", "subtitle": "From Coinbase Earn", "header": "Received 0.43489605 LRC ($0.50)", "health": "positive"}, "hide_native_amount": false}
  timestamp=2021-11-04 00:49:31 +0000
  asset=LRC
  exchange=Coinbase
  holder=(name)
  transaction_type=Income
  spot_price=1.149700026017711588780813254
  crypto_in=0.43489605
  fiat_fee=0
  fiat_in_no_fee=0.50
  fiat_in_with_fee=0.50
  notes=Coinbase EARN; Received Loopring
2022-04-03 07:34:25,455/dali/DEBUG: Self-contained transaction: OutTransaction:
  plugin=Coinbase
  unique_id=AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA
  raw_data={"id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "type": "trade", "status": "completed", "amount": {"amount": "-1.30317832", "currency": "LRC"}, "native_amount": {"amount": "-1.50", "currency": "USD"}, "description": null, "created_at": "2021-11-04T00:51:21Z", "updated_at": "2021-11-04T00:51:21Z", "resource": "transaction", "resource_path": "/v2/accounts/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/transactions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "instant_exchange": false, "trade": {"id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "resource": "trade", "resource_path": "/v2/accounts/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/trades/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"}, "details": {"title": "Converted from Loopring", "subtitle": "Using LRC Wallet", "header": "Converted 1.30317832 LRC ($1.50)", "health": "positive", "payment_method_name": "LRC Wallet"}, "hide_native_amount": false}
  timestamp=2021-11-04 00:51:21 +0000
  asset=LRC
  exchange=Coinbase
  holder=(name)
  transaction_type=Sell
  spot_price=1.151032039882308662102359100
  crypto_out_no_fee=1.30317832
  crypto_fee=0E+27
  crypto_out_with_fee=1.30317832
  fiat_out_no_fee=1.50
  fiat_fee=0
  notes=Sell side of conversion of 1.30317832 LRC	

Looks like the 3 IN total to 1.3031783
The Out shows as: 1.30317832.

So it's off by −0.00000002

@jameskupke
Copy link
Author

Now that I see that's the crypto values, I may have to just manually edit a few around to make sure that the rounding errors are corrected in the ODS.

@eprbell
Copy link
Owner

eprbell commented Apr 3, 2022

Yes, looks like Coinbase is feeding us bad data via REST (though the error is really small)... :-( However it's interesting that they are generating their CSV file correctly: perhaps they have another version of the REST API they use only internally.

@jameskupke
Copy link
Author

Yeah. I was able to generate the RP2 files after manually adjusting. Since I think this is a limitation on the REST API data, I don't think it's specifically an RP2 issue. I guess close this out?

@eprbell
Copy link
Owner

eprbell commented Apr 3, 2022

Yes, it's a problem with Coinbase REST data. Let's close the issue. Thanks for reporting!

@eprbell eprbell closed this as completed Apr 3, 2022
@eprbell
Copy link
Owner

eprbell commented Apr 4, 2022

BTW, the RP2 error message was a bit cryptic: I improved it and now it should be easier to understand.

@heiden12
Copy link

Is there a work around for this issue?

@eprbell
Copy link
Owner

eprbell commented Apr 21, 2022

Unfortunately not... This is a Coinbase REST API bug, so there isn't a lot we can do about it. The only workaround would be to write a script to patch the ODS file before feeding it to RP2.

@eprbell
Copy link
Owner

eprbell commented Apr 21, 2022

Here's something hacky I put together a while ago for testing purposes. With a few modifications you should be able to use it to patch your file before feeding it to RP2:

# Copyright 2022 eprbell
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys

from argparse import ArgumentParser, Namespace, RawTextHelpFormatter
from configparser import ConfigParser
from pathlib import Path
from typing import Any, Dict

import ezodf

from dali.dali_configuration import DEFAULT_CONFIGURATION, Keyword, is_builtin_section_name
from dali.dali_main import _validate_header_configuration
from rp2.rp2_decimal import CRYPTO_DECIMALS

_VERSION: str = "0.0.1"


def main() -> None:

    args: Namespace
    parser: ArgumentParser

    dali_configuration: Dict[str, Any] = DEFAULT_CONFIGURATION

    parser = _setup_argument_parser()
    args = parser.parse_args()

    ini_config: ConfigParser = ConfigParser()
    ini_config.read(args.ini_file)
    
    for section_name in ini_config.sections():
        normalized_section_name: str = section_name.split(" ", 1)[0]
        if is_builtin_section_name(normalized_section_name):
            if section_name != normalized_section_name:
                print("Builtin section '%s' cannot have extra trailing keywords: '%s'", normalized_section_name, section_name)
                sys.exit(1)
            if section_name in {Keyword.IN_HEADER.value, Keyword.OUT_HEADER.value, Keyword.INTRA_HEADER.value}:
                dali_configuration[section_name] = _validate_header_configuration(ini_config, section_name)
            continue

    print(f"File to patch: {args.file_to_patch}")

    _apply_patch(args.file_to_patch, dali_configuration)

def _setup_argument_parser() -> ArgumentParser:
    parser: ArgumentParser = ArgumentParser(
        description=(
            "Patch Dali output\n"
        ),
        formatter_class=RawTextHelpFormatter,
    )

    parser.add_argument(
        "-v",
        "--version",
        action="version",
        version=f"DaLI Output Patcher {_VERSION}",
        help="Print DaLI Output Patcher version",
    )
    parser.add_argument(
        "ini_file",
        action="store",
        help="INI file",
        metavar="INI_FILE",
        type=str,
    )

    parser.add_argument(
        "file_to_patch",
        action="store",
        help="Dali-generated ODS file to patch",
        metavar="FILE_TO_PATCH",
        type=str,
    )

    return parser


def _apply_patch(file_to_patch: str, configuration: Dict[str, Any]) -> ArgumentParser:
    file_to_patch_path: Path = Path(file_to_patch)
    if not file_to_patch_path.exists():
        return f"Error: {file_to_patch} does not exist"

    ods_file: Any = ezodf.opendoc(str(file_to_patch_path))
    sheet: Any

    for sheet in ods_file.sheets:
        if sheet.name == "ETH":

            row_count: int = 0
            row: Any = None
            for row_count, row in enumerate(sheet.rows()):
                if row[configuration["in_header"]["unique_id"]].value == "<...snip>"
                    print(f"{row_count}: Row to patch (before): {_row_as_string(row)}")
                    sheet[row_count, configuration["in_header"]["fiat_fee"]].set_value(14.18)
                    sheet[row_count, configuration["in_header"]["fiat_in_with_fee"]].set_value(1728)
                    print(f"{row_count}: Row to patch (after):  {_row_as_string(row)}")

    ods_file.save()

def _row_as_string(row: Any) -> str:
    values: List[str] = []
    cell: Any
    for cell in row:
        value: Any = str(_parse_cell_value(cell))
        values.append(value)

    # Remove trailing whitespace from row
    i: int
    for i in range(len(values) - 1, -1, -1):
        if not values[i]:
            del values[i]
        else:
            break

    return ",".join(values)

def _parse_cell_value(cell: Any) -> Any:
    value: Any
    try:
        if cell.formula:
            value = cell.formula
        elif cell.value or cell.value == 0:
            value = round(float(cell.value), CRYPTO_DECIMALS)
            if value == -0.0:
                value = 0
        else:
            value = ""
    except ValueError:
        value = cell.value

    return value

                
if __name__ == "__main__":
    main()

@jameskupke
Copy link
Author

Is there a work around for this issue?

I didn't have very complicated amount of transactions. (Multiple INs, and 1 OUT), so I ended up just SUM-ing the values to make it equal in the ODS file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants