container: lib_taxes: cost-basis work-around for bitcoin.tax

Issue #51 describes at least a few undocumented bitcoin.tax issues:

  - Bicoin.tax gives unexpected cost-basis results when `Fee` is given
    with `Total` (when `Total` is given in place of `Price`). The
    expectation is that bitcoin.tax will perform the cost-basis
    calculation on `Total` when `Fee` is also given.

    However, bitcoin.tax *will* give expected cost-basis results if
    `Price` is given in place of `Total` (with `Fee` also given) *or* if
    `Total` is given *after* local cost-basis adjustments are made (but
    *without* `Fee` given).

    The rationale for why docker-finance doesn't use `Price`:

      * docker-finance has all of the `Total`s; so `Price` isn't
        necessary.

      * Local price information isn't available for most trades (and
        shouldn't be necessary since all `Total`s are available).

  - Additionally, when `Fee` is non-fiat (crypto), it now must be marked
    as a SPEND in order to be disposed (and to produce an accurate
    closing report).

  - Finally, if `FeeCurrency` *does* not match either `Symbol` or
    `Currency` (e.g., BTC-ETH w/ BNB fee), it's unknown if cost-basis
    must be calculated locally as well (if `Total` is given). Local
    calculations cannot be done because `Fee` price information is
    (almost certainly) not available for this type of trade.

Until upstream can assert that attaching the `Fee` will subsequently
adjust the cost-basis of `Total` *and* dispose of the `Fee` in the
process (while also allowing `Total` to be used in place of `Price`),
the `Fee` (and `FeeCurrency`) column(s) must not be populated and values
instead moved to SPEND (as described above).

Upstream is aware of these issues (since May) and they're in the process
of resolution. In the meantime, docker-finance work-arounds should
suffice for all trades that have a fiat `Fee` and/or a
`Fee`/`FeeCurrency` that matches one side of the trading pair.
This commit is contained in:
2024-07-26 18:59:33 -07:00
parent 5b3693ebc1
commit 6d119df31b

View File

