diff --git a/container/src/hledger-flow/accounts/coinbase/coinbase-shared.bash b/container/src/hledger-flow/accounts/coinbase/coinbase-shared.bash index ae883bc..35bf4e5 100755 --- a/container/src/hledger-flow/accounts/coinbase/coinbase-shared.bash +++ b/container/src/hledger-flow/accounts/coinbase/coinbase-shared.bash @@ -36,102 +36,57 @@ source "${DOCKER_FINANCE_CONTAINER_REPO}/src/hledger-flow/lib/lib_preprocess.bas [ -z "$global_in_filename" ] && exit 1 -# NOTES: # -# - Coinbase REST API CSVs are a beast. They provide inconsistent headers and -# are complex. +# Notes regarding this implementation: # -# - When fetching new files, if you are expecting no changes, the transaction -# confirmation number may change and subsequently change your git-diff +# - Input data MUST be provided by Coinbase SIWC V2 REST API (via `fetch`). +# +# - Output data will consist of a "universal" CSV format for hledger rules. # -# TODO: optimize - -function parse() +function add_to_headers() { - # - # Create account ID as first column, followed by TXID - # - # NOTE: *MUST* have filename with format, where X is account ID: - # - # XXXXXXXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX_transactions.csv - # - local _account_id="${global_in_filename:0:-17}" + [ -z "$universal_header" ] && exit 1 + [ -z "$selected_header" ] && exit 1 - local _details_header="details_header2" # courtesy of fix_header() + local _column_name="$1" + [ -z "$_column_name" ] && exit 1 - local _header="id,type,status,amount_amount,amount_currency,native_amount_amount,native_amount_currency,description,created_at,updated_at,resource,resource_path,instant_exchange,details_title,details_subtitle,${_details_header},network_transaction_fee_amount,network_transaction_fee_currency,network_transaction_amount_amount,network_transaction_amount_currency,to_currency,to_address_info_address,advanced_trade_fill_fill_price,advanced_trade_fill_product_id,advanced_trade_fill_order_id,advanced_trade_fill_commission,advanced_trade_fill_order_side,network_hash,network_status" - - # A clean but hacky way to keep the stream intact while testing headers - - local _header_network="network_transaction_fee_amount,network_transaction_fee_currency,network_transaction_amount_amount,network_transaction_amount_currency,network_hash,network_status" - if lib_preprocess::test_header "$_header_network"; then _header_network=""; fi - - local _header_to="to_currency,to_address_info_address" - if lib_preprocess::test_header "$_header_to"; then _header_to=""; fi - - local _header_advanced="advanced_trade_fill_fill_price,advanced_trade_fill_product_id,advanced_trade_fill_order_id,advanced_trade_fill_commission,advanced_trade_fill_order_side" - if lib_preprocess::test_header "$_header_advanced"; then _header_advanced=""; fi - - # NOTE: csvjoin BUG(?): csvjoin *MUST* use -I or else satoshis like 0.00000021 will turn into 21000000 BTC! - csvjoin -I --snifflimit 0 "$global_in_path" <(fix_header "$global_in_path") \ - | if [ ! -z "$_header_network" ]; then add_to_header - "$_header_network"; else cat; fi \ - | if [ ! -z "$_header_to" ]; then add_to_header - "$_header_to"; else cat; fi \ - | if [ ! -z "$_header_advanced" ]; then add_to_header - "$_header_advanced"; else cat; fi \ - | xsv select "$_header" \ - | sed -e "s:^:${_account_id},:g" -e "1 s:^${_account_id},:account_id,:g" \ - | __parse >"$global_out_path" - - # TODO: complete hack because hledger-flow won't ignore deleted 1-in and - # 2-preprocessed files after script has been called. Is this still needed? - # - # NOTES: - # - Must work/be noted in rules file. - # - Parsing correct years into correct directories can create empty files - # in 2-preprocessed because the innapropriate data is sent to 1-in to - # begin with. This can possibly be handled in the fetching API but it's - # easier to do here for now. - - if [[ ! -s "$global_out_path" ]]; then - echo "SKIP,SKIP,SKIP,000.00000000,SKIP,00000.00,SKIP,SKIP,0000-00-00 00:00:00,0000-00-00 00:00:00,SKIP,SKIP,SKIP,SKIP,SKIP,SKIP,0.00000000,SKIP,000.00000000,SKIP,00.00000000,0.00000000,0.00000000" \ - >"$global_out_path" - fi + universal_header+=",${_column_name}" + lib_preprocess::test_header "$_column_name" \ + && selected_header+=",${_column_name}" } -# Fixes column data that has comma (which will ruin output order) -function fix_header() -{ - # NOTE: output will be details_header2, not details_header - xsv select "details_header" "$1" \ - | sed -e 's:,::g' -e 's:"::g' -} - -# Adds column to CSV -function add_to_header() +function join_to_header() { + # WARNING: csvjoin *MUST* use -I or else satoshis like 0.00000021 will turn into 21000000 BTC! + # TODO: `xsv join` is significantly faster but does not produce same results csvjoin -I --snifflimit 0 "$1" <(echo "$2") } -# Primary parse impl after previous preparation +# +# Internal implementation for forming "universal" CSV stream +# + function __parse() { gawk -v global_year="$global_year" -v global_subaccount="$global_subaccount" \ '{ - if (NR<2 || $10 !~ global_year) + if (NR<2 || $9 !~ global_year) next - # Coinbase is not uniform in its - sign, so remove it and deal re-add in rules + # Remove `-` for calculations (must re-add in rules) direction=($5 ~ /^-/ ? "OUT" : "IN") sub(/^-/, "", $5) sub(/^-/, "", $7) - # Cleanup the time - sub(/T/, " ", $10); sub(/T/, " ", $11) - sub(/Z/, "", $10); sub(/Z/, "", $11) - sub(/\+.*/, "", $10); sub(/\+.*/, "", $11) + # Cleanup timestamp + sub(/T/, " ", $9); + sub(/Z/, "", $9); + sub(/\+.*/, "", $9); - # Print - printf $1 OFS # account id + # Print for rules consumption + printf $1 OFS # account_id (prepended column) printf $2 OFS # id (coinbase_id) printf $3 OFS # type printf $4 OFS # status @@ -139,44 +94,92 @@ function __parse() printf $6 OFS # amount_currency printf $7 OFS # native_amount_amount printf $8 OFS # native_amount_currency - printf $9 OFS # description - printf $10 OFS # created_at - printf $11 OFS # updated_at - printf $12 OFS # resource - printf $13 OFS # resource_path - printf $14 OFS # instant_exchange - printf $15 OFS # details_title - printf $16 OFS # details_subtitle - printf $17 OFS # details_header2 - printf("%.8f", $18); printf OFS # network_transaction_fee_amount - printf $19 OFS # network_transaction_fee_currency - printf("%.8f", $20); printf OFS # network_transaction_amount_amount - printf $21 OFS # network_transaction_amount_currency - printf $22 OFS # to_currency - printf $23 OFS # to_address_info_address + printf $9 OFS # created_at + printf $10 OFS # resource + printf $11 OFS # resource_path + printf $12 OFS # description + printf $13 OFS # network_status + printf $14 OFS # network_network_name + printf $15 OFS # network_hash (txid) + printf("%.8f", $16); printf OFS # network_transaction_fee_amount + printf $17 OFS # network_transaction_fee_currency + printf $18 OFS # to_resource + printf $19 OFS # to_address + printf $20 OFS # to_email + printf $21 OFS # from_resource + printf $22 OFS # from_resource_path + printf $23 OFS # from_id + printf $24 OFS # from_name + printf $25 OFS # cancelable + printf $26 OFS # idem + printf("%.8f", $27); printf OFS # buy_total_amount + printf $28 OFS # buy_total_currency + printf("%.8f", $29); printf OFS # buy_subtotal_amount + printf $30 OFS # buy_subtotal_currency + printf("%.8f", $31); printf OFS # buy_fee_amount + printf $32 OFS # buy_fee_currency + printf $33 OFS # buy_id + printf $34 OFS # buy_payment_method_name + printf("%.8f", $35); printf OFS # sell_total_amount + printf $36 OFS # sell_total_currency + printf("%.8f", $37); printf OFS # sell_subtotal_amount + printf $38 OFS # sell_subtotal_currency + printf("%.8f", $39); printf OFS # sell_fee_amount + printf $40 OFS # sell_fee_currency + printf $41 OFS # sell_id + printf $42 OFS # sell_payment_method_name + printf("%.8f", $43); printf OFS # trade_fee_amount + printf $44 OFS # trade_fee_currency + printf $45 OFS # trade_id + printf $46 OFS # trade_payment_method_name + printf("%.8f", $47); printf OFS # advanced_trade_fill_fill_price + printf $48 OFS # advanced_trade_fill_product_id + printf $49 OFS # advanced_trade_fill_order_id + printf("%.8f", $50); printf OFS # advanced_trade_fill_commission + printf $51 OFS # advanced_trade_fill_order_side + # # Add new columns to calculate fees against native currency price - # NOTE: 0 because CB does not accurately display fiat amount - # if satoshi is small (if less than $0.00) - if ($7 > 0) {printf("%.8f", $7 / $5)} printf OFS # native_amount_price - if ($7 > 0) {printf("%.8f", ($7 / $5) * $18)} printf OFS # native_network_transaction_fee_amount - if ($7 > 0) {printf("%.8f", ($5 * 0.01) / $7)} printf OFS # native_conversion_fee_amount - # NOTE: this is a guestimation of actual conversion fees because Coinbase - # never specifies exact amount. + # + # - 0 is used because Coinbase does not accurately display fiat amount, + # if satoshi is small (valued less than $0.00) + # - # CB Advanced Trading - printf("%.8f", $24); printf OFS # advanced_trade_fill_fill_price - printf $25 OFS # advanced_trade_fill_product_id - printf $26 OFS # advanced_trade_fill_order_id - printf("%.8f", $27); printf OFS # advanced_trade_fill_commission - printf $28 OFS # advanced_trade_fill_order_side + if ($7 > 0) {printf("%.8f", $7 / $5)}; printf OFS # native_amount_price + if ($7 > 0) {printf("%.8f", ($7 / $5) * $16)}; printf OFS # native_network_transaction_fee_amount - # USER ADDED: small_satoshi_multiplier (for sat trades less than a penny, - # since `@@ 0` is not needed (or any empty tax columns)) - if ($7 == 0) {printf("%.8f", $24 * $5)}; printf OFS + # + # Add new column to calculate the difference between amount and network fee, + # i.e., amount_amount - network_transaction_fee_amount + # - printf $29 OFS # network_hash (txid, if applicable) - printf $30 OFS # network_status (if applicable) + if ($16 > 0) {printf("%.8f", $5 - $16)}; printf OFS # network_transaction_amount_amount + + # + # Advanced Trade: add new column for calculating real value amount + # + # - Works with any pairing (not only fiat) + # + + # Multiply amount_amount by advanced_trade_fill_fill_price + real_value_amount = $5 * $47 + printf("%.8f", real_value_amount); printf OFS # advanced_trade_fill_real_value_amount + + # + # Advanced Trade: add new columns for trade pairing + # + + split($48, pair, "-"); # Pair exists as advanced_trade_fill_product_id + printf pair[1] OFS # advanced_trade_fill_pair_lhs (left-hand side of the pair) + printf pair[2] OFS # advanced_trade_fill_pair_rhs (right-hand side of the pair) + + # + # Advanced Trade: add column for cost-basis (comment2) + # + + # NOTE: sale/proceeds (real_value_amount) will have the fee removed by default + cost_basis=(direction ~ /^IN$/ ? real_value_amount + $50 : real_value_amount) + printf("%.8f", cost_basis); printf OFS # advanced_trade_fill_cost_basis_amount printf direction OFS printf global_subaccount @@ -186,6 +189,127 @@ function __parse() }' FS=, OFS=, } +# +# Notes regarding the parsing process: +# +# 0. Since given headers may be variable, it's not possible to assert a single header. +# +# - `fetch`-provided input CSVs are a beast. Headers are inconsistent and complex. +# +# 1. Create a "selected" header which describes the given header (the header given by `fetch`). +# +# 2. Select all of the columns in said "selected" header. +# +# 3. Join the "universal" header (which contains the entirety of all possible entries) +# +# - Input streams with duplicate header columns will be joined with a new column name (e.g., `col2` instead of `col`). +# +# 4. Do a final select on input stream using the "universal" header. +# +# - This will only select the correct columns while also removing duplicates (e.g., said `col2`) +# +# 5. Have a drink while contemplating life. +# + +function parse() +{ + # + # Create account ID as first column, followed by TXID + # + + # WARNING: Existing filename *MUST* have the following format (where X is account ID): + # + # XXXXXXXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX_transactions.csv + # + + # TODO: fetch/preprocess: Coinbase API provides `account` type `vault`. + # This can be prepended to an account file so it's known that it's a vault + # (versus a wallet, which can also be prepended). + + local -r _account_id="${global_in_filename:0:-17}" + + # + # Reconstruct into "universal" header + # + + # Required schema, per documentation, that appears to exist amongst all entries + declare -g universal_header="id,type,status,amount_amount,amount_currency,native_amount_amount,native_amount_currency,created_at,resource,resource_path" + + # Selected header will always have minimum requirements + declare -g selected_header+="$universal_header" + + # `description` + add_to_headers "description" + + # `network` + add_to_headers "network_status" + add_to_headers "network_network_name" + add_to_headers "network_hash" + add_to_headers "network_transaction_fee_amount" + add_to_headers "network_transaction_fee_currency" + + # `to` + add_to_headers "to_resource" + add_to_headers "to_address" + add_to_headers "to_email" + + # `from` + add_to_headers "from_resource" + add_to_headers "from_resource_path" + add_to_headers "from_id" + add_to_headers "from_name" + + # Remaining SEND related + add_to_headers "cancelable" + add_to_headers "idem" + + # `buy` + add_to_headers "buy_total_amount" + add_to_headers "buy_total_currency" + add_to_headers "buy_subtotal_amount" + add_to_headers "buy_subtotal_currency" + add_to_headers "buy_fee_amount" + add_to_headers "buy_fee_currency" + add_to_headers "buy_id" + add_to_headers "buy_payment_method_name" + + # `sell` + add_to_headers "sell_total_amount" + add_to_headers "sell_total_currency" + add_to_headers "sell_subtotal_amount" + add_to_headers "sell_subtotal_currency" + add_to_headers "sell_fee_amount" + add_to_headers "sell_fee_currency" + add_to_headers "sell_id" + add_to_headers "sell_payment_method_name" + + # `trade` + add_to_headers "trade_fee_amount" + add_to_headers "trade_fee_currency" + add_to_headers "trade_id" + add_to_headers "trade_payment_method_name" + + # `advanced_trade_fill` + # Note: Coinbase appears to always present this in an ordered set + add_to_headers "advanced_trade_fill_fill_price" + add_to_headers "advanced_trade_fill_product_id" + add_to_headers "advanced_trade_fill_order_id" + add_to_headers "advanced_trade_fill_commission" + add_to_headers "advanced_trade_fill_order_side" + + # + # Finalize the "universal" format and parse + # + + # NOTE: prepends account_id to header (this will now be the first column) + + xsv select "$selected_header" "$global_in_path" \ + | join_to_header - "$universal_header" \ + | xsv select "$universal_header" \ + | sed -e "s:^:${_account_id},:g" -e "1 s:^${_account_id},:account_id,:g" \ + | __parse >"$global_out_path" +} + function main() { parse diff --git a/container/src/hledger-flow/accounts/coinbase/coinbase-shared.rules b/container/src/hledger-flow/accounts/coinbase/coinbase-shared.rules index 19c3a19..96fde6e 100644 --- a/container/src/hledger-flow/accounts/coinbase/coinbase-shared.rules +++ b/container/src/hledger-flow/accounts/coinbase/coinbase-shared.rules @@ -15,162 +15,285 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -fields account_id,coinbase_id,type,status_,amount_amount,amount_currency,native_amount_amount,native_amount_currency,description,created_at,updated_at,resource,resource_path,instant_exchange,details_title,details_subtitle,details_header,network_transaction_fee_amount,network_transaction_fee_currency,network_transaction_amount_amount,network_transaction_amount_currency,to_currency,to_address_info_address,native_amount_price,native_network_transaction_fee_amount,native_conversion_fee_amount,advanced_trade_fill_fill_price,advanced_trade_fill_product_id,advanced_trade_fill_order_id,advanced_trade_fill_commission,advanced_trade_fill_order_side,small_satoshi_multiplier,txid,network_status,direction,subaccount +fields account_id,coinbase_id,type,status_,amount_amount,amount_currency,native_amount_amount,native_amount_currency,created_at,resource,resource_path,description,network_status,network_network_name,network_hash,network_transaction_fee_amount,network_transaction_fee_currency,to_resource,to_address,to_email,from_resource,from_resource_path,from_id,from_name,cancelable,idem,buy_total_amount,buy_total_currency,buy_subtotal_amount,buy_subtotal_currency,buy_fee_amount,buy_fee_currency,buy_id,buy_payment_method_name,sell_total_amount,sell_total_currency,sell_subtotal_amount,sell_subtotal_currency,sell_fee_amount,sell_fee_currency,sell_id,sell_payment_method_name,trade_fee_amount,trade_fee_currency,trade_id,trade_payment_method_name,advanced_trade_fill_fill_price,advanced_trade_fill_product_id,advanced_trade_fill_order_id,advanced_trade_fill_commission,advanced_trade_fill_order_side,native_amount_price,native_network_transaction_fee_amount,network_transaction_amount_amount,advanced_trade_fill_real_value_amount,advanced_trade_fill_pair_lhs,advanced_trade_fill_pair_rhs,advanced_trade_fill_cost_basis_amount,direction,subaccount + +# The above fields; in order and in human-readable form: +# +# account_id +# coinbase_id +# type +# status_ +# amount_amount +# amount_currency +# native_amount_amount +# native_amount_currency +# created_at +# resource +# resource_path +# description +# network_status +# network_network_name +# network_hash +# network_transaction_fee_amount +# network_transaction_fee_currency +# to_resource +# to_address +# to_email +# from_resource +# from_resource_path +# from_id +# from_name +# cancelable +# idem +# buy_total_amount +# buy_total_currency +# buy_subtotal_amount +# buy_subtotal_currency +# buy_fee_amount +# buy_fee_currency +# buy_id +# buy_payment_method_name +# sell_total_amount +# sell_total_currency +# sell_subtotal_amount +# sell_subtotal_currency +# sell_fee_amount +# sell_fee_currency +# sell_id +# sell_payment_method_name +# trade_fee_amount +# trade_fee_currency +# trade_id +# trade_payment_method_name +# advanced_trade_fill_fill_price +# advanced_trade_fill_product_id +# advanced_trade_fill_order_id +# advanced_trade_fill_commission +# advanced_trade_fill_order_side +# native_amount_price +# native_network_transaction_fee_amount +# network_transaction_amount_amount +# advanced_trade_fill_real_value_amount +# advanced_trade_fill_pair_lhs +# advanced_trade_fill_pair_rhs +# advanced_trade_fill_cost_basis_amount +# direction +# subaccount date %created_at -# TODO: for trades, use updated_at? - date-format %Y-%m-%d %H:%M:%S -# TODO: any reference to USD should instead be the native currency field - account1 assets:coinbase:%subaccount:%amount_currency amount %amount_amount %amount_currency +# NOTE: server time is UTC, as are the timestamps. description %created_at +0000 -comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id -if %txid [0-1a-z] - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, network_status:%network_status, txid:%txid - -# See preprocess note -if %coinbase_id ^SKIP$ - skip +comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id ################################################################################ # # -# TRANSFER # +# TRANSFER # # # ################################################################################ -# Not needed since canceled won't be accounted for anyway +# `delayed_canceled` should not alter the journal if %type ^delayed_canceled$ skip -# Banking -if %type (^fiat_withdrawal$|^exchange_deposit$) +# ---------------------------------------------------------------------------- # +# Banking # +# ---------------------------------------------------------------------------- # + +if %type ^fiat_deposit$ + account2 equity:coinbase:%subaccount:withdrawal:%amount_currency + +if %type ^fiat_withdrawal$ amount -%amount_amount %amount_currency account2 equity:coinbase:%subaccount:deposit:%amount_currency # ---------------------------------------------------------------------------- # -# Wallet # +# Staking # # ---------------------------------------------------------------------------- # -# Normal wallet sends (default assume equity / send to self. Use custom rules if expense.) +# NOTE: see the Vault TODO regarding why a staking subaccount isn't used +if %type (^staking_transfer$|^unstaking_transfer$) + account2 assets:coinbase:%subaccount:%amount_currency + +# ---------------------------------------------------------------------------- # +# Network # +# ---------------------------------------------------------------------------- # + +# NOTE: use custom rules if income if %type ^send$ -& %details_title Sent +& %direction ^IN$ + account2 equity:coinbase:%subaccount:withdrawal:%amount_currency + +# NOTE: use custom rules if expense or gift +if %type ^send$ +& %direction ^OUT$ amount -%amount_amount %amount_currency account2 equity:coinbase:%subaccount:deposit:%amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, network_status:%network_status + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, idem:%idem, network_status:%network_status, direction:%direction if %type ^send$ -& %details_title Sent -& %to_currency [a-z] -& %to_address_info_address [0-9a-z] - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, to_currency:%to_currency, to_address:%to_address_info_address, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, network_status:%network_status +& %direction (^IN$|^OUT$) +& %network_network_name [a-z] + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, idem:%idem, network_status:%network_status, network_name:%network_network_name, direction:%direction + +# +# On-chain +# if %type ^send$ -& %details_title Sent +& %direction (^IN$|^OUT$) +& %network_hash ([a-z]|[0-9]) + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, network_status:%network_status, network_name:%network_network_name, txid:%network_hash, direction:%direction + +if %type ^send$ +& %direction (^IN$|^OUT$) +& %to_resource ^address$ + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, idem:%idem, network_status:%network_status, network_name:%network_network_name, to_address:%to_address, txid:%network_hash, direction:%direction + +if %type ^send$ +& %direction ^OUT$ & %native_network_transaction_fee_amount [1-9] amount -%network_transaction_amount_amount %amount_currency account3 assets:coinbase:%subaccount:%network_transaction_fee_currency amount3 -%network_transaction_fee_amount %network_transaction_fee_currency @@ %native_network_transaction_fee_amount %native_amount_currency - account4 expenses:coinbase:%subaccount:fees:%native_amount_currency + account4 expenses:coinbase:%subaccount:fees:send:%native_amount_currency amount4 %native_network_transaction_fee_amount %native_amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, to_currency:%to_currency, to_address:%to_address_info_address, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, network_status:%network_status, txid:%txid, taxed_as:SPEND + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, idem:%idem, network_status:%network_status, network_name:%network_network_name, to_address:%to_address, txid:%network_hash, direction:%direction, taxed_as:SPEND comment3 %created_at +0000,SPEND,coinbase,%network_transaction_fee_currency,%network_transaction_fee_amount,%native_amount_currency,%native_network_transaction_fee_amount,FEE -# Marked as comment3 in case user-defined rules use comment2 to SPEND the non-fee amount +# Marked as comment3 in case user-defined rules use comment2 to SPEND/GIFT the non-fee amount + +# +# Off-chain +# -# Normal wallet receives if %type ^send$ -& %details_title Received -& %details_subtitle address - account2 equity:coinbase:%subaccount:withdrawal:%amount_currency +& %direction (^IN$|^OUT$) +& %to_resource ^address$ +& %network_status ^off_blockchain$ + account2 expenses:coinbase:%subaccount:send:%amount_currency + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, idem:%idem, network_status:%network_status, network_name:%network_network_name, to_address:%to_address, direction:%direction + +if %type ^send$ +& %direction ^OUT$ +& %to_resource ^email$ + account2 expenses:coinbase:%subaccount:send_email:%amount_currency + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, idem:%idem, network_status:%network_status, to_resource:%to_resource, direction:%direction, taxed_as:SPEND + comment2 %created_at +0000,SPEND,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,Send via email + +if %type ^send$ +& %direction ^OUT$ +& %to_resource ^email$ +& %to_email [a-z] + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, idem:%idem, network_status:%network_status, to_resource:%to_resource, to_email:%to_email, direction:%direction, taxed_as:SPEND + comment2 %created_at +0000,SPEND,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,Send to %to_email + +# TODO: phone number support # ---------------------------------------------------------------------------- # -# Coinbase Pro # +# Coinbase Vault # # ---------------------------------------------------------------------------- # -# Deposit from Coinbase *to* Pro -if %type ^pro_deposit$ +# - Treatment of subaccounts are from the perspective of the wallet. +# +# - If you have staking income that was deposited into a vault subaccount, +# after you make a full withdrawal, a negative balance will show - but will +# be balanced by a positive amount in the wallet subaccount. +# +# This is less than ideal but, since the only available datastore is a +# linear CSV, it will be unknown where the staking income was deposited to. +# +# See the `preprocess` TODO regarding vault. + +if %type (^vault_withdrawal$|^transfer$) + account2 assets:coinbase:%subaccount:vault:%amount_currency + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, direction:%direction + +# +# From wallet -> To vault +# + +# NOTE: apparently there's no such thing as vault_deposit ... +if %type ^transfer$ +& %direction ^OUT$ + amount -%amount_amount %amount_currency + +# Remove duplicate vault tx (use wallet tx instead) +if %type ^transfer$ +& %direction ^IN$ + skip + +# +# To wallet <- From vault +# + +# Remove duplicate vault tx (use wallet tx instead) +if %type ^vault_withdrawal$ +& %direction ^OUT$ + skip + +# ---------------------------------------------------------------------------- # +# Coinbase Borrow # +# ---------------------------------------------------------------------------- # + +# WARNING: +# +# The Spring 2024 SIWC V2 update has completely removed the ability to know +# which tx is a Coinbase Borrow tx... All that remains is `%type ^tx$` and +# nothing more (other than the schema's "Required" metadata). +# +# As a result, to accurately account for borrow/loan payment, %coinbase_id +# must be manually entered in custom rule on a per borrow/loan transaction. +# +# NOTE: I imagine this is because Coinbase Borrow no longer offers loans. + +# ---------------------------------------------------------------------------- # +# Coinbase Pro # +# ---------------------------------------------------------------------------- # + +# From wallet -> To Pro +if %type (^pro_deposit$|^exchange_deposit$) amount -%amount_amount %amount_currency account2 equity:coinbase-pro:deposit:%amount_currency -# Withdrawal from Pro *to* Coinbase +# To wallet <- From Pro if %type ^pro_withdrawal$ account2 equity:coinbase-pro:withdrawal:%amount_currency # ---------------------------------------------------------------------------- # -# Coinbase Vault # -# ---------------------------------------------------------------------------- # - -# To vault -if %type ^transfer$ - amount -%amount_amount %amount_currency - account2 assets:coinbase:%subaccount:vault:%amount_currency - -# From vault -if %type ^vault_withdrawal$ - account1 assets:coinbase:%subaccount:vault:%amount_currency - amount -%amount_amount %amount_currency - account2 assets:coinbase:%subaccount:%amount_currency - -# TODO: fix(?) pagination and see if this is still needed -# From vault (eliminate double-spend / extra tx "To XXX Wallet") -if %type ^vault_withdrawal$ -& %details_subtitle To - skip - -# TODO: fix(?) pagination and see if this is still needed -# To vault (eliminate double-spend / extra tx "From XXX Wallet") -if %type ^transfer$ -& %details_subtitle From - skip - -# ---------------------------------------------------------------------------- # -# Coinbase Borrow # -# ---------------------------------------------------------------------------- # - -# NOTE: TODO: currently, CSVs do not provide cash borrowing tx!!!! All that exists is loan payback - -if %details_subtitle ^To coinbase-credit-consumer-wallet@coinbase.com$ - account2 expenses:coinbase:%subaccount:receivable:loans:USD - -# ---------------------------------------------------------------------------- # -# Spend (Coinbase Card) # +# Coinbase Card # # ---------------------------------------------------------------------------- # # Rebates ("rewards") + if %type ^send$ -& %details_subtitle (From Card Rewards|From Coinbase Card) +& %description reward +& %from_name (^Coinbase Card$|^Card Rewards$) amount %amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency account2 income:coinbase:%subaccount:card:%native_amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:REBATE + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:REBATE comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,, # Crypto Spend + if %type ^cardspend$ amount -%amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency account2 expenses:coinbase:%subaccount:card:%amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:SPEND - comment2 %created_at +0000,SPEND,coinbase,%amount_currency,%amount_amount,USD,, + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:SPEND + comment2 %created_at +0000,SPEND,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,Coinbase Card -# Crypto Spend (USDC) +# Cash Spend (Native Fiat Wallet) +# TODO: any reference to USD should be a backreferenced %native_amount_currency if %type ^cardspend$ -& %amount_currency ^USDC$ - amount -%amount_amount %amount_currency @@ %amount_amount %native_amount_currency - account2 expenses:coinbase:%subaccount:card:%native_amount_currency - comment2 %created_at +0000,SPEND,coinbase,%amount_currency,%amount_amount,USD,%amount_amount, - -# Cash (USD Wallet) Spend -# NOTE: not a taxable event but SPEND is kept for potential need -if %type ^cardspend$ -& %details_subtitle ^Using Coinbase Card$ & %amount_currency ^USD$ amount -%amount_amount %amount_currency account2 expenses:coinbase:%subaccount:card:%amount_currency - comment details_subtitle:%details_subtitle, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:SPEND - comment2 %created_at +0000,SPEND,coinbase,%amount_currency,%amount_amount,USD,%amount_amount, + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id + comment2 ################################################################################ # # @@ -179,23 +302,37 @@ if %type ^cardspend$ ################################################################################ # Quiz rewards + if %type ^send$ -& %details_subtitle From Coinbase Earn - account2 income:coinbase:%subaccount:%amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:INCOME - comment2 %created_at +0000,INCOME,coinbase,%amount_currency,%amount_amount,%native_amount_currency,,REWARD +& %from_name ^Coinbase Earn$ + account2 income:coinbase:%subaccount:earn:%amount_currency + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:INCOME + comment2 %created_at +0000,INCOME,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,REWARD + +if %type ^earn_payout$ + account2 income:coinbase:%subaccount:earn:%amount_currency + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:INCOME + comment2 %created_at +0000,INCOME,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,REWARD + +if %type ^tx$ +& %description ^Earn Task$ + account2 income:coinbase:%subaccount:earn:%amount_currency + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:INCOME + comment2 %created_at +0000,INCOME,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,REWARD # Interest + if %type ^interest$ account2 income:coinbase:%subaccount:interest:%amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:INCOME - comment2 %created_at +0000,INCOME,coinbase,%amount_currency,%amount_amount,%native_amount_currency,,INTEREST + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:INCOME + comment2 %created_at +0000,INCOME,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,INTEREST # Staking + if %type (^inflation_reward$|^staking_reward$) account2 income:coinbase:%subaccount:staking:%amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:INCOME - comment2 %created_at +0000,INCOME,coinbase,%amount_currency,%amount_amount,%native_amount_currency,,STAKING + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:INCOME + comment2 %created_at +0000,INCOME,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,STAKING ################################################################################ # # @@ -203,120 +340,368 @@ if %type (^inflation_reward$|^staking_reward$) # # ################################################################################ -# Gets rid of erroneous double-tx when dealing with USD Coin and $ on Coinbase Advanced Trading -if %type (^buy$|^sell$|^advanced_trade_fill$) +# Remove effective duplicates (erroneous fiat double-txs) e.g., trading USD-USD +# TODO: any reference to USD should be a backreferenced %native_amount_currency +if %type (^buy$|^sell$) & %amount_currency ^USD$ skip -# ---------------------------------------------------------------------------- # -# Convert / Conversion # -# ---------------------------------------------------------------------------- # - -# "Convert/Conversion" -# NOTE: this appears to be *only* for Coinbase and NOT Coinbase Pro -# NOTE: for whatever reason, for conversions back in 2019 or so, Coinbase never -# dumped to the official USD wallet/account. I don't know where the dump went, -# so a "conversion" account is created which should account for that. An -# accurate fiat balance should now be provided, as a result. -if %type ^trade$ - amount %amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency - account2 equity:coinbase:%subaccount:conversion:%native_amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, native_conversion_fee_amount:%native_conversion_fee_amount, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:BUY - comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,%native_amount_currency,%native_conversion_fee_amount - -if %type ^trade$ -& %details_title Converted from - amount -%amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, native_conversion_fee_amount:%native_conversion_fee_amount, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:SELL - comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,%native_amount_currency,%native_conversion_fee_amount - -if %type ^trade$ -& %details_title Converted from -& %details_subtitle Using USDC Wallet - amount -%amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency - account2 equity:coinbase:%subaccount:conversion:%native_amount_currency - # ---------------------------------------------------------------------------- # # Coinbase Trade # # ---------------------------------------------------------------------------- # -# Trade (buy/sell) but not "convert/conversion" +# +# BUY +# if %type ^buy$ + amount %amount_amount %amount_currency @@ %buy_total_amount %buy_total_currency + 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,, + +if %type ^buy$ +& %buy_total_currency ^USDC$ amount %amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency - account2 assets:coinbase:%subaccount:%native_amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, native_conversion_fee_amount:%native_conversion_fee_amount, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:BUY comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,, -#comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%native_amount_amount,%native_amount_currency,%native_conversion_fee_amount,%native_amount_currency + +if %type ^buy$ +& %buy_fee_amount [1-9] + amount %amount_amount %amount_currency @@ %buy_subtotal_amount %buy_subtotal_currency + account2 assets:coinbase:%subaccount:%buy_subtotal_currency + amount3 -%buy_fee_amount %buy_fee_currency + account3 assets:coinbase:%subaccount:%buy_fee_currency + amount4 %buy_fee_amount %buy_fee_currency + account4 expenses:coinbase:%subaccount:fees:trading:%buy_fee_currency + comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%buy_subtotal_currency,%buy_subtotal_amount,, +# NOTE: cost-basis = proceeds + fee = %buy_subtotal_amount (but do *NOT* include Fee/FeeCurrency column) + +if %type ^buy$ +& %buy_payment_method_name [a-z] + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, buy_id:%buy_id, buy_payment_method_name:%buy_payment_method_name, taxed_as:BUY + +if %type ^buy$ +& %buy_fee_amount [1-9] +& %buy_payment_method_name (bank|checking|credit|debit|saving) + amount5 %buy_total_amount %buy_total_currency + account5 assets:coinbase:%subaccount:%buy_total_currency + amount6 -%buy_total_amount %buy_total_currency + account6 equity:coinbase:%subaccount:withdrawal:%buy_total_currency + +if %type ^buy$ +& %buy_fee_amount ^[^1-9]*$ +& %buy_payment_method_name (bank|checking|credit|debit|saving) + amount3 %buy_total_amount %buy_total_currency + account3 assets:coinbase:%subaccount:%buy_total_currency + amount4 -%buy_total_amount %buy_total_currency + account4 equity:coinbase:%subaccount:withdrawal:%buy_total_currency + +# +# SELL +# if %type ^sell$ + amount -%amount_amount %amount_currency @@ %sell_total_amount %sell_total_currency + 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,, + +if %type ^sell$ +& %sell_total_currency ^USDC$ amount -%amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency - account2 assets:coinbase:%subaccount:%native_amount_currency - comment details_title:%details_title, details_subtitle:%details_subtitle, details_header:%details_header, type:%type, status:%status_, native_conversion_fee_amount:%native_conversion_fee_amount, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:SELL comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,, -#comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%native_amount_amount,%native_amount_currency,%native_conversion_fee_amount,%native_amount_currency -# TODO: not very templatable, unique to each user's bank name -if %type (^buy$|^fiat_deposit$) -& %details_subtitle (Bank|Checking|Credit|Debit|Saving) - account2 equity:coinbase:%subaccount:withdrawal:%native_amount_currency +if %type ^sell$ +& %sell_fee_amount [1-9] + amount -%amount_amount %amount_currency @@ %sell_subtotal_amount %sell_subtotal_currency + account2 assets:coinbase:%subaccount:%sell_subtotal_currency + amount3 -%sell_fee_amount %sell_fee_currency + account3 assets:coinbase:%subaccount:%sell_fee_currency + amount4 %sell_fee_amount %sell_fee_currency + account4 expenses:coinbase:%subaccount:fees:trading:%sell_fee_currency + comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%sell_total_currency,%sell_total_amount,, +# NOTE: cost-basis = sale minus fee = %sell_total_amount (but do *NOT* include Fee/FeeCurrency column) -if %type (^sell$|^fiat_withdrawal$) -& %details_subtitle (Bank|Checking|Credit|Debit|Saving) - account2 equity:coinbase:%subaccount:deposit:%native_amount_currency +if %type ^sell$ +& %sell_payment_method_name [a-z] + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, sell_id:%sell_id, sell_payment_method_name:%sell_payment_method_name, taxed_as:SELL -# ---------------------------------------------------------------------------- # -# Coinbase Advanced Trading # -# ---------------------------------------------------------------------------- # +if %type ^sell$ +& %sell_fee_amount [1-9] +& %sell_payment_method_name (bank|checking|credit|debit|saving) + amount5 -%sell_total_amount %sell_total_currency + account5 assets:coinbase:%subaccount:%sell_total_currency + amount6 %sell_total_amount %sell_total_currency + account6 equity:coinbase:%subaccount:deposit:%sell_total_currency -# BUY +if %type ^sell$ +& %sell_fee_amount ^[^1-9]*$ +& %sell_payment_method_name (bank|checking|credit|debit|saving) + amount3 -%sell_total_amount %sell_total_currency + account3 assets:coinbase:%subaccount:%sell_total_currency + amount4 %sell_total_amount %sell_total_currency + account4 equity:coinbase:%subaccount:deposit:%sell_total_currency -if %type ^advanced_trade_fill$ -& %advanced_trade_fill_order_side ^buy$ - account2 assets:coinbase:%subaccount:%native_amount_currency - account3 assets:coinbase:%subaccount:%native_amount_currency - amount3 -%advanced_trade_fill_commission %native_amount_currency - account4 expenses:coinbase:%subaccount:fees:trading:%native_amount_currency - amount4 %advanced_trade_fill_commission %native_amount_currency - comment details_title:%details_title, 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_commission:%advanced_trade_fill_commission, advanced_trade_fill_order_side:%advanced_trade_fill_order_side, taxed_as:BUY +# +# Convert/Conversion +# -# Satoshis large enough to produce > 0 native amount -if %type ^advanced_trade_fill$ -& %advanced_trade_fill_order_side ^buy$ -& %native_amount_amount [1-9] - amount %amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency - comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,%native_amount_currency,%advanced_trade_fill_commission - -# Satoshis small enough to produce $0.00 native amount (thanks, Coinbase!) -if %type ^advanced_trade_fill$ -& %advanced_trade_fill_order_side ^buy$ -& %small_satoshi_multiplier [1-9] - amount %amount_amount %amount_currency @@ %small_satoshi_multiplier %native_amount_currency - comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%small_satoshi_multiplier,%native_amount_currency,%advanced_trade_fill_commission +# The conversion is treated as: +# +# token1->fiat->token2 i.e., sell->fiat->buy +# +# but the cost-basis should be the same as a straight conversion. +# +# +# Here is a mockup example of an API-provided conversion of USDC->ETH: +# +# ----------------- +# On the SELL side: +# ----------------- +# +# ( +# [id] => XXXXXXXX-XXXX-XXXX-XXXX-COINBASE_ID1 +# [type] => trade +# [status] => completed +# [amount] => Array +# ( +# [amount] => -100.000000 +# [currency] => USDC +# ) +# +# [native_amount] => Array +# ( +# [amount] => -100.00 +# [currency] => USD +# ) +# +# [created_at] => 2020-02-02T02:02:02Z +# [resource] => transaction +# [resource_path] => /v2/accounts/XXXXXXXX-XXXX-XXXX-XXXX-XXACCOUNTID1/transactions/XXXXXXXX-XXXX-XXXX-XXXX-XCOINBASED1 +# [trade] => Array +# ( +# [fee] => Array +# ( +# [amount] => 0.00549387 +# [currency] => ETH +# ) +# +# [id] => XXXXXXXX-XXXX-XXXX-SAME-TRADEID00001 +# [payment_method_name] => USDC Wallet +# ) +# +# ) +# +# -------------------- +# And on the BUY side: +# -------------------- +# +# ( +# [id] => XXXXXXXX-XXXX-XXXX-XXXX-XCOINBASEID2 +# [type] => trade +# [status] => completed +# [amount] => Array +# ( +# [amount] => 0.12345678 +# [currency] => ETH +# ) +# +# [native_amount] => Array +# ( +# [amount] => 95.55 +# [currency] => USD +# ) +# +# [created_at] => 2020-02-02T02:02:04Z +# [resource] => transaction +# [resource_path] => /v2/accounts/XXXXXXXX-XXXX-XXXX-XXXX-XXACCOUNTID2/transactions/XXXXXXXX-XXXX-XXXX-XXXX-XCOINBASEID2 +# [trade] => Array +# ( +# [fee] => Array +# ( +# [amount] => 0.00549387 +# [currency] => ETH +# ) +# +# [id] => XXXXXXXX-XXXX-XXXX-SAME-TRADEID00001 +# [payment_method_name] => USDC Wallet +# ) +# +# ) +# +# -------------------- +# Will be produced as: +# -------------------- +# +# 2020-02-02 2020-02-02 02:02:02 +0000 ; type:trade, status:completed, account_id:XXXXXXXX-XXXX-XXXX-XXXX-XXACCOUNTID1, coinbase_id:XXXXXXXX-XXXX-XXXX-XXXX-XCOINBASEID1, trade_id:XXXXXXXX-XXXX-XXXX-SAME-TRADEID00001, taxed_as:SELL +# assets:coinbase:platform:USDC -100.000000 USDC @@ 100.00 USD +# equity:coinbase:platform:conversion:USD 100.00 USD ; 2020-02-02 02:02:02 +0000,SELL,coinbase,USDC,100.000000,USD,100.00,, +# +# 2020-02-02 2020-02-02 02:02:04 +0000 ; type:trade, status:completed, account_id:XXXXXXXX-XXXX-XXXX-XXXX-XXACCOUNTID2, coinbase_id:XXXXXXXX-XXXX-XXXX-XXXX-XCOINBASEID2, trade_id:XXXXXXXX-XXXX-XXXX-SAME-TRADEID00001, taxed_as:BUY +# assets:coinbase:platform:ETH 0.12345678 ETH @@ 95.55 USD +# equity:coinbase:platform:conversion:USD -95.55 USD ; 2020-02-02 02:02:04 +0000,BUY,coinbase,ETH,0.12345678,USD,95.55,, +# +# NOTE: regarding cost-basis, there appears to be no `cost/proceeds +/- fee` but rather cost/proceeds at a wider spread. +# +# -------------------- +# The rationale being: +# -------------------- +# +# - This API call doesn't provide a "conversion from/to" or equivalent nor +# does it provide any associated market price or fee percentage. +# +# However, total market price *could* be inferred via the non-stable crypto: +# +# %native_amount_amount / (%amount_amount + %trade_fee_amount) +# +# (assuming that %trade_fee_currency is identical to %amount_currency) +# +# but this would need to be supported by a `preprocess` test of: +# +# %payment_method_name +# +# to create new column with token string: +# +# "USDC" for "USDC Wallet" +# +# as provided by the above BUY-side example because %payment_method_name is +# the only available evidence of a "conversion from". +# +# This would create a single tx; thus dropping the other side of trade. +# +# However, this is avoided because: +# +# * Not all trades have fees. +# * Dropping a non-duplicate tx is confusing and appears not to be needed. +# +# - An interested observation: you'll notice that the conversion timestamps +# aren't identical when they *should* be (or, they could be, server-side, +# retroactively). +# +# This difference could be a result of any number of factors including +# backend delay when updating accounts. Another factor could imply that the +# trade being done behind the scenes is actually two executions of higher +# liquidity pairings: token1->fiat / fiat->token2 and the "conversion" is +# simply an external simplification of what is an internal set of trades. +# +# In other words, the trade may not actually be a token1->token2 conversion +# but rather a sell->fiat->buy transaction. In terms of accounting, this is +# not only more accurate but I belive to also be more appropriate. +# +# NOTE: crypto-crypto pairings in Advanced Trade have identical timestamps. +# +# - Because the given %native_amount_amount includes the fee in the cost-basis, +# and there is no total on the buy side (only subtotal), %trade_fee_amount +# should not be added as it A) it's not needed B) there's no market price +# given from this Coinbase API call to deteremine how they define the total +# (see notes above). +# +# - The drawback to using an equity conversion account in this instance is, +# because of the lack of information provided by the API, the account will +# almost certainly not be 0 after all conversions are completed. +# +# This shouldn't be an issue though because A) the conversion account is not +# meant to be treated as a real equity account and B) %trade_fee_id can be +# used as a correlating tool to track the conversion. +# +# Alternate solution: don't use Convert; use Buy/Sell or Advanced Trade. +# # SELL +# -if %type ^advanced_trade_fill$ -& %advanced_trade_fill_order_side ^sell$ - account2 assets:coinbase:%subaccount:%native_amount_currency - account3 assets:coinbase:%subaccount:%native_amount_currency - amount3 -%advanced_trade_fill_commission %native_amount_currency - account4 expenses:coinbase:%subaccount:fees:trading:%native_amount_currency - amount4 %advanced_trade_fill_commission %native_amount_currency - comment details_title:%details_title, 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_commission:%advanced_trade_fill_commission, advanced_trade_fill_order_side:%advanced_trade_fill_order_side, taxed_as:SELL - -# Satoshis large enough to produce > 0 native amount -if %type ^advanced_trade_fill$ -& %advanced_trade_fill_order_side ^sell$ -& %native_amount_amount [1-9] +if %type ^trade$ +& %direction ^OUT$ amount -%amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency - comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,%native_amount_currency,%advanced_trade_fill_commission + account2 equity:coinbase:%subaccount:conversion:%native_amount_currency + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:SELL + comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,, -# Satoshis small enough to produce $0.00 native amount +if %type ^trade$ +& %direction ^OUT$ +& %trade_id ([a-z]|[0-9]) + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, trade_id:%trade_id, taxed_as:SELL + +# +# BUY +# + +if %type ^trade$ +& %direction ^IN$ + amount %amount_amount %amount_currency @@ %native_amount_amount %native_amount_currency + account2 equity:coinbase:%subaccount:conversion:%native_amount_currency + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, taxed_as:BUY + comment2 %created_at +0000,BUY,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%native_amount_amount,, + +if %type ^trade$ +& %direction ^IN$ +& %trade_id ([a-z]|[0-9]) + comment type:%type, status:%status_, account_id:%account_id, coinbase_id:%coinbase_id, trade_id:%trade_id, taxed_as:BUY + +# ---------------------------------------------------------------------------- # +# Coinbase Advanced Trade # +# ---------------------------------------------------------------------------- # + +# WARNING: their native amount is *not* the native amount of the fill price. + +# +# BUY / SELL +# + +# Sometimes they provide 0 %amount_amount and 0 %native_amount_amount but will +# also provide a commission in the same tx... Fortunately, since the fees are +# already caught in the below sections, these 0 value records can be skipped. +if %type ^advanced_trade_fill$ +& %amount_amount ^[^1-9]*$ +& %native_amount_amount ^[^1-9]*$ + skip + +if %type ^advanced_trade_fill$ +& %advanced_trade_fill_order_side (^buy$|^sell$) +& %advanced_trade_fill_real_value_amount [1-9] + account1 assets:coinbase:%subaccount:%advanced_trade_fill_pair_lhs + account2 assets:coinbase:%subaccount:%advanced_trade_fill_pair_rhs + amount3 -%advanced_trade_fill_commission %advanced_trade_fill_pair_rhs + account3 assets:coinbase:%subaccount:%advanced_trade_fill_pair_rhs + amount4 %advanced_trade_fill_commission %advanced_trade_fill_pair_rhs + account4 expenses:coinbase:%subaccount:fees:trading:%advanced_trade_fill_pair_rhs + +# +# BUY +# + +# Remove what is effectively a duplicate +if %type ^advanced_trade_fill$ +& %advanced_trade_fill_order_side ^buy$ +& %advanced_trade_fill_real_value_amount [1-9] +& %direction ^OUT$ + skip + +if %type ^advanced_trade_fill$ +& %advanced_trade_fill_order_side ^buy$ +& %advanced_trade_fill_real_value_amount [1-9] +& %direction ^IN$ + amount %amount_amount %advanced_trade_fill_pair_lhs @@ %advanced_trade_fill_real_value_amount %advanced_trade_fill_pair_rhs + 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,, + +# +# SELL +# + +# Remove what is effectively a duplicate if %type ^advanced_trade_fill$ & %advanced_trade_fill_order_side ^sell$ -& %small_satoshi_multiplier [1-9] - amount -%amount_amount %amount_currency @@ %small_satoshi_multiplier %native_amount_currency - comment2 %created_at +0000,SELL,coinbase,%amount_currency,%amount_amount,%native_amount_currency,%small_satoshi_multiplier,%native_amount_currency,%advanced_trade_fill_commission +& %advanced_trade_fill_real_value_amount [1-9] +& %direction ^IN$ + skip + +if %type ^advanced_trade_fill$ +& %advanced_trade_fill_order_side ^sell$ +& %advanced_trade_fill_real_value_amount [1-9] +& %direction ^OUT$ + amount -%amount_amount %advanced_trade_fill_pair_lhs @@ %advanced_trade_fill_real_value_amount %advanced_trade_fill_pair_rhs + 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,, # vim: sw=2 sts=2 si ai et