Merge pull request #96 into master

731a8c9 hledger-flow: changelly: cost-basis comments for bitcoin.tax (Aaron Fiore)
f228da9 hledger-flow: bittrex: cost-basis work-around for bitcoin.tax (Aaron Fiore)
1975e1d hledger-flow: coinbase: cost-basis work-around for bitcoin.tax (Aaron Fiore)
2c1b896 hledger-flow: gemini: exchange: cost-basis work-around for bitcoin.tax (Aaron Fiore)
6d119df container: lib_taxes: cost-basis work-around for bitcoin.tax (Aaron Fiore)
This commit is contained in:
2024-07-29 16:21:51 -07:00
7 changed files with 351 additions and 27 deletions

View File

@@ -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!

View File

@@ -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

View File

@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
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

View File

@@ -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,,

View File

@@ -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

View File

@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
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$

View File

@@ -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