@@ -421,8 +421,10 @@ function lib_taxes::__taxes_print()
# since it's technically *not* income (such as card cashback rebates) # since it's technically *not* income (such as card cashback rebates)
# #
# - "RAW_TRADE" has no need for a parser/formatter since the comment # - "RAW_TRADE" has no need for a parser/formatter since the comment
# contains all of the trade but *MUST* contain tags found within # contains all of the trade but
# trades tags (BUY/SELL/FEE) in order to be printed and not skipped. # * *MUST* contain tags found within trades tags (BUY/SELL/FEE)
# in order to be printed and not skipped.
# * TODO: HACK: *MUST* have cost-basis calculated here (for #51)
# TODO: AAVE/Compound income formatter (may be replaced by contract query) # TODO: AAVE/Compound income formatter (may be replaced by contract query)
@@ -477,11 +479,84 @@ function lib_taxes::__taxes_print()
if (Total ~ /\./) {sub("0*$", "", Total); sub("\\.$", "", Total)} if (Total ~ /\./) {sub("0*$", "", Total); sub("\\.$", "", Total)}
if (Fee ~ /\./) {sub("0*$", "", Fee); sub("\\.$", "", Fee)} if (Fee ~ /\./) {sub("0*$", "", Fee); sub("\\.$", "", Fee)}
# TODO: HACK: cost-basis calculated here (instead of preprocess) for #51
if (tag == "RAW_TRADE")
{
printf Date OFS Action OFS Account OFS
switch (Action)
{
case "SELL":
if (FeeCurrency == Symbol)
{
if (Fee ~ /\./)
{
split(Fee, cost_basis, ".")
rhs=length(cost_basis[2])
CostBasis=sprintf("%." rhs "f", Volume - Fee)
}
else
{
CostBasis=Volume - Fee
}
}
else
{
CostBasis=Volume
}
printf Currency OFS Total OFS Symbol OFS CostBasis OFS
break
case "BUY":
if (FeeCurrency == Currency)
{
if (Fee ~ /\./)
{
split(Fee, cost_basis, ".")
rhs=length(cost_basis[2])
CostBasis=sprintf("%." rhs "f", Total + Fee)
}
else
{
CostBasis=Total + Fee
}
}
else
{
CostBasis=Total
}
printf Symbol OFS Volume OFS Currency OFS CostBasis OFS
break
default:
printf "FATAL: unsupported Action: " Action
print $0
exit
}
printf OFS # FeeCurrency handled below
# Fee handled below
printf "\n"
}
# TODO: HACK: print SPEND line for non-fiat fee (see #51)
# NOTE: cost-basis *MUST* be calculated above or within preprocess
if (is_trades && has_fee && FeeCurrency != "USD" && Fee)
{
printf Date OFS "SPEND" OFS Account OFS
printf FeeCurrency OFS Fee OFS "USD"
printf "\n";
}
# - Drop USD-only spends (such as with Coinbase Card) because they are not needed. # - Drop USD-only spends (such as with Coinbase Card) because they are not needed.
# - Do not print empty USD entries or empty symbol (Algorand) entries. # - Do not print empty USD entries or empty symbol (Algorand) entries.
# - Do not print testnet symbols. # - Do not print testnet symbols.
# TODO: regexp optimize # TODO: regexp optimize
if ((Symbol != "USD" && Symbol != Currency) \ # TODO: HACK (isolating from RAW_TRADE): see #51 (and related lib_taxes work-around)
if (tag != "RAW_TRADE" \
&& (Symbol != "USD" && Symbol != Currency) \
&& Volume != 0 && Volume != "" \ && Volume != 0 && Volume != "" \
&& Symbol != "BTCt" && Symbol != "DOGEt" && Symbol != "tLTC" && Symbol != "LTCTEST") && Symbol != "BTCt" && Symbol != "DOGEt" && Symbol != "tLTC" && Symbol != "LTCTEST")
{ {
@@ -492,7 +567,24 @@ function lib_taxes::__taxes_print()
printf Volume OFS printf Volume OFS
printf Currency OFS printf Currency OFS
printf Total OFS printf Total OFS
printf (has_fee ? FeeCurrency OFS Fee : Memo)
if (has_fee)
{
# TODO: HACK: see #51 (and related lib_taxes work-around)
# NOTE: cost-basis *MUST* be calculated within preprocess
if (is_trades && Fee && FeeCurrency != "USD")
{
printf "" OFS ""
}
else
{
printf FeeCurrency OFS Fee
}
}
else
{
printf Memo
}
printf "\n" printf "\n"
} }
@@ -632,17 +724,77 @@ function lib_taxes::__taxes_print()
switch (Action) switch (Action)
{ {
case "SELL": case "SELL":
# TODO: HACK: cost-basis calculated here (instead of preprocess) for #51
if (FeeCurrency == Symbol)
{
if (Fee ~ /\./)
{
split(Fee, cost_basis, ".")
rhs=length(cost_basis[2])
CostBasis=sprintf("%." rhs "f", Volume - Fee)
}
else
{
CostBasis=Volume - Fee
}
}
else
{
CostBasis=Volume
}
printf Date OFS Action OFS Account OFS printf Date OFS Action OFS Account OFS
printf Currency OFS Total OFS Symbol OFS Volume OFS printf Currency OFS Total OFS Symbol OFS CostBasis OFS
printf FeeCurrency OFS Fee printf OFS # FeeCurrency/Fee handled below
# Fee handled below
printf "\n" printf "\n"
# TODO: HACK: print SPEND line for non-fiat fee (see #51)
# NOTE: cost-basis *MUST* be calculated above or within preprocess
if (FeeCurrency != "USD")
{
printf Date OFS "SPEND" OFS Account OFS
printf FeeCurrency OFS Fee OFS "USD"
printf "\n";
}
break break
case "BUY": case "BUY":
# TODO: HACK: cost-basis calculated here (instead of preprocess) for #51
if (FeeCurrency == Currency)
{
if (Fee ~ /\./)
{
split(Fee, cost_basis, ".")
rhs=length(cost_basis[2])
CostBasis=sprintf("%." rhs "f", Total + Fee)
}
else
{
CostBasis=Total + Fee
}
}
else
{
CostBasis=Total
}
printf Date OFS Action OFS Account OFS printf Date OFS Action OFS Account OFS
printf Symbol OFS Volume OFS Currency OFS Total OFS printf Symbol OFS Volume OFS Currency OFS CostBasis OFS
printf FeeCurrency OFS Fee printf OFS # FeeCurrency handled below
# Fee handled below
printf "\n" printf "\n"
# TODO: HACK: print SPEND line for non-fiat fee (see #51)
# NOTE: cost-basis *MUST* be calculated above or within preprocess
if (FeeCurrency != "USD")
{
printf Date OFS "SPEND" OFS Account OFS
printf FeeCurrency OFS Fee OFS "USD"
printf "\n";
}
break break
case "BORROW": case "BORROW":
# Reset tail end vars since this is an "INCOME" tag # Reset tail end vars since this is an "INCOME" tag
FeeCurrency = $14 FeeCurrency = $14
@@ -763,17 +915,36 @@ function lib_taxes::__taxes_print()
if (Total ~ /\./) {sub("0*$", "", Total); sub("\\.$", "", Total)} if (Total ~ /\./) {sub("0*$", "", Total); sub("\\.$", "", Total)}
if (Fee ~ /\./) {sub("0*$", "", Fee); sub("\\.$", "", Fee)} if (Fee ~ /\./) {sub("0*$", "", Fee); sub("\\.$", "", Fee)}
# TODO: HACK: cost-basis calculated here (instead of preprocess) for #51
if (FeeCurrency == Currency)
{
if (Action == "BUY") {CostBasis=Total + Fee}
else {CostBasis=Total - Fee}
}
else
{
CostBasis=Total
}
printf Date OFS printf Date OFS
printf Action OFS printf Action OFS
printf Account OFS printf Account OFS
printf Symbol OFS printf Symbol OFS
printf Volume OFS printf Volume OFS
printf Currency OFS printf Currency OFS
printf Total OFS printf CostBasis OFS
printf FeeCurrency OFS printf OFS # FeeCurrency handled below
printf Fee # Fee handled below
printf "\n"; printf "\n";
# TODO: HACK: print SPEND line for non-fiat fee (see #51)
# NOTE: cost-basis *MUST* be calculated above
if (FeeCurrency != "USD")
{
printf Date OFS "SPEND" OFS Account OFS
printf FeeCurrency OFS Fee OFS "USD"
printf "\n";
}
}' }'
fi fi
@@ -963,10 +1134,13 @@ function lib_taxes::__reports_patch()
# Ensure appropriate tags in respective files # Ensure appropriate tags in respective files
# #
# NOTE: # NOTE:
#
# - Due to PARTIAL_TRADES, there may be a lone FEE SPEND straggler # - Due to PARTIAL_TRADES, there may be a lone FEE SPEND straggler
# - Due to MATCH, income/spend BORROW/REPAY may be in trades output # - Due to MATCH, income/spend BORROW/REPAY may be in trades output
# - Bitcoin.tax will allow SPENDS in trades file with error complaints # - Bitcoin.tax:
# * will allow SPENDS in trades file (though, with error complaints)
# * TODO: HACK: due to #51, trades with non-fiat fees will need a SPEND
# line added for the disposal of said fee until upstream resolves
# their importer.
# #
# WARNING: # WARNING:
# - Do *NOT* do a unique sort here! # - Do *NOT* do a unique sort here!