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"]
|
||||
[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"]])
|
||||
|
|
|
@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))))))
|
||||
|
|
Loading…
Reference in New Issue