From 6d119df31ba641017a5fae6e93a793f871f64dbd Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Fri, 26 Jul 2024 18:59:33 -0700 Subject: [PATCH 1/5] 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. --- .../src/finance/lib/internal/lib_taxes.bash | 202 ++++++++++++++++-- 1 file changed, 188 insertions(+), 14 deletions(-) diff --git a/container/src/finance/lib/internal/lib_taxes.bash b/container/src/finance/lib/internal/lib_taxes.bash index 282883b..80adfc3 100644 --- a/container/src/finance/lib/internal/lib_taxes.bash +++ b/container/src/finance/lib/internal/lib_taxes.bash @@ -421,8 +421,10 @@ function lib_taxes::__taxes_print() # since it's technically *not* income (such as card cashback rebates) # # - "RAW_TRADE" has no need for a parser/formatter since the comment - # contains all of the trade but *MUST* contain tags found within - # trades tags (BUY/SELL/FEE) in order to be printed and not skipped. + # contains all of the trade but + # * *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) @@ -477,11 +479,84 @@ function lib_taxes::__taxes_print() if (Total ~ /\./) {sub("0*$", "", Total); sub("\\.$", "", Total)} 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. # - Do not print empty USD entries or empty symbol (Algorand) entries. # - Do not print testnet symbols. # 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 != "" \ && Symbol != "BTCt" && Symbol != "DOGEt" && Symbol != "tLTC" && Symbol != "LTCTEST") { @@ -492,7 +567,24 @@ function lib_taxes::__taxes_print() printf Volume OFS printf Currency 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" } @@ -632,17 +724,77 @@ function lib_taxes::__taxes_print() switch (Action) { 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 Currency OFS Total OFS Symbol OFS Volume OFS - printf FeeCurrency OFS Fee + printf Currency OFS Total OFS Symbol OFS CostBasis OFS + printf OFS # FeeCurrency/Fee 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 (FeeCurrency != "USD") + { + printf Date OFS "SPEND" OFS Account OFS + printf FeeCurrency OFS Fee OFS "USD" + printf "\n"; + } break + 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 Symbol OFS Volume OFS Currency OFS Total OFS - printf FeeCurrency OFS Fee + printf Symbol OFS Volume OFS Currency OFS CostBasis OFS + 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 (FeeCurrency != "USD") + { + printf Date OFS "SPEND" OFS Account OFS + printf FeeCurrency OFS Fee OFS "USD" + printf "\n"; + } break + case "BORROW": # Reset tail end vars since this is an "INCOME" tag FeeCurrency = $14 @@ -763,17 +915,36 @@ function lib_taxes::__taxes_print() if (Total ~ /\./) {sub("0*$", "", Total); sub("\\.$", "", Total)} 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 Action OFS printf Account OFS printf Symbol OFS printf Volume OFS printf Currency OFS - printf Total OFS - printf FeeCurrency OFS - printf Fee - + printf CostBasis OFS + 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 + if (FeeCurrency != "USD") + { + printf Date OFS "SPEND" OFS Account OFS + printf FeeCurrency OFS Fee OFS "USD" + printf "\n"; + } }' fi @@ -963,10 +1134,13 @@ function lib_taxes::__reports_patch() # Ensure appropriate tags in respective files # # NOTE: - # # - Due to PARTIAL_TRADES, there may be a lone FEE SPEND straggler # - 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: # - Do *NOT* do a unique sort here! From 2c1b8969510349030b403653488ce4d0b607b955 Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Fri, 26 Jul 2024 19:34:27 -0700 Subject: [PATCH 2/5] hledger-flow: gemini: exchange: 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. --- .../gemini/gemini-exchange-shared.rules | 18 ++++++++++++++--- .../accounts/gemini/gemini-shared.bash | 20 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/container/src/hledger-flow/accounts/gemini/gemini-exchange-shared.rules b/container/src/hledger-flow/accounts/gemini/gemini-exchange-shared.rules index 7645fa0..6fc31db 100644 --- a/container/src/hledger-flow/accounts/gemini/gemini-exchange-shared.rules +++ b/container/src/hledger-flow/accounts/gemini/gemini-exchange-shared.rules @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -fields code_,date,type,order_type,currency_one,currency_two,amount_,cost,fees,destination,txid,direction,subaccount +fields code_,date,type,order_type,currency_one,currency_two,amount_,cost,fees,cost_basis,destination,txid,direction,subaccount date-format %Y-%m-%d %H:%M:%S description %date +0000 @@ -92,12 +92,24 @@ if %type ^TRADE$ if %order_type ^Buy$ amount %amount_ %currency_one @@ %cost %currency_two comment type:%type, order_type:%order_type, order_id:%txid, tid:%code_, taxed_as:BUY - comment2 %date +0000,BUY,gemini,%currency_one,%amount_,%currency_two,%cost,%currency_two,%fees + comment2 %date +0000,BUY,gemini,%currency_one,%amount_,%currency_two,%cost_basis,%currency_two,%fees +# TODO: HACK: re: #51, FeeCurrency and Fee are added even though cost_basis is calculated (for non-fiat fee disposal, see lib_taxes) + +# TODO: HACK: see #51 and respective lib_taxes work-around +if %order_type ^Buy$ +& %currency_two ^USD$ + comment2 %date +0000,BUY,gemini,%currency_one,%amount_,%currency_two,%cost_basis,, if %order_type ^Sell$ amount -%amount_ %currency_one @@ %cost %currency_two comment type:%type, order_type:%order_type, order_id:%txid, tid:%code_, taxed_as:SELL - comment2 %date +0000,SELL,gemini,%currency_one,%amount_,%currency_two,%cost,%currency_two,%fees + comment2 %date +0000,SELL,gemini,%currency_one,%amount_,%currency_two,%cost_basis,%currency_two,%fees +# TODO: HACK: re: #51, FeeCurrency and Fee are added even though cost_basis is calculated (for non-fiat fee disposal, see lib_taxes) + +# TODO: HACK: see #51 and respective lib_taxes work-around +if %order_type ^Sell$ +& %currency_two ^USD$ + comment2 %date +0000,SELL,gemini,%currency_one,%amount_,%currency_two,%cost_basis,, # GUSD/USD workaround (see preprocess) if %type ^TRADE$ diff --git a/container/src/hledger-flow/accounts/gemini/gemini-shared.bash b/container/src/hledger-flow/accounts/gemini/gemini-shared.bash index 1340634..df3c0f5 100755 --- a/container/src/hledger-flow/accounts/gemini/gemini-shared.bash +++ b/container/src/hledger-flow/accounts/gemini/gemini-shared.bash @@ -111,6 +111,7 @@ function parse_transfers() } printf info_feeAmount OFS + printf OFS # Cost-basis (N/A) printf $8 OFS # Destination (info_destination) printf $9 OFS # TXID (info_txHash) @@ -176,6 +177,7 @@ function parse_transfers() printf OFS # Price (N/A) printf OFS # Fees (N/A) + printf OFS # Cost-basis (N/A) printf $6 OFS # Destination (info_destination) printf $7 OFS # TXID (info_txHash) @@ -250,6 +252,23 @@ function parse_trades() } printf fee_amount OFS # Fees + # TODO: HACK: see #51 and respective lib_taxes work-around + # Calculate cost-basis + switch ($5) + { + case "Buy": + cost_basis=sprintf("%.8f", cost + fee_amount) + break + case "Sell": + cost_basis=sprintf("%.8f", cost - fee_amount) + break + default: + printf "FATAL: unsupported order type: " $5 + print $0 + exit + } + printf cost_basis OFS + printf OFS # Destination (N/A) printf $10 OFS # TXID (tid) @@ -309,6 +328,7 @@ function parse_earn() printf priceAmount OFS printf OFS # Fees + printf OFS # Cost-basis printf OFS # Destination printf OFS # TXID From 1975e1dbbbd211ea842f130a9812511cb2ac897f Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Fri, 26 Jul 2024 19:55:11 -0700 Subject: [PATCH 3/5] hledger-flow: coinbase: 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. --- .../accounts/coinbase/coinbase-shared.rules | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/container/src/hledger-flow/accounts/coinbase/coinbase-shared.rules b/container/src/hledger-flow/accounts/coinbase/coinbase-shared.rules index 1eb8664..4f7154d 100644 --- a/container/src/hledger-flow/accounts/coinbase/coinbase-shared.rules +++ b/container/src/hledger-flow/accounts/coinbase/coinbase-shared.rules @@ -359,7 +359,17 @@ if %type ^buy$ account2 assets:coinbase:%subaccount:%buy_total_currency comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, buy_id:%buy_id, taxed_as:BUY comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%buy_total_currency,%buy_total_amount,, -# NOTE: cost-basis = proceeds + fee = %buy_total_amount (but do *NOT* include Fee/FeeCurrency column) + +if %type ^buy$ +& %buy_fee_amount [1-9] + comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%buy_total_currency,%buy_total_amount,%buy_fee_currency,%buy_fee_amount +# TODO: HACK: re: #51, FeeCurrency and Fee are added even though cost-basis is calculated (for non-fiat fee disposal, see lib_taxes) + +# TODO: HACK: see #51 and respective lib_taxes work-around +if %type ^buy$ +& %buy_fee_amount [1-9] +& %buy_fee_currency ^USD$ + comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%buy_total_currency,%buy_total_amount,, if %type ^buy$ & %buy_total_currency ^USDC$ @@ -404,7 +414,17 @@ if %type ^sell$ account2 assets:coinbase:%subaccount:%sell_total_currency comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, sell_id:%sell_id, taxed_as:SELL comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%sell_total_currency,%sell_total_amount,, -# NOTE: cost-basis = sale - fee = %sell_total_amount (but do *NOT* include Fee/FeeCurrency column) + +if %type ^sell$ +& %sell_fee_amount [1-9] + comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%sell_total_currency,%sell_total_amount,%sell_fee_currency,%sell_fee_amount +# TODO: HACK: re: #51, FeeCurrency and Fee are added even though cost-basis is calculated (for non-fiat fee disposal, see lib_taxes) + +# TODO: HACK: see #51 and respective lib_taxes work-around +if %type ^sell$ +& %sell_fee_amount [1-9] +& %sell_fee_currency ^USD$ + comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%sell_total_currency,%sell_total_amount,, if %type ^sell$ & %sell_total_currency ^USDC$ @@ -689,6 +709,23 @@ if %type ^advanced_trade_fill$ comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, advanced_trade_fill_fill_price:%advanced_trade_fill_fill_price, advanced_trade_fill_product_id:%advanced_trade_fill_product_id, advanced_trade_fill_order_id:%advanced_trade_fill_order_id, advanced_trade_fill_order_side:%advanced_trade_fill_order_side, advanced_trade_fill_pair_lhs:%advanced_trade_fill_pair_lhs, advanced_trade_fill_pair_rhs:%advanced_trade_fill_pair_rhs, direction:%direction, taxed_as:BUY comment2 %created_at +0000,BUY,coinbase,%advanced_trade_fill_pair_lhs,%amount_amount,%advanced_trade_fill_pair_rhs,%advanced_trade_fill_cost_basis_amount,, +if %type ^advanced_trade_fill$ +& %advanced_trade_fill_order_side ^buy$ +& %advanced_trade_fill_real_value_amount [1-9] +& %advanced_trade_fill_commission [1-9] +& %direction ^IN$ + comment2 %created_at +0000,BUY,coinbase,%advanced_trade_fill_pair_lhs,%amount_amount,%advanced_trade_fill_pair_rhs,%advanced_trade_fill_cost_basis_amount,%advanced_trade_fill_pair_rhs,%advanced_trade_fill_commission +# TODO: HACK: re: #51, FeeCurrency and Fee are added even though cost-basis is calculated (for non-fiat fee disposal, see lib_taxes) + +# TODO: HACK: see #51 and respective lib_taxes work-around +if %type ^advanced_trade_fill$ +& %advanced_trade_fill_order_side ^buy$ +& %advanced_trade_fill_real_value_amount [1-9] +& %advanced_trade_fill_commission [1-9] +& %advanced_trade_fill_pair_rhs ^USD$ +& %direction ^IN$ + comment2 %created_at +0000,BUY,coinbase,%advanced_trade_fill_pair_lhs,%amount_amount,%advanced_trade_fill_pair_rhs,%advanced_trade_fill_cost_basis_amount,, + # # SELL # @@ -708,4 +745,21 @@ if %type ^advanced_trade_fill$ comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, advanced_trade_fill_fill_price:%advanced_trade_fill_fill_price, advanced_trade_fill_product_id:%advanced_trade_fill_product_id, advanced_trade_fill_order_id:%advanced_trade_fill_order_id, advanced_trade_fill_order_side:%advanced_trade_fill_order_side, advanced_trade_fill_pair_lhs:%advanced_trade_fill_pair_lhs, advanced_trade_fill_pair_rhs:%advanced_trade_fill_pair_rhs, direction:%direction, taxed_as:SELL comment2 %created_at +0000,SELL,coinbase,%advanced_trade_fill_pair_lhs,%amount_amount,%advanced_trade_fill_pair_rhs,%advanced_trade_fill_cost_basis_amount,, +if %type ^advanced_trade_fill$ +& %advanced_trade_fill_order_side ^sell$ +& %advanced_trade_fill_real_value_amount [1-9] +& %advanced_trade_fill_commission [1-9] +& %direction ^OUT$ + comment2 %created_at +0000,SELL,coinbase,%advanced_trade_fill_pair_lhs,%amount_amount,%advanced_trade_fill_pair_rhs,%advanced_trade_fill_cost_basis_amount,%advanced_trade_fill_pair_rhs,%advanced_trade_fill_commission +# TODO: HACK: re: #51, FeeCurrency and Fee are added even though cost-basis is calculated (for non-fiat fee disposal, see lib_taxes) + +# TODO: HACK: see #51 and respective lib_taxes work-around +if %type ^advanced_trade_fill$ +& %advanced_trade_fill_order_side ^sell$ +& %advanced_trade_fill_real_value_amount [1-9] +& %advanced_trade_fill_commission [1-9] +& %advanced_trade_fill_pair_rhs ^USD$ +& %direction ^OUT$ + comment2 %created_at +0000,SELL,coinbase,%advanced_trade_fill_pair_lhs,%amount_amount,%advanced_trade_fill_pair_rhs,%advanced_trade_fill_cost_basis_amount,, + # vim: sw=2 sts=2 si ai et From f228da98d889ad3e6f3adaa2ac90bb64d4991dc6 Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Fri, 26 Jul 2024 20:05:23 -0700 Subject: [PATCH 4/5] hledger-flow: bittrex: 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. --- .../accounts/bittrex/bittrex-shared.bash | 19 ++++++++ .../accounts/bittrex/bittrex-shared.rules | 44 +++++++++++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/container/src/hledger-flow/accounts/bittrex/bittrex-shared.bash b/container/src/hledger-flow/accounts/bittrex/bittrex-shared.bash index e09889e..075641d 100755 --- a/container/src/hledger-flow/accounts/bittrex/bittrex-shared.bash +++ b/container/src/hledger-flow/accounts/bittrex/bittrex-shared.bash @@ -60,6 +60,7 @@ function parse_deposit() printf("%.8f", $4); printf OFS # Amount printf OFS # Proceeds printf OFS # Fees + printf OFS # Cost-basis printf $5 OFS # Destination printf $6 OFS # TXID printf "IN" OFS # Direction @@ -90,6 +91,22 @@ function parse_trade() # So, turn BTC-USD into two separate columns. sub(/-/, ",", $4) + # TODO: HACK: see #51 and respective lib_taxes work-around + # Calculate cost-basis + switch ($3) + { + case "BUY": + cost_basis=sprintf("%.8f", $6 + $7) + break + case "SELL": + cost_basis=sprintf("%.8f", $6 - $7) + break + default: + printf "FATAL: unsupported order type: " $3 + print $0 + exit + } + printf $1 OFS # UID printf date OFS # Date printf "TRADE" OFS # Type @@ -99,6 +116,7 @@ function parse_trade() printf("%.8f", $5); printf OFS # Amount printf("%.8f", $6); printf OFS # Proceeds printf("%.8f", $7); printf OFS # Fees + printf cost_basis OFS # Cost-basis printf OFS # Destination printf OFS # TXID printf OFS # Direction @@ -129,6 +147,7 @@ function parse_withdrawal() printf("%.8f", $4); printf OFS # Amount printf OFS # Proceeds printf("%.8f", $5); printf OFS # Fees + printf OFS # Cost-basis printf $6 OFS # Destination printf $7 OFS # TXID printf "OUT" OFS # Direction diff --git a/container/src/hledger-flow/accounts/bittrex/bittrex-shared.rules b/container/src/hledger-flow/accounts/bittrex/bittrex-shared.rules index 9477ea7..a990260 100644 --- a/container/src/hledger-flow/accounts/bittrex/bittrex-shared.rules +++ b/container/src/hledger-flow/accounts/bittrex/bittrex-shared.rules @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -fields code_,date,type,order_type,currency_one,currency_two,amount_,proceeds,fees,destination,txid,direction,subaccount +fields code_,date,type,order_type,currency_one,currency_two,amount_,proceeds,fees,cost_basis,destination,txid,direction,subaccount date-format %Y-%m-%d %H:%M:%S description %date +0000 @@ -59,7 +59,6 @@ if %type ^TRADE$ amount %amount_ %currency_one @@ %proceeds %currency_two account2 assets:bittrex:%subaccount:%currency_two comment type:%type, order_type:%order_type, code:%code_, taxed_as:%order_type - comment2 %date +0000,BUY,bittrex,%currency_one,%amount_,%currency_two,%proceeds,, if %type ^TRADE$ & %fees [1-9] @@ -67,11 +66,48 @@ if %type ^TRADE$ amount3 -%fees %currency_two account4 expenses:bittrex:%subaccount:fees:trades:%currency_two amount4 %fees %currency_two - comment2 %date +0000,BUY,bittrex,%currency_one,%amount_,%currency_two,%proceeds,%currency_two,%fees + +# +# BUY +# + +if %type ^TRADE$ +& %order_type ^BUY$ + comment2 %date +0000,BUY,bittrex,%currency_one,%amount_,%currency_two,%cost_basis,, + +if %type ^TRADE$ +& %order_type ^BUY$ +& %fees [1-9] + comment2 %date +0000,BUY,bittrex,%currency_one,%amount_,%currency_two,%cost_basis,%currency_two,%fees +# TODO: HACK: re: #51, FeeCurrency and Fee are added even though cost_basis is calculated (for non-fiat fee disposal, see lib_taxes) + +# TODO: HACK: see #51 and respective lib_taxes work-around +if %type ^TRADE$ +& %order_type ^BUY$ +& %fees [1-9] +& %currency_two ^USD$ + comment2 %date +0000,BUY,bittrex,%currency_one,%amount_,%currency_two,%cost_basis,, + +# +# SELL +# if %type ^TRADE$ & %order_type ^SELL$ amount -%amount_ %currency_one @@ %proceeds %currency_two - comment2 %date +0000,SELL,bittrex,%currency_one,%amount_,%currency_two,%proceeds,%currency_two,%fees + comment2 %date +0000,SELL,bittrex,%currency_one,%amount_,%currency_two,%cost_basis,, + +if %type ^TRADE$ +& %order_type ^SELL$ +& %fees [1-9] + comment2 %date +0000,SELL,bittrex,%currency_one,%amount_,%currency_two,%cost_basis,%currency_two,%fees +# TODO: HACK: re: #51, FeeCurrency and Fee are added even though cost_basis is calculated (for non-fiat fee disposal, see lib_taxes) + +# TODO: HACK: see #51 and respective lib_taxes work-around +if %type ^TRADE$ +& %order_type ^SELL$ +& %fees [1-9] +& %currency_two ^USD$ + comment2 %date +0000,SELL,bittrex,%currency_one,%amount_,%currency_two,%cost_basis,, # vim: sw=2 sts=2 si ai et From 731a8c95635d9b524fd7811070023999e6415fe7 Mon Sep 17 00:00:00 2001 From: Aaron Fiore Date: Fri, 26 Jul 2024 20:12:49 -0700 Subject: [PATCH 5/5] hledger-flow: changelly: cost-basis comments 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. --- .../accounts/changelly/changelly-shared.rules | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/container/src/hledger-flow/accounts/changelly/changelly-shared.rules b/container/src/hledger-flow/accounts/changelly/changelly-shared.rules index aeaa0c0..20364be 100644 --- a/container/src/hledger-flow/accounts/changelly/changelly-shared.rules +++ b/container/src/hledger-flow/accounts/changelly/changelly-shared.rules @@ -26,10 +26,19 @@ account3 assets:changelly:%subaccount:%currency_from amount3 -%currency_from_amount %currency_from @@ %currency_to_amount %currency_to account4 equity:changelly:%subaccount:deposit:%currency_to -# NOTE: fee is not added to cost-basis here because currency_to_amount includes -# that automatically (it deducts the fee and computes into currency_to_amount). -# NOTE: because the fee is considered a facilitation fee for the trade, -# *don't* use the provided %fee and instead use the adjust cost-basis instead. +# NOTE: +# +# - %fee is not added to cost-basis here because %currency_to_amount includes +# the fee (it adjusts the proceeds and computes into %currency_to_amount). +# +# - %fee is considered a facilitation fee for the trade; so *don't* add to +# column (see above). +# +# - %currency_from_amount is fully disposed (as the adjustment happens to +# %currency_to_amount). +# +# - For details, see #51. +# comment rate:%rate, destination:%destination, taxed_as:SELL comment2 %date +0000,SELL,changelly,%currency_from,%currency_from_amount,%currency_to,%currency_to_amount,,