Why are buys mapping to sells?
This commit is contained in:
parent
90aefc0f29
commit
0aff915bb8
|
@ -17,4 +17,5 @@
|
||||||
[org.clojure/data.json "0.2.6"]
|
[org.clojure/data.json "0.2.6"]
|
||||||
[orchestra "2018.08.19-1"]
|
[orchestra "2018.08.19-1"]
|
||||||
[org.fudo.utils "0.0.2"]
|
[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"]])
|
||||||
|
|
|
@ -35,4 +35,4 @@ a5231ece-4c46-4a31-9e2e-b093d9cf6a8b,USDT-BTG,LIMIT_SELL,7.94203782,315.0000001,
|
||||||
7cce5777-ed16-4b96-9c60-95f70e975453,BTC-ADA,LIMIT_SELL,10000,0.0000275,0.00069251,0.27700857,12/17/2017 7:46:28 AM,12/17/2017 7:46:28 AM
|
7cce5777-ed16-4b96-9c60-95f70e975453,BTC-ADA,LIMIT_SELL,10000,0.0000275,0.00069251,0.27700857,12/17/2017 7:46:28 AM,12/17/2017 7:46:28 AM
|
||||||
d49216c6-7513-4f8a-ab26-22a402a8f781,BTC-ADA,LIMIT_SELL,10000,0.00005363,0.00134074,0.53629998,12/17/2017 7:59:03 AM,12/31/2017 12:41:57 AM
|
d49216c6-7513-4f8a-ab26-22a402a8f781,BTC-ADA,LIMIT_SELL,10000,0.00005363,0.00134074,0.53629998,12/17/2017 7:59:03 AM,12/31/2017 12:41:57 AM
|
||||||
95efafd5-5978-42ad-9f64-3caf5976310a,BTC-ADA,LIMIT_SELL,10000,0.00005225,0.00130625,0.5225,12/17/2017 7:58:50 AM,12/31/2017 12:40:05 AM
|
95efafd5-5978-42ad-9f64-3caf5976310a,BTC-ADA,LIMIT_SELL,10000,0.00005225,0.00130625,0.5225,12/17/2017 7:58:50 AM,12/31/2017 12:40:05 AM
|
||||||
7fc00045-5a59-4315-86a5-a8a31080111d,BTC-ADA,LIMIT_SELL,10000,0.000044,0.0011,0.44,12/17/2017 7:48:02 AM,12/30/2017 7:32:42 AM
|
7fc00045-5a59-4315-86a5-a8a31080111d,BTC-ADA,LIMIT_SELL,10000,0.000044,0.0011,0.44,12/17/2017 7:48:02 AM,12/30/2017 7:32:42 AM
|
||||||
|
|
|
|
@ -12,6 +12,7 @@ Basic idea:
|
||||||
- BCH and BTG count as income: all deposits should be multiplied by the day-1
|
- 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.
|
value and counted as income--everything else counts as capital gains.
|
||||||
- BCH traded on the first day at $277, according to bitcoin.tax.
|
- 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".
|
- Uhh, some Coinbase "sends" are really sales. Look for "Sent to Coinbase".
|
||||||
- Coinbase sent to Vault of Satoshi, where they were sold. Should be
|
- 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
|
considered sold...but I don't know how to calculate the tax. Anyway, I
|
||||||
|
@ -22,10 +23,17 @@ Basic idea:
|
||||||
- 18as544Wxg3ZScCZo7fjWSV4JGUhsLv6AR
|
- 18as544Wxg3ZScCZo7fjWSV4JGUhsLv6AR
|
||||||
- 18as544Wxg3ZScCZo7fjWSV4JGUhsLv6AR
|
- 18as544Wxg3ZScCZo7fjWSV4JGUhsLv6AR
|
||||||
- 1GxxcLKjqHD1wZWoZ3KNgiqjQonGDpZS4
|
- 1GxxcLKjqHD1wZWoZ3KNgiqjQonGDpZS4
|
||||||
- 1G3GM7izNsegWnxTB3RbuXAkC9YZxfeYP1
|
|
||||||
- Definitely gifts:
|
- Definitely gifts:
|
||||||
- 1L4kschshGtKJPM3T5RXhBJYtEHhFa3xq (Omar)
|
- 1L4kschshGtKJPM3T5RXhBJYtEHhFa3xq (Omar)
|
||||||
- 18Co5639x3Dp1EExfbgEzBKXYn329cwYKt
|
- 18Co5639x3Dp1EExfbgEzBKXYn329cwYKt
|
||||||
|
- 17XNrTEqMe4PkUcCFVaihW7Yu2gFasaKxM
|
||||||
|
- 15My27F2QLkLrZ3bqR8SSUHHrCKwQyGeLd
|
||||||
|
- 1Ae5kmNpAweTDGGguQCK55G84LDcHArJYn
|
||||||
|
- 1G3GM7izNsegWnxTB3RbuXAkC9YZxfeYP1
|
||||||
|
- 18Co5639x3Dp1EExfbgEzBKXYn329cwYKt
|
||||||
|
- Purchases:r
|
||||||
|
- 1EjPaprQpLmPPJnfECRg9jK9v7eyEYkBNH
|
||||||
|
- 1Nc59oJunufbRe6uhqByJ1Kop19tqTQ1hr
|
||||||
- Vircurex:
|
- Vircurex:
|
||||||
- 1FripmTRgNFx6M2C7udeDWKYW8wfR5vuUU
|
- 1FripmTRgNFx6M2C7udeDWKYW8wfR5vuUU
|
||||||
- 1FripmTRgNFx6M2C7udeDWKYW8wfR5vuUU
|
- 1FripmTRgNFx6M2C7udeDWKYW8wfR5vuUU
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
(ns taxer.core
|
(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 ::currency #{:usd :bch :btc :eth :ada :btg})
|
||||||
|
|
||||||
(s/def ::from-currency ::currency)
|
(s/def ::from-currency ::currency)
|
||||||
(s/def ::to-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?)
|
(s/def ::timestamp integer?)
|
||||||
|
|
||||||
|
@ -15,7 +20,7 @@
|
||||||
(re-matches #"^[0-9a-f]{40}$" obj))))
|
(re-matches #"^[0-9a-f]{40}$" obj))))
|
||||||
(s/def ::id ::sha1-sum)
|
(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 ::from-account ::sha1-sum)
|
||||||
(s/def ::to-account ::sha1-sum)
|
(s/def ::to-account ::sha1-sum)
|
||||||
|
@ -29,7 +34,8 @@
|
||||||
::currency
|
::currency
|
||||||
::txn-type
|
::txn-type
|
||||||
::account
|
::account
|
||||||
::currency])
|
::currency
|
||||||
|
::datetime])
|
||||||
(defmulti txn-type ::txn-type)
|
(defmulti txn-type ::txn-type)
|
||||||
(defmethod txn-type :buy [_]
|
(defmethod txn-type :buy [_]
|
||||||
(s/keys :req (concat txn-common-req
|
(s/keys :req (concat txn-common-req
|
||||||
|
@ -52,10 +58,55 @@
|
||||||
(defmethod txn-type :fee [_]
|
(defmethod txn-type :fee [_]
|
||||||
(s/keys :req (concat txn-common-req [])
|
(s/keys :req (concat txn-common-req [])
|
||||||
:opt [::notes]))
|
: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 ::txn (s/multi-spec txn-type ::txn-type))
|
||||||
(s/def ::txns (s/coll-of ::txn))
|
(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/def ::pricemap
|
||||||
(s/map-of ::timestamp
|
(s/map-of ::timestamp
|
||||||
(s/keys :req-un [::date
|
(s/keys :req-un [::date
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
(ns taxer.executor
|
(ns taxer.executor
|
||||||
(:require [taxer.importer :as import]
|
(:require [taxer.importer :as import]
|
||||||
|
[taxer.ops :as op]
|
||||||
[taxer.core :as tax]
|
[taxer.core :as tax]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]
|
||||||
|
[orchestra.spec.test :as st]))
|
||||||
|
|
||||||
|
(st/instrument)
|
||||||
|
|
||||||
(defn gdax []
|
(defn gdax []
|
||||||
(import/merge-gdax-transactions
|
(import/merge-gdax-transactions
|
||||||
|
@ -37,9 +41,6 @@
|
||||||
(import/merge-bittrex-transactions (rates)
|
(import/merge-bittrex-transactions (rates)
|
||||||
(import/load-bittrex-csv "resources/bittrex-fullOrders.csv")))
|
(import/load-bittrex-csv "resources/bittrex-fullOrders.csv")))
|
||||||
|
|
||||||
(defn all-txns []
|
|
||||||
(concat (gdax) (coinbase) (bittrex)))
|
|
||||||
|
|
||||||
(defn project [ks m]
|
(defn project [ks m]
|
||||||
(into {} (map (fn [k] [k (get m k)]) ks)))
|
(into {} (map (fn [k] [k (get m k)]) ks)))
|
||||||
|
|
||||||
|
@ -53,19 +54,208 @@
|
||||||
(defn update-field [field f txns]
|
(defn update-field [field f txns]
|
||||||
(map (fn [txn] (update txn 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]
|
(defn print-as-table [txns]
|
||||||
(let [restrict-fields (partial project [::tax/timestamp
|
(let [restrict-fields (partial project [::tax/datetime
|
||||||
::tax/txn-id
|
;;::tax/txn-id
|
||||||
::tax/txn-type
|
::tax/txn-type
|
||||||
::tax/amount
|
::tax/amount
|
||||||
::tax/currency
|
::tax/currency
|
||||||
::tax/usd-amount
|
::tax/usd-amount
|
||||||
::tax/id
|
;;::tax/account
|
||||||
;;::tax/notes
|
;;::tax/id
|
||||||
|
::tax/notes
|
||||||
])]
|
])]
|
||||||
(clojure.pprint/print-table
|
(clojure.pprint/print-table
|
||||||
(->> txns
|
(->> txns
|
||||||
(sort-by ::tax/timestamp)
|
(sort-by ::tax/datetime)
|
||||||
(update-field ::tax/amount round-str)
|
(update-field ::tax/amount round-str)
|
||||||
(update-field ::tax/usd-amount round-str)
|
(update-field ::tax/usd-amount round-str)
|
||||||
(update-field ::tax/timestamp format-date)
|
(update-field ::tax/timestamp format-date)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
|
||||||
(ns taxer.importer
|
(ns taxer.importer
|
||||||
(:require [taxer.core :as tax]
|
(:require [taxer.core :as tax]
|
||||||
|
[taxer.ops :as op]
|
||||||
[org.fudo.utils.sorted :as sort]
|
[org.fudo.utils.sorted :as sort]
|
||||||
[clojure.core.match :refer [match]]
|
[clojure.core.match :refer [match]]
|
||||||
[clojure.data.csv :as csv]
|
[clojure.data.csv :as csv]
|
||||||
|
@ -13,6 +15,9 @@
|
||||||
(defn file? [obj]
|
(defn file? [obj]
|
||||||
(instance? java.io.File obj))
|
(instance? java.io.File obj))
|
||||||
|
|
||||||
|
(defn path-url? [obj]
|
||||||
|
(instance? java.net.URL obj))
|
||||||
|
|
||||||
(s/def ::header keyword?)
|
(s/def ::header keyword?)
|
||||||
|
|
||||||
(defn headify [str]
|
(defn headify [str]
|
||||||
|
@ -30,7 +35,7 @@
|
||||||
(s/fdef make-row
|
(s/fdef make-row
|
||||||
:args (s/cat :headers (s/coll-of ::header)
|
:args (s/cat :headers (s/coll-of ::header)
|
||||||
:fields (s/coll-of string?))
|
:fields (s/coll-of string?))
|
||||||
:ret (s/map-of ::tax/header string?))
|
:ret (s/map-of ::header string?))
|
||||||
|
|
||||||
(defn load-csv [file]
|
(defn load-csv [file]
|
||||||
(with-open [reader (io/reader file)]
|
(with-open [reader (io/reader file)]
|
||||||
|
@ -38,46 +43,27 @@
|
||||||
headers (map headify (first lines))]
|
headers (map headify (first lines))]
|
||||||
(map (partial make-row headers) (rest lines)))))
|
(map (partial make-row headers) (rest lines)))))
|
||||||
(s/fdef load-csv
|
(s/fdef load-csv
|
||||||
:args (s/cat :file file?)
|
:args (s/cat :file path-url?)
|
||||||
:ret (s/coll-of (s/map-of ::tax/header string?)))
|
:ret (s/coll-of (s/map-of ::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))))))
|
|
||||||
|
|
||||||
(defn parse-date [date-format]
|
(defn parse-date [date-format]
|
||||||
(let [date-format (java.text.SimpleDateFormat. date-format)]
|
(let [date-format (java.text.SimpleDateFormat. date-format)]
|
||||||
(fn [date-str] (.parse date-format date-str))))
|
(fn [date-str] (.parse date-format date-str))))
|
||||||
|
|
||||||
(defn load-coinbase-csv [file]
|
(defn load-coinbase-csv [file]
|
||||||
(map (comp (alter-field :timestamp (parse-date "M/d/y"))
|
(map (comp (op/alter-field :timestamp (parse-date "M/d/y"))
|
||||||
(alter-field :transaction_type headify)
|
(op/alter-field :transaction_type headify)
|
||||||
(alter-field :asset headify)
|
(op/alter-field :asset headify)
|
||||||
(alter-field :quantity_transacted bigdec)
|
(op/alter-field :quantity_transacted bigdec)
|
||||||
(alter-field :usd_spot_price_at_transaction bigdec)
|
(op/alter-field :usd_spot_price_at_transaction bigdec)
|
||||||
(alter-field :usd_amount_transacted bigdec))
|
(op/alter-field :usd_amount_transacted bigdec))
|
||||||
(load-csv file)))
|
(load-csv file)))
|
||||||
|
|
||||||
(defn load-gdax-csv [file]
|
(defn load-gdax-csv [file]
|
||||||
(map (comp (alter-field :type headify)
|
(map (comp (op/alter-field :type headify)
|
||||||
(alter-field :date (parse-date "y-M-d H:m:s"))
|
(op/alter-field :date (parse-date "y-M-d H:m:s"))
|
||||||
(split-field :amount [:txn_amount bigdec :txn_currency headify])
|
(op/split-field :amount [:txn_amount bigdec :txn_currency headify])
|
||||||
(split-field :balance [:balance_amount bigdec :balance_currency headify]))
|
(op/split-field :balance [:balance_amount bigdec :balance_currency headify]))
|
||||||
(load-csv file)))
|
(load-csv file)))
|
||||||
|
|
||||||
(defn sha1-sum [s]
|
(defn sha1-sum [s]
|
||||||
|
@ -99,7 +85,7 @@
|
||||||
;; :notes)
|
;; :notes)
|
||||||
|
|
||||||
(defn common->local [txn]
|
(defn common->local [txn]
|
||||||
(let [id-fields [::tax/timestamp
|
(let [id-fields [::tax/datetime
|
||||||
::tax/txn-type
|
::tax/txn-type
|
||||||
::tax/amount
|
::tax/amount
|
||||||
::tax/currency
|
::tax/currency
|
||||||
|
@ -110,7 +96,7 @@
|
||||||
|
|
||||||
(defn common-coinbase->local [txn]
|
(defn common-coinbase->local [txn]
|
||||||
(-> txn
|
(-> txn
|
||||||
(assoc ::tax/timestamp (:timestamp txn)
|
(assoc ::tax/datetime (:timestamp txn)
|
||||||
::tax/usd-amount (:usd_amount_transacted txn)
|
::tax/usd-amount (:usd_amount_transacted txn)
|
||||||
::tax/account :coinbase
|
::tax/account :coinbase
|
||||||
::tax/amount (:quantity_transacted txn)
|
::tax/amount (:quantity_transacted txn)
|
||||||
|
@ -131,7 +117,7 @@
|
||||||
|
|
||||||
(defmethod coinbase->local :receive [txn]
|
(defmethod coinbase->local :receive [txn]
|
||||||
(-> txn
|
(-> txn
|
||||||
(assoc ::tax/txn-type :receive)
|
(assoc ::tax/txn-type :deposit)
|
||||||
(common-coinbase->local)))
|
(common-coinbase->local)))
|
||||||
|
|
||||||
(defmethod coinbase->local :send [txn]
|
(defmethod coinbase->local :send [txn]
|
||||||
|
@ -150,9 +136,9 @@
|
||||||
(defn common-gdax->local [txn]
|
(defn common-gdax->local [txn]
|
||||||
(-> txn
|
(-> txn
|
||||||
(assoc ::tax/txn-id (:txid txn)
|
(assoc ::tax/txn-id (:txid txn)
|
||||||
::tax/timestamp (:date txn)
|
::tax/datetime (:date txn)
|
||||||
::tax/account :gdx
|
::tax/account :gdax
|
||||||
::tax/amount (:txn_amount txn)
|
::tax/amount (.abs (:txn_amount txn))
|
||||||
::tax/currency (:txn_currency txn))
|
::tax/currency (:txn_currency txn))
|
||||||
(common->local)))
|
(common->local)))
|
||||||
|
|
||||||
|
@ -209,46 +195,70 @@
|
||||||
(defn load-bittrex-rates [file]
|
(defn load-bittrex-rates [file]
|
||||||
(let [take-first-3 (fn [sym] (-> sym (subs 0 3) str/lower-case keyword))
|
(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))]
|
take-rest (fn [sym] (-> sym (subs 3) str/lower-case keyword))]
|
||||||
(group-by (fn [measure] (.getTime (:date measure)))
|
(into {}
|
||||||
(map (comp (alter-field :date (parse-date "y-M-d K-a"))
|
(map (fn [measure] {(.getTime (:date measure)) measure}))
|
||||||
(add-field :from-currency (fn [row] (take-first-3 (:symbol row))))
|
(map (comp (op/alter-field :date (parse-date "y-M-d K-a"))
|
||||||
(add-field :to-currency (fn [row] (take-rest (:symbol row))))
|
(op/add-field :from-currency (fn [row] (take-first-3 (:symbol row))))
|
||||||
(alter-field :open bigdec)
|
(op/add-field :to-currency (fn [row] (take-rest (:symbol row))))
|
||||||
(alter-field :high bigdec)
|
(op/alter-field :open bigdec)
|
||||||
(alter-field :low bigdec)
|
(op/alter-field :high bigdec)
|
||||||
(alter-field :close bigdec)
|
(op/alter-field :low bigdec)
|
||||||
(alter-field :volume_from bigdec)
|
(op/alter-field :close bigdec)
|
||||||
(alter-field :volume_to 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)))))
|
(load-csv file)))))
|
||||||
(s/fdef bittrex-load-prices
|
(s/fdef bittrex-load-prices
|
||||||
:args (s/cat :file file?)
|
:args (s/cat :file path-url?)
|
||||||
:ret ::tax/pricemap)
|
:ret ::tax/pricemap)
|
||||||
|
|
||||||
(defn load-coinmarketcap-rates [file from to]
|
(defn load-coinmarketcap-rates [file from to]
|
||||||
(group-by (fn [measure] (.getTime (:date measure)))
|
(into {}
|
||||||
(map (comp (alter-field :date (parse-date "M-d-y"))
|
(map (fn [measure] {(.getTime (:date measure)) measure}))
|
||||||
(alter-field :open bigdec)
|
(map (comp (op/alter-field :date (parse-date "M-d-y"))
|
||||||
(alter-field :high bigdec)
|
(op/alter-field :open bigdec)
|
||||||
(alter-field :low bigdec)
|
(op/alter-field :high bigdec)
|
||||||
(alter-field :close bigdec)
|
(op/alter-field :low bigdec)
|
||||||
(alter-field :volume bigdec)
|
(op/alter-field :close bigdec)
|
||||||
(alter-field :market_cap bigdec)
|
(op/alter-field :volume bigdec)
|
||||||
(add-field :from-currency (fn [row] from))
|
(op/alter-field :market_cap bigdec)
|
||||||
(add-field :to-currency (fn [row] to)))
|
(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))))
|
(load-csv file))))
|
||||||
(s/fdef load-coinmarketcap-rates
|
(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)
|
:ret ::tax/pricemap)
|
||||||
|
|
||||||
(defn load-bittrex-csv [file]
|
(defn load-bittrex-csv [file]
|
||||||
(map (comp (split-field :exchange [:to-currency headify :from-currency headify] #"-")
|
(map (comp (op/split-field :exchange [:to-currency headify :from-currency headify] #"-")
|
||||||
(alter-field :type (fn [type-str] (headify (last (str/split type-str #"_")))))
|
(op/alter-field :type (fn [type-str] (headify (last (str/split type-str #"_")))))
|
||||||
(alter-field :quantity bigdec)
|
(op/alter-field :quantity bigdec)
|
||||||
(alter-field :limit bigdec)
|
(op/alter-field :limit bigdec)
|
||||||
(alter-field :commissionpaid bigdec)
|
(op/alter-field :commissionpaid bigdec)
|
||||||
(alter-field :price bigdec)
|
(op/alter-field :price bigdec)
|
||||||
(alter-field :opened (parse-date "M/d/y K:m:s a"))
|
(op/alter-field :opened (parse-date "M/d/y K:m:s a"))
|
||||||
(alter-field :closed (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)))
|
(load-csv file)))
|
||||||
|
|
||||||
#_(defn minimize
|
#_(defn minimize
|
||||||
|
@ -281,20 +291,22 @@
|
||||||
(defn bittrex-rate:btc->usd [btcusd]
|
(defn bittrex-rate:btc->usd [btcusd]
|
||||||
(let [timestamps (sort (keys btcusd))]
|
(let [timestamps (sort (keys btcusd))]
|
||||||
(fn [date]
|
(fn [date]
|
||||||
(first (map get-avg-rate
|
(get-avg-rate (get btcusd
|
||||||
(get btcusd (bsearch-timestamps timestamps (.getTime date))))))))
|
(bsearch-timestamps timestamps
|
||||||
|
(.getTime date)))))))
|
||||||
(s/fdef bittrex-rate:btc->usd
|
(s/fdef bittrex-rate:btc->usd
|
||||||
:args (s/cat :btcusd ::tax/pricemap)
|
: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]
|
(defn bittrex-rate:bcc->usd [bccusd]
|
||||||
(let [timestamps (sort (keys bccusd))]
|
(let [timestamps (sort (keys bccusd))]
|
||||||
(fn [date]
|
(fn [date]
|
||||||
(first (map get-avg-rate
|
(get-avg-rate (get bccusd
|
||||||
(get bccusd (bsearch-timestamps timestamps (.getTime date))))))))
|
(bsearch-timestamps timestamps
|
||||||
(s/fdef bittrex-rate:btc->usd
|
(.getTime date)))))))
|
||||||
|
(s/fdef bittrex-rate:bcc->usd
|
||||||
:args (s/cat :bccusd ::tax/pricemap)
|
: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]
|
(defn pp [obj]
|
||||||
(clojure.pprint/pprint obj)
|
(clojure.pprint/pprint obj)
|
||||||
|
@ -304,52 +316,40 @@
|
||||||
(let [->usd (bittrex-rate:btc->usd btcusd)
|
(let [->usd (bittrex-rate:btc->usd btcusd)
|
||||||
timestamps (sort (keys adabtc))]
|
timestamps (sort (keys adabtc))]
|
||||||
(fn [date]
|
(fn [date]
|
||||||
(let [ada-in-btc (first (map get-avg-rate
|
(let [ada-in-btc (get-avg-rate (get adabtc
|
||||||
(get adabtc (bsearch-timestamps timestamps
|
(bsearch-timestamps timestamps
|
||||||
(.getTime date)))))]
|
(.getTime date))))]
|
||||||
(* ada-in-btc (->usd date))))))
|
(* ada-in-btc (->usd date))))))
|
||||||
(s/fdef bittrex-rate:ada->usd
|
(s/fdef bittrex-rate:ada->usd
|
||||||
:args (s/cat :btcusd ::tax/pricemap :adabtc ::tax/pricemap)
|
: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]
|
(defn bittrex-rate:eth->usd [ethusd]
|
||||||
(let [timestamps (sort (keys ethusd))]
|
(let [timestamps (sort (keys ethusd))]
|
||||||
(fn [date]
|
(fn [date]
|
||||||
(first (map get-avg-rate
|
(get-avg-rate (get ethusd
|
||||||
(get ethusd (bsearch-timestamps timestamps (.getTime date))))))))
|
(bsearch-timestamps timestamps
|
||||||
|
(.getTime date)))))))
|
||||||
(s/fdef bittrex-rate:eth->usd
|
(s/fdef bittrex-rate:eth->usd
|
||||||
:args (s/cat :ethusd ::tax/pricemap)
|
: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]
|
(defn coinmarketcap-rate:btg->usd [btgusd]
|
||||||
(let [timestamps (sort (keys btgusd))]
|
(let [timestamps (sort (keys btgusd))]
|
||||||
(fn [date]
|
(fn [date]
|
||||||
(first (map get-avg-rate
|
(get-avg-rate (get btgusd
|
||||||
(get btgusd (bsearch-timestamps timestamps (.getTime date))))))))
|
(bsearch-timestamps timestamps
|
||||||
|
(.getTime date)))))))
|
||||||
|
|
||||||
(defn bittrex-rate:usdt->usd []
|
(defn bittrex-rate:usdt->usd []
|
||||||
(fn [date] 1))
|
(fn [date] 1))
|
||||||
(s/fdef bittrex-rate:usdt->usd
|
(s/fdef bittrex-rate:usdt->usd
|
||||||
:ret (s/fspec :args (s/cat :date ::tax/timestamp) :ret decimal?))
|
:ret (s/fspec :args (s/cat :date ::tax/datetime) :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)
|
|
||||||
|
|
||||||
(defn common-bittrex->local [txn]
|
(defn common-bittrex->local [txn]
|
||||||
(-> txn
|
(-> txn
|
||||||
(assoc ::tax/txn-id (:orderuuid txn)
|
(assoc ::tax/txn-id (:orderuuid txn)
|
||||||
::tax/timestamp (:closed txn)
|
::tax/datetime (:closed txn)
|
||||||
::tax/account :bittrex)
|
::tax/account :bittrex)
|
||||||
(common->local)))
|
(common->local)))
|
||||||
|
|
||||||
|
@ -372,27 +372,29 @@
|
||||||
(common-bittrex->local))
|
(common-bittrex->local))
|
||||||
(-> txn
|
(-> txn
|
||||||
(assoc ::tax/txn-type :buy
|
(assoc ::tax/txn-type :buy
|
||||||
::tax/amount (with-precision 10 (/ (:quantity txn)
|
::tax/amount (:price txn)
|
||||||
(:price txn)))
|
;; ::tax/amount (with-precision 10 (/ (:quantity txn)
|
||||||
|
;; (:price txn)))
|
||||||
::tax/currency (:to-currency txn)
|
::tax/currency (:to-currency txn)
|
||||||
::tax/usd-amount usd-amount
|
::tax/usd-amount usd-amount
|
||||||
::tax/notes "split from sell")
|
::tax/notes "split from sell")
|
||||||
(common-bittrex->local))]))
|
(common-bittrex->local))]))
|
||||||
|
|
||||||
(defn bittrex-buy->local-txns [rates txn]
|
(defn bittrex-buy->local-txns [rates txn]
|
||||||
(let [usd-amount (* (:quantity txn)
|
(let [usd-amount (* (:price txn)
|
||||||
(get-bittrex-rate rates (:from-currency txn) :usd (:closed txn)))]
|
(get-bittrex-rate rates (:to-currency txn) :usd (:closed txn)))]
|
||||||
[(-> txn
|
[(-> txn
|
||||||
(assoc ::tax/txn-type :buy
|
(assoc ::tax/txn-type :sell
|
||||||
::tax/amount (:quantity txn)
|
::tax/amount (:price txn)
|
||||||
::tax/currency (:to-currency txn)
|
::tax/currency (:to-currency txn)
|
||||||
::tax/usd-amount usd-amount
|
::tax/usd-amount usd-amount
|
||||||
::tax/notes "split from buy")
|
::tax/notes "split from buy")
|
||||||
(common-bittrex->local))
|
(common-bittrex->local))
|
||||||
(-> txn
|
(-> txn
|
||||||
(assoc ::tax/txn-type :sell
|
(assoc ::tax/txn-type :buy
|
||||||
::tax/amount (with-precision 10 (/ (:quantity txn)
|
::tax/amount (:quantity txn)
|
||||||
(:price txn)))
|
;; ::tax/amount (with-precision 10 (/ (:quantity txn)
|
||||||
|
;; (:price txn)))
|
||||||
::tax/currency (:from-currency txn)
|
::tax/currency (:from-currency txn)
|
||||||
::tax/usd-amount usd-amount
|
::tax/usd-amount usd-amount
|
||||||
::tax/notes "split from buy")
|
::tax/notes "split from buy")
|
||||||
|
@ -409,5 +411,5 @@
|
||||||
(mapcat (partial bittrex-txn->local-txns rates) txns))
|
(mapcat (partial bittrex-txn->local-txns rates) txns))
|
||||||
(s/fdef merge-bittrex-transactions
|
(s/fdef merge-bittrex-transactions
|
||||||
:args (s/cat :rates ::tax/pricemaps
|
: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)
|
:ret ::tax/txns)
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
(ns taxer.ops
|
(ns taxer.ops
|
||||||
(:require [taxer.core :as tax]
|
(: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]
|
(defn filter-on-value [rows & filters]
|
||||||
(filter (fn [row] (= (get row field) value)) rows))
|
(let [pairs (partition 2 filters)]
|
||||||
(s/fdef filter-on-value
|
(filter (fn [row]
|
||||||
|
(every? (fn [[k v]] (= v (get row k))) pairs))
|
||||||
|
rows)))
|
||||||
|
#_(s/fdef filter-on-value
|
||||||
:args (s/cat :rows ::tax/txns
|
:args (s/cat :rows ::tax/txns
|
||||||
:field keyword?
|
:field keyword?
|
||||||
:value any?)
|
:value any?)
|
||||||
|
@ -24,3 +29,24 @@
|
||||||
:without-order (s/cat :rows ::tax/txns
|
:without-order (s/cat :rows ::tax/txns
|
||||||
:field keyword?))
|
:field keyword?))
|
||||||
:ret ::tax/txns)
|
: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))))))
|
||||||
|
|
Loading…
Reference in New Issue