Why are buys mapping to sells?

This commit is contained in:
Peter Selby 2018-10-07 22:33:25 -07:00
parent 90aefc0f29
commit 0aff915bb8
7 changed files with 408 additions and 130 deletions

View File

@ -17,4 +17,5 @@
[org.clojure/data.json "0.2.6"]
[orchestra "2018.08.19-1"]
[org.fudo.utils "0.0.2"]
[org.clojure/core.match "0.3.0-alpha5"]])
[org.clojure/core.match "0.3.0-alpha5"]
[org.clojure/test.check "0.9.0"]])

View File

@ -12,6 +12,7 @@ Basic idea:
- BCH and BTG count as income: all deposits should be multiplied by the day-1
value and counted as income--everything else counts as capital gains.
- BCH traded on the first day at $277, according to bitcoin.tax.
- Apparently BTG opened at $479! Huh.
- Uhh, some Coinbase "sends" are really sales. Look for "Sent to Coinbase".
- Coinbase sent to Vault of Satoshi, where they were sold. Should be
considered sold...but I don't know how to calculate the tax. Anyway, I
@ -22,10 +23,17 @@ Basic idea:
- 18as544Wxg3ZScCZo7fjWSV4JGUhsLv6AR
- 18as544Wxg3ZScCZo7fjWSV4JGUhsLv6AR
- 1GxxcLKjqHD1wZWoZ3KNgiqjQonGDpZS4
- 1G3GM7izNsegWnxTB3RbuXAkC9YZxfeYP1
- Definitely gifts:
- 1L4kschshGtKJPM3T5RXhBJYtEHhFa3xq (Omar)
- 18Co5639x3Dp1EExfbgEzBKXYn329cwYKt
- 17XNrTEqMe4PkUcCFVaihW7Yu2gFasaKxM
- 15My27F2QLkLrZ3bqR8SSUHHrCKwQyGeLd
- 1Ae5kmNpAweTDGGguQCK55G84LDcHArJYn
- 1G3GM7izNsegWnxTB3RbuXAkC9YZxfeYP1
- 18Co5639x3Dp1EExfbgEzBKXYn329cwYKt
- Purchases:r
- 1EjPaprQpLmPPJnfECRg9jK9v7eyEYkBNH
- 1Nc59oJunufbRe6uhqByJ1Kop19tqTQ1hr
- Vircurex:
- 1FripmTRgNFx6M2C7udeDWKYW8wfR5vuUU
- 1FripmTRgNFx6M2C7udeDWKYW8wfR5vuUU

View File

@ -1,12 +1,17 @@
(ns taxer.core
(:require [clojure.spec.alpha :as s]))
(:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]))
(s/def ::currency #{:usd :bch :btc :eth :ada :btg})
(s/def ::from-currency ::currency)
(s/def ::to-currency ::currency)
(s/def ::datetime (fn [obj] (instance? java.util.Date obj)))
(def datetime-gen (gen/fmap #(java.util.Date.)
(s/gen (s/int-in 0 4133923200000))))
(s/def ::datetime (s/inst-in #inst "1990" #inst "2100"))
(s/def ::timestamp integer?)
@ -15,7 +20,7 @@
(re-matches #"^[0-9a-f]{40}$" obj))))
(s/def ::id ::sha1-sum)
(s/def ::txn-type #{:buy :sell :send :receive :trade :fee})
(s/def ::txn-type #{:buy :sell :send :trade :fee :income :deposit})
(s/def ::from-account ::sha1-sum)
(s/def ::to-account ::sha1-sum)
@ -29,7 +34,8 @@
::currency
::txn-type
::account
::currency])
::currency
::datetime])
(defmulti txn-type ::txn-type)
(defmethod txn-type :buy [_]
(s/keys :req (concat txn-common-req
@ -52,10 +58,55 @@
(defmethod txn-type :fee [_]
(s/keys :req (concat txn-common-req [])
:opt [::notes]))
(defmethod txn-type :deposit [_]
(s/keys :req (concat txn-common-req [])
:opt [::notes]))
(s/def ::txn (s/multi-spec txn-type ::txn-type))
(s/def ::txns (s/coll-of ::txn))
(defn nonnegative-float? [obj]
(and (float? obj) (>= obj 0)))
(defn positive-float? [obj]
(and (float? obj) (> obj 0)))
(defn sell? [obj]
(= :sell (::txn-type obj)))
(defn acquisition? [obj]
(or (= :income (::txn-type obj))
(= :buy (::txn-type obj))))
(defn fully-sourced? [txn]
(< (* 0.001 (::amount txn))
(Math/abs (- (reduce + (map ::amount (::consumed txn)))
(::amount txn)))))
(s/def ::sell (s/and ::txn sell?))
(s/def ::acquisition (s/and ::txn acquisition?))
(s/def ::consumed (s/coll-of (s/keys :amount positive-float?
:txn ::txn)))
(s/def ::unconsumed nonnegative-float?)
(s/def ::sourced-sell
(s/and (s/keys :req [::consumed])
fully-sourced?))
(s/def ::unsourced-sell (s/keys :req [::consumed]))
(s/def ::unconsumed-buy (s/keys :req [::unconsumed-amount]))
(s/def ::open number?)
(s/def ::close number?)
(s/def ::high number?)
(s/def ::low number?)
(s/def ::volume-from number?)
(s/def ::volume-to number?)
(s/def ::from-currency ::currency)
(s/def ::to-currency ::currency)
(s/def ::date ::datetime)
(s/def ::pricemap
(s/map-of ::timestamp
(s/keys :req-un [::date

View File

@ -1,8 +1,12 @@
(ns taxer.executor
(:require [taxer.importer :as import]
[taxer.ops :as op]
[taxer.core :as tax]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]))
[clojure.spec.alpha :as s]
[orchestra.spec.test :as st]))
(st/instrument)
(defn gdax []
(import/merge-gdax-transactions
@ -37,9 +41,6 @@
(import/merge-bittrex-transactions (rates)
(import/load-bittrex-csv "resources/bittrex-fullOrders.csv")))
(defn all-txns []
(concat (gdax) (coinbase) (bittrex)))
(defn project [ks m]
(into {} (map (fn [k] [k (get m k)]) ks)))
@ -53,19 +54,208 @@
(defn update-field [field f txns]
(map (fn [txn] (update txn field f)) txns))
(defn build-tx [amount currency timestamp value-per txn-type]
(import/common->local {::tax/amount amount
::tax/currency currency
::tax/datetime timestamp
::tax/usd-amount (* value-per amount)
::tax/txn-type txn-type
::tax/txn-id "injected"}))
(def mk-date (import/parse-date "y-M-d"))
(defn bcc->bch [txns]
(map (fn [tx]
(if (= (::tax/currency tx) :bcc)
(assoc tx ::tax/currency :bch)
tx))
txns))
(defn insert-magic-txns [txns]
(-> txns
;; From the POV of taxes, these are income
(conj (build-tx 125.1234564
:bch
(mk-date "2017-08-17")
277
:income))
(conj (build-tx 125.1234564
:btg
(mk-date "2017-11-12")
479
:income))
;; But they have to be discoverable to calculate capital gains
(conj (build-tx 125.1234564
:bch
(mk-date "2017-08-17")
277
:buy))
(conj (build-tx 125.1234564
:btg
(mk-date "2017-11-12")
479
:buy))))
(defn modify-where [txns pred mod]
(map (fn [tx] (if (pred tx) (mod tx) tx)) txns))
(defn coinbase-sends-to-sells [txns]
(modify-where txns
(fn [tx] (re-matches #"^Sent to Coinbase" (or (::tax/notes tx) "")))
(fn [tx] (assoc tx ::tax/txn-type :sell))))
(defn coinbase-switch-type [txns addresses type memo]
(modify-where txns
(fn [tx]
(some (fn [address] (.contains (or (::tax/notes tx) "") address))
addresses))
(fn [tx] (assoc tx
::tax/txn-type type
::tax/notes (format "%s - %s"
(::tax/notes tx)
memo)))))
(defn coinbase-manual-modify [txns]
(-> txns
(coinbase-switch-type ["1AF6ZPez9NFc7nUfJtwBgod6aWcYaDTi3F"
"1LTrqFApTvfSn415Rjs1ukCVN149zADxxJ"
"1LTrqFApTvfSn415Rjs1ukCVN149zADxxJ"
"18as544Wxg3ZScCZo7fjWSV4JGUhsLv6AR"
"18as544Wxg3ZScCZo7fjWSV4JGUhsLv6AR"
"1GxxcLKjqHD1wZWoZ3KNgiqjQonGDpZS4"]
:sell
"Sent to Vault of Satoshi, sold from there, taxes paid previously")
(coinbase-switch-type ["1L4kschshGtKJPM3T5RXhBJYtEHhFa3xq"
"18Co5639x3Dp1EExfbgEzBKXYn329cwYKt"
"17XNrTEqMe4PkUcCFVaihW7Yu2gFasaKxM"
"15My27F2QLkLrZ3bqR8SSUHHrCKwQyGeLd"
"1Ae5kmNpAweTDGGguQCK55G84LDcHArJYn"
"1G3GM7izNsegWnxTB3RbuXAkC9YZxfeYP1"
"18Co5639x3Dp1EExfbgEzBKXYn329cwYKt"]
:sell
"Gifts or payments sent to friends & family")
(coinbase-switch-type ["1EjPaprQpLmPPJnfECRg9jK9v7eyEYkBNH"
"1Nc59oJunufbRe6uhqByJ1Kop19tqTQ1hr"]
:sell
"Purchases (eg. Steam)")))
(defn all-txns []
(sort-by ::tax/datetime
(-> (concat (gdax) (coinbase) (bittrex))
(bcc->bch)
(insert-magic-txns)
(coinbase-sends-to-sells)
(coinbase-manual-modify))))
(defn currencies [txns]
(distinct (map ::tax/currency txns)))
(defn filter-currency [txns curr]
(sort-by ::tax/datetime
(op/filter-on-value txns ::tax/currency curr)))
(defn all-sells [txns]
(sort-by ::tax/datetime
(op/filter-on-value txns ::tax/txn-type :sell)))
(defn all-buys [txns]
(sort-by ::tax/datetime
(op/filter-on-value txns
(fn [tx]
(or (= (::tax/txn-type tx) :buy)
(= (::tax/txn-type tx) :income))))))
(defrecord ConsumedBuy [amount txn])
(defn sourced-balance [sell]
(reduce + 0 (map :amount (::tax/consumed sell))))
(defn unsourced-remaining [sell]
(- (::tax/amount sell) (sourced-balance sell)))
;; Okay, what to do, what to do...
;; - Take a list of all buys and all sells
;; - Starting with the first sell, consume as many 'buys' as necessary to cover
;; - From each, subtract the relevant amount...returning both
;; - Move on to the next sell
(defn consume-buy [sell buy]
(println (format "Taking %s (%s) from %s (%s)"
(unsourced-remaining sell)
(::tax/id sell)
(::tax/unconsumed-amount buy)
(::tax/id buy)))
(let [consumed-amount (min (unsourced-remaining sell)
(::tax/unconsumed-amount buy))]
(println (format "%s - consuming %s of %s, %s remaining"
(::tax/id sell)
consumed-amount
(unsourced-remaining sell)
(- (unsourced-remaining sell) consumed-amount)))
[(update sell ::tax/consumed
(fn [consumed-list]
(conj consumed-list (->ConsumedBuy consumed-amount buy))))
(update buy ::tax/unconsumed-amount
(fn [amt] (- amt consumed-amount)))]))
(s/fdef consume-buy
:args (s/cat :sell ::tax/unsourced-sell :buy ::tax/unconsumed-buy)
:ret (s/cat :sell ::tax/sourced-sell :buy ::tax/consumed-buy))
(defn consume-buys [sell unconsumed-buys]
(let [buy (first unconsumed-buys)
remaining-buys (rest unconsumed-buys)
[sourced-sell consumed-buy] (consume-buy sell buy)]
(if (<= 0 (unsourced-remaining sourced-sell))
;; We've sourced this full sell--return!
;; Include the remainder of this buy, there's probably some left
[sourced-sell (cons consumed-buy remaining-buys)]
;; There's a remaining balance that needs sourcing, and the current buy is
;; spent--iterate!
(consume-buys sourced-sell remaining-buys))))
(defn source-sells [raw-sells raw-buys]
"Given a list of sells and a list of buys, map sells to source buys."
(let [buys (map (fn [row] (assoc row ::tax/unconsumed-amount (::tax/amount row)))
raw-buys)
sells (map (fn [row] (assoc row ::tax/consumed [])) raw-sells)]
(loop [sell (first sells)
unsourced-sells (rest sells)
unconsumed-buys buys
sourced-sells []]
(if (empty? unsourced-sells)
(let [[sourced-sell _] (consume-buys sell unconsumed-buys)]
(conj sourced-sells sourced-sell))
(let [[sourced-sell remaining-buys] (consume-buys sell unconsumed-buys)]
(recur (first unsourced-sells)
(rest unsourced-sells)
remaining-buys
(conj sourced-sells sourced-sell)))))))
(s/fdef source-sells
:args (s/cat :raw-sells ::tax/sell :raw-buys ::tax/acquisition)
:ret (s/coll-of ::tax/sourced-sell)
:fn #(and (= (-> % :args :raw-sells count)
(-> % :ret count))))
(defn source-sells-by-currency [txns currency]
(let [currency-txns (filter-currency txns currency)]
(source-sells (all-sells currency-txns) (all-buys currency-txns))))
(defn print-as-table [txns]
(let [restrict-fields (partial project [::tax/timestamp
::tax/txn-id
(let [restrict-fields (partial project [::tax/datetime
;;::tax/txn-id
::tax/txn-type
::tax/amount
::tax/currency
::tax/usd-amount
::tax/id
;;::tax/notes
;;::tax/account
;;::tax/id
::tax/notes
])]
(clojure.pprint/print-table
(->> txns
(sort-by ::tax/timestamp)
(sort-by ::tax/datetime)
(update-field ::tax/amount round-str)
(update-field ::tax/usd-amount round-str)
(update-field ::tax/timestamp format-date)

View File

@ -1,5 +1,7 @@
(ns taxer.importer
(:require [taxer.core :as tax]
[taxer.ops :as op]
[org.fudo.utils.sorted :as sort]
[clojure.core.match :refer [match]]
[clojure.data.csv :as csv]
@ -13,6 +15,9 @@
(defn file? [obj]
(instance? java.io.File obj))
(defn path-url? [obj]
(instance? java.net.URL obj))
(s/def ::header keyword?)
(defn headify [str]
@ -30,7 +35,7 @@
(s/fdef make-row
:args (s/cat :headers (s/coll-of ::header)
:fields (s/coll-of string?))
:ret (s/map-of ::tax/header string?))
:ret (s/map-of ::header string?))
(defn load-csv [file]
(with-open [reader (io/reader file)]
@ -38,46 +43,27 @@
headers (map headify (first lines))]
(map (partial make-row headers) (rest lines)))))
(s/fdef load-csv
:args (s/cat :file file?)
:ret (s/coll-of (s/map-of ::tax/header string?)))
(defn alter-field [field f]
(fn [row]
(if (get row field)
(update row field (fn [value] (f value)))
row)))
(defn add-field [field f]
(fn [row]
(assoc row field (f row))))
(defn split-field [field header-generators & [passed-sep]]
(let [header-pairs (partition 2 header-generators)
sep (or passed-sep #" ")]
(fn [row]
(into row
(map (fn [[header generator] value] {header (generator value)})
header-pairs
(str/split (field row) sep))))))
:args (s/cat :file path-url?)
:ret (s/coll-of (s/map-of ::header string?)))
(defn parse-date [date-format]
(let [date-format (java.text.SimpleDateFormat. date-format)]
(fn [date-str] (.parse date-format date-str))))
(defn load-coinbase-csv [file]
(map (comp (alter-field :timestamp (parse-date "M/d/y"))
(alter-field :transaction_type headify)
(alter-field :asset headify)
(alter-field :quantity_transacted bigdec)
(alter-field :usd_spot_price_at_transaction bigdec)
(alter-field :usd_amount_transacted bigdec))
(map (comp (op/alter-field :timestamp (parse-date "M/d/y"))
(op/alter-field :transaction_type headify)
(op/alter-field :asset headify)
(op/alter-field :quantity_transacted bigdec)
(op/alter-field :usd_spot_price_at_transaction bigdec)
(op/alter-field :usd_amount_transacted bigdec))
(load-csv file)))
(defn load-gdax-csv [file]
(map (comp (alter-field :type headify)
(alter-field :date (parse-date "y-M-d H:m:s"))
(split-field :amount [:txn_amount bigdec :txn_currency headify])
(split-field :balance [:balance_amount bigdec :balance_currency headify]))
(map (comp (op/alter-field :type headify)
(op/alter-field :date (parse-date "y-M-d H:m:s"))
(op/split-field :amount [:txn_amount bigdec :txn_currency headify])
(op/split-field :balance [:balance_amount bigdec :balance_currency headify]))
(load-csv file)))
(defn sha1-sum [s]
@ -99,7 +85,7 @@
;; :notes)
(defn common->local [txn]
(let [id-fields [::tax/timestamp
(let [id-fields [::tax/datetime
::tax/txn-type
::tax/amount
::tax/currency
@ -110,7 +96,7 @@
(defn common-coinbase->local [txn]
(-> txn
(assoc ::tax/timestamp (:timestamp txn)
(assoc ::tax/datetime (:timestamp txn)
::tax/usd-amount (:usd_amount_transacted txn)
::tax/account :coinbase
::tax/amount (:quantity_transacted txn)
@ -131,7 +117,7 @@
(defmethod coinbase->local :receive [txn]
(-> txn
(assoc ::tax/txn-type :receive)
(assoc ::tax/txn-type :deposit)
(common-coinbase->local)))
(defmethod coinbase->local :send [txn]
@ -150,9 +136,9 @@
(defn common-gdax->local [txn]
(-> txn
(assoc ::tax/txn-id (:txid txn)
::tax/timestamp (:date txn)
::tax/account :gdx
::tax/amount (:txn_amount txn)
::tax/datetime (:date txn)
::tax/account :gdax
::tax/amount (.abs (:txn_amount txn))
::tax/currency (:txn_currency txn))
(common->local)))
@ -209,46 +195,70 @@
(defn load-bittrex-rates [file]
(let [take-first-3 (fn [sym] (-> sym (subs 0 3) str/lower-case keyword))
take-rest (fn [sym] (-> sym (subs 3) str/lower-case keyword))]
(group-by (fn [measure] (.getTime (:date measure)))
(map (comp (alter-field :date (parse-date "y-M-d K-a"))
(add-field :from-currency (fn [row] (take-first-3 (:symbol row))))
(add-field :to-currency (fn [row] (take-rest (:symbol row))))
(alter-field :open bigdec)
(alter-field :high bigdec)
(alter-field :low bigdec)
(alter-field :close bigdec)
(alter-field :volume_from bigdec)
(alter-field :volume_to bigdec))
(into {}
(map (fn [measure] {(.getTime (:date measure)) measure}))
(map (comp (op/alter-field :date (parse-date "y-M-d K-a"))
(op/add-field :from-currency (fn [row] (take-first-3 (:symbol row))))
(op/add-field :to-currency (fn [row] (take-rest (:symbol row))))
(op/alter-field :open bigdec)
(op/alter-field :high bigdec)
(op/alter-field :low bigdec)
(op/alter-field :close bigdec)
(op/alter-field :volume_from bigdec)
(op/alter-field :volume_to bigdec))
(load-csv file)))
#_(group-by (fn [measure] (.getTime (:date measure)))
(map (comp (op/alter-field :date (parse-date "y-M-d K-a"))
(op/add-field :from-currency (fn [row] (take-first-3 (:symbol row))))
(op/add-field :to-currency (fn [row] (take-rest (:symbol row))))
(op/alter-field :open bigdec)
(op/alter-field :high bigdec)
(op/alter-field :low bigdec)
(op/alter-field :close bigdec)
(op/alter-field :volume_from bigdec)
(op/alter-field :volume_to bigdec))
(load-csv file)))))
(s/fdef bittrex-load-prices
:args (s/cat :file file?)
:args (s/cat :file path-url?)
:ret ::tax/pricemap)
(defn load-coinmarketcap-rates [file from to]
(group-by (fn [measure] (.getTime (:date measure)))
(map (comp (alter-field :date (parse-date "M-d-y"))
(alter-field :open bigdec)
(alter-field :high bigdec)
(alter-field :low bigdec)
(alter-field :close bigdec)
(alter-field :volume bigdec)
(alter-field :market_cap bigdec)
(add-field :from-currency (fn [row] from))
(add-field :to-currency (fn [row] to)))
(into {}
(map (fn [measure] {(.getTime (:date measure)) measure}))
(map (comp (op/alter-field :date (parse-date "M-d-y"))
(op/alter-field :open bigdec)
(op/alter-field :high bigdec)
(op/alter-field :low bigdec)
(op/alter-field :close bigdec)
(op/alter-field :volume bigdec)
(op/alter-field :market_cap bigdec)
(op/add-field :from-currency (fn [row] from))
(op/add-field :to-currency (fn [row] to)))
(load-csv file)))
#_(group-by (fn [measure] (.getTime (:date measure)))
(map (comp (op/alter-field :date (parse-date "M-d-y"))
(op/alter-field :open bigdec)
(op/alter-field :high bigdec)
(op/alter-field :low bigdec)
(op/alter-field :close bigdec)
(op/alter-field :volume bigdec)
(op/alter-field :market_cap bigdec)
(op/add-field :from-currency (fn [row] from))
(op/add-field :to-currency (fn [row] to)))
(load-csv file))))
(s/fdef load-coinmarketcap-rates
:args (s/cat :file file? :from keyword? :to keyword?)
:args (s/cat :file path-url? :from keyword? :to keyword?)
:ret ::tax/pricemap)
(defn load-bittrex-csv [file]
(map (comp (split-field :exchange [:to-currency headify :from-currency headify] #"-")
(alter-field :type (fn [type-str] (headify (last (str/split type-str #"_")))))
(alter-field :quantity bigdec)
(alter-field :limit bigdec)
(alter-field :commissionpaid bigdec)
(alter-field :price bigdec)
(alter-field :opened (parse-date "M/d/y K:m:s a"))
(alter-field :closed (parse-date "M/d/y K:m:s a")))
(map (comp (op/split-field :exchange [:to-currency headify :from-currency headify] #"-")
(op/alter-field :type (fn [type-str] (headify (last (str/split type-str #"_")))))
(op/alter-field :quantity bigdec)
(op/alter-field :limit bigdec)
(op/alter-field :commissionpaid bigdec)
(op/alter-field :price bigdec)
(op/alter-field :opened (parse-date "M/d/y K:m:s a"))
(op/alter-field :closed (parse-date "M/d/y K:m:s a")))
(load-csv file)))
#_(defn minimize
@ -281,20 +291,22 @@
(defn bittrex-rate:btc->usd [btcusd]
(let [timestamps (sort (keys btcusd))]
(fn [date]
(first (map get-avg-rate
(get btcusd (bsearch-timestamps timestamps (.getTime date))))))))
(get-avg-rate (get btcusd
(bsearch-timestamps timestamps
(.getTime date)))))))
(s/fdef bittrex-rate:btc->usd
:args (s/cat :btcusd ::tax/pricemap)
:ret (s/fspec :args (s/cat :date ::tax/timestamp) :ret decimal?))
:ret (s/fspec :args (s/cat :date ::tax/datetime) :ret decimal?))
(defn bittrex-rate:bcc->usd [bccusd]
(let [timestamps (sort (keys bccusd))]
(fn [date]
(first (map get-avg-rate
(get bccusd (bsearch-timestamps timestamps (.getTime date))))))))
(s/fdef bittrex-rate:btc->usd
(get-avg-rate (get bccusd
(bsearch-timestamps timestamps
(.getTime date)))))))
(s/fdef bittrex-rate:bcc->usd
:args (s/cat :bccusd ::tax/pricemap)
:ret (s/fspec :args (s/cat :date ::tax/timestamp) :ret decimal?))
:ret (s/fspec :args (s/cat :date ::tax/datetime) :ret decimal?))
(defn pp [obj]
(clojure.pprint/pprint obj)
@ -304,52 +316,40 @@
(let [->usd (bittrex-rate:btc->usd btcusd)
timestamps (sort (keys adabtc))]
(fn [date]
(let [ada-in-btc (first (map get-avg-rate
(get adabtc (bsearch-timestamps timestamps
(.getTime date)))))]
(let [ada-in-btc (get-avg-rate (get adabtc
(bsearch-timestamps timestamps
(.getTime date))))]
(* ada-in-btc (->usd date))))))
(s/fdef bittrex-rate:ada->usd
:args (s/cat :btcusd ::tax/pricemap :adabtc ::tax/pricemap)
:ret (s/fspec :args (s/cat :date ::tax/timestamp) :ret decimal?))
:ret (s/fspec :args (s/cat :date ::tax/datetime) :ret decimal?))
(defn bittrex-rate:eth->usd [ethusd]
(let [timestamps (sort (keys ethusd))]
(fn [date]
(first (map get-avg-rate
(get ethusd (bsearch-timestamps timestamps (.getTime date))))))))
(get-avg-rate (get ethusd
(bsearch-timestamps timestamps
(.getTime date)))))))
(s/fdef bittrex-rate:eth->usd
:args (s/cat :ethusd ::tax/pricemap)
:ret (s/fspec :args (s/cat :date ::tax/timestamp) :ret decimal?))
:ret (s/fspec :args (s/cat :date ::tax/datetime) :ret decimal?))
(defn coinmarketcap-rate:btg->usd [btgusd]
(let [timestamps (sort (keys btgusd))]
(fn [date]
(first (map get-avg-rate
(get btgusd (bsearch-timestamps timestamps (.getTime date))))))))
(get-avg-rate (get btgusd
(bsearch-timestamps timestamps
(.getTime date)))))))
(defn bittrex-rate:usdt->usd []
(fn [date] 1))
(s/fdef bittrex-rate:usdt->usd
:ret (s/fspec :args (s/cat :date ::tax/timestamp) :ret decimal?))
#_(defn build-bittrex-rates [btcusd-csv bccusd-csv adabtc-csv ethusd-csv]
(let [btcusd (load-bittrex-rates btcusd-csv)]
{:btc->usd (-> btcusd bittrex-rate:btc->usd)
:bcc->usd (-> bccusd-csv load-bittrex-rates bittrex-rate:bcc->usd)
:ada->usd (-> adabtc-csv load-bittrex-rates (partial bittrex-rate:ada->usd btcusd))
:eth->usd (-> ethusd-csv load-bittrex-rates bittrex-rate:eth->usd)
:usdt->usd (bittrex-rate:usdt->usd)}))
#_(s/fdef build-bittrex-rates
:args (s/cat :btcusd-csv file?
:bccusd-csv file?
:adabtc-csv file?
:ethusd-csv file?)
:ret ::tax/pricemaps)
:ret (s/fspec :args (s/cat :date ::tax/datetime) :ret decimal?))
(defn common-bittrex->local [txn]
(-> txn
(assoc ::tax/txn-id (:orderuuid txn)
::tax/timestamp (:closed txn)
::tax/datetime (:closed txn)
::tax/account :bittrex)
(common->local)))
@ -372,27 +372,29 @@
(common-bittrex->local))
(-> txn
(assoc ::tax/txn-type :buy
::tax/amount (with-precision 10 (/ (:quantity txn)
(:price txn)))
::tax/amount (:price txn)
;; ::tax/amount (with-precision 10 (/ (:quantity txn)
;; (:price txn)))
::tax/currency (:to-currency txn)
::tax/usd-amount usd-amount
::tax/notes "split from sell")
(common-bittrex->local))]))
(defn bittrex-buy->local-txns [rates txn]
(let [usd-amount (* (:quantity txn)
(get-bittrex-rate rates (:from-currency txn) :usd (:closed txn)))]
(let [usd-amount (* (:price txn)
(get-bittrex-rate rates (:to-currency txn) :usd (:closed txn)))]
[(-> txn
(assoc ::tax/txn-type :buy
::tax/amount (:quantity txn)
(assoc ::tax/txn-type :sell
::tax/amount (:price txn)
::tax/currency (:to-currency txn)
::tax/usd-amount usd-amount
::tax/notes "split from buy")
(common-bittrex->local))
(-> txn
(assoc ::tax/txn-type :sell
::tax/amount (with-precision 10 (/ (:quantity txn)
(:price txn)))
(assoc ::tax/txn-type :buy
::tax/amount (:quantity txn)
;; ::tax/amount (with-precision 10 (/ (:quantity txn)
;; (:price txn)))
::tax/currency (:from-currency txn)
::tax/usd-amount usd-amount
::tax/notes "split from buy")
@ -409,5 +411,5 @@
(mapcat (partial bittrex-txn->local-txns rates) txns))
(s/fdef merge-bittrex-transactions
:args (s/cat :rates ::tax/pricemaps
:txns (s/coll-of (s/map-of ::tax/header any?)))
:txns (s/coll-of (s/map-of ::header any?)))
:ret ::tax/txns)

View File

@ -1,10 +1,15 @@
(ns taxer.ops
(:require [taxer.core :as tax]
[clojure.spec.alpha :as s]))
[clojure.spec.alpha :as s]
[clojure.core.reducers :refer [reduce]]
[clojure.string :as str]))
(defn filter-on-value [rows field value]
(filter (fn [row] (= (get row field) value)) rows))
(s/fdef filter-on-value
(defn filter-on-value [rows & filters]
(let [pairs (partition 2 filters)]
(filter (fn [row]
(every? (fn [[k v]] (= v (get row k))) pairs))
rows)))
#_(s/fdef filter-on-value
:args (s/cat :rows ::tax/txns
:field keyword?
:value any?)
@ -24,3 +29,24 @@
:without-order (s/cat :rows ::tax/txns
:field keyword?))
:ret ::tax/txns)
(defn alter-field [field f]
(fn [row]
(if (get row field)
(update row field (fn [value] (f value)))
row)))
(defn add-field [field f]
(fn [row]
(assoc row field (f row))))
(defn split-field [field header-generators & [passed-sep]]
(let [header-pairs (partition 2 header-generators)
sep (or passed-sep #" ")]
(fn [row]
(into row
(map (fn [[header generator] value] {header (generator value)})
header-pairs
(str/split (field row) sep))))))