Initial commit.
This commit is contained in:
commit
2eeffb057d
|
@ -0,0 +1,6 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
/private
|
||||
.DS_Store
|
||||
.vscode/*
|
||||
rls*
|
|
@ -0,0 +1,730 @@
|
|||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parse-zoneinfo 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptools-rs"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"decimal 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustyline 5.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bstr 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decimal"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ord_subset 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ord_subset"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand_os"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "5.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "structopt"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "structopt-derive"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
|
||||
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
||||
"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
|
||||
"checksum backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "1371048253fa3bac6704bfd6bbfc922ee9bdcee8881330d40f308b81cc5adc55"
|
||||
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
|
||||
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
|
||||
"checksum blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bf775a81bb2d464e20ff170ac20316c7b08a43d11dbc72f0f82e8e8d3d6d0499"
|
||||
"checksum bstr 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94cdf78eb7e94c566c1f5dbe2abf8fc70a548fc902942a48c4b3a98b48ca9ade"
|
||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
||||
"checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7"
|
||||
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
|
||||
"checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe"
|
||||
"checksum chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e0e430fad0384e4defc3dc6b1223d1b886087a8bf9b7080e5ae027f73851ea15"
|
||||
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
|
||||
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
|
||||
"checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"
|
||||
"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
|
||||
"checksum decimal 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e6458723bc760383275fbc02f4c769b2e5f3de782abaf5e7e0b9b7f0368a63ed"
|
||||
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
||||
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
|
||||
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
|
||||
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
||||
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
|
||||
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
||||
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
|
||||
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
|
||||
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
||||
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
|
||||
"checksum ord_subset 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7ce14664caf5b27f5656ff727defd68ae1eb75ef3c4d95259361df1eb376bef"
|
||||
"checksum parse-zoneinfo 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "089a398ccdcdd77b8c38909d5a1e4b67da1bc4c9dbfe6d5b536c828eddb779e5"
|
||||
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||
"checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802"
|
||||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d"
|
||||
"checksum regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88c3d9193984285d544df4a30c23a4e62ead42edf70a4452ceb76dac1ce05c26"
|
||||
"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9"
|
||||
"checksum regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b143cceb2ca5e56d5671988ef8b15615733e7ee16cd348e064333b251b89343f"
|
||||
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum rustyline 5.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f8ee0838a6594169a1c5f4bb9af0fe692cc99691941710a8cc6576395ede804e"
|
||||
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
|
||||
"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f"
|
||||
"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425"
|
||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
"checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
|
||||
"checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107"
|
||||
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
"checksum syn 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c65d951ab12d976b61a41cf9ed4531fc19735c6e6d84a4bb1453711e762ec731"
|
||||
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
|
||||
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "cryptools-rs"
|
||||
version = "0.6.0"
|
||||
authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
description = "This is a command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'."
|
||||
|
||||
[dependencies]
|
||||
csv = "1.0.0"
|
||||
serde = { version = "1.0.75", features = ["derive"] }
|
||||
serde_derive = "1.0.75"
|
||||
decimal = "2.0.4"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
chrono-tz = "0.5"
|
||||
time = "0.1.42"
|
||||
structopt = "0.2.10"
|
||||
rustyline = "5.0.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions, and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// 4. Individual files may or may not contain the entire contents of this file. If a an
|
||||
// individual file is copied/redistributed/so on, this license must be pasted in place
|
||||
// of the license notice on that page.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,84 @@
|
|||
# cryptools-rs
|
||||
|
||||
### Accounting library for cryptocurrency transaction activity.
|
||||
|
||||
It provides a way to measure cryptocurrency activity in one's home currency (the default value is USD, but anything can be used).
|
||||
Reports may be exported as CSV files that reflect income/expense/gains/losses.
|
||||
|
||||
The activity that gets imported **must** be in a prescribed form that effectively looks like this:
|
||||
|
||||
|
||||
|txDate |proceeds|memo |1 |2 |3 |4 |5 |
|
||||
|-------|--------|--------|------|--------|--------|--------|------------|
|
||||
| | | |Bank |Exchange|Exchange|Exchange|Simplewallet|
|
||||
| | | |USD |BTC |BTC |XMR |XMR |
|
||||
| | | |non |non |non |non |non |
|
||||
|2/1/16 |0 |FIRST |-220 |0.25 | | | |
|
||||
|3/1/16 |250 |SECOND | |-0.25 | |180 | |
|
||||
|4/1/16 |0 |THIRD | | | |-90 |90 |
|
||||
|5/1/16 |0 |FOURTH | | | |90 |-90 |
|
||||
|5/2/16 |160 |FIFTH | |0.3 | |-90 | |
|
||||
|6/1/16 |0 |SIXTH | |-0.3 |0.3 | | |
|
||||
|7/1/16 |200 |SEVENTH | | |0.7 |-90 | |
|
||||
|8/1/16 |0 |EIGHTH | |0.5 |-0.5 | | |
|
||||
|9/1/16 |400 |NINTH | | |-0.5 |200 | |
|
||||
|10/1/16|900 |TENTH | |1 | |-200 | |
|
||||
|11/1/16|0 |ELEVENTH| |-1.5 |1.5 | | |
|
||||
|12/1/16|2000 |TWELFTH | | |-1.5 |400 | |
|
||||
|
||||
|
||||
#### CSV file components
|
||||
|
||||
* **txDate** is currently set to parse dates of the format MM/dd/YY.
|
||||
|
||||
* **proceeds** may seem tricky.
|
||||
The way to understand it, since it can apply to any transaction (aside from transfers from one owned-account to another owned-account), is that this is the value transferred in the transaction.
|
||||
For example, if one spends 0.01 BTC for an item at a time when BTC/USD is $10,000/BTC, then the user received value of $100, therefore the proceeds of that transaction would be $100.
|
||||
This field is ignored when the user's home currency is used to purchase cryptocurrency.
|
||||
|
||||
* **memo** is useful for evaluating the final output but isn't important.
|
||||
Currently, commas in the memo are **not** supported.
|
||||
|
||||
After three column of transaction metadata, the *Account* columns follow.
|
||||
|
||||
* *Accounts* (**1**, **2**, **3**, **4**, **5**, ...): the top row reflects the account number (which currently must start at 1 and increase sequentially).
|
||||
The three other values are the *name*, *ticker*, and *margin_bool*.
|
||||
*name* and *ticker* should be self-explanatory.
|
||||
*margin_bool* is set usually set as 'no', 'non' (i.e., non-margin), or 'false.'
|
||||
To indicate a margin account, set it as 'yes', 'margin' or 'true'.
|
||||
|
||||
###### Margin accounts
|
||||
|
||||
* Margin accounts must come in pairs, the base account and the quote account.
|
||||
The base account is the coin being longed or shorted, and its ticker is reflected like normal.
|
||||
The quote account is the market.
|
||||
The quote account's ticker requires a different formatting.
|
||||
For example, when using BTC to long XMR, the BTC account must be reflected with the ticker BTC_xmr.
|
||||
|
||||
#### Constraints
|
||||
|
||||
* *All* cryptocurrency-related activity for the user generally must be included in the input CSV file.
|
||||
|
||||
* There can only be either one or two accounts used in a given transaction (i.e., if a Counterparty token or Ethereum token transaction must be recorded, the XCP or ETH transaction fee must be reflected in a separate transaction/row).
|
||||
|
||||
* Currently, manual adjustments may need to be made to the output files in cases, for example, when the user used cryptocurrency to make atax-deductible charitable contribution.
|
||||
|
||||
## Installation
|
||||
|
||||
1. `cargo build` (or include `--release` for a non-debug build)
|
||||
|
||||
This will build `./cryptools-rs`.
|
||||
|
||||
## Usage
|
||||
|
||||
Run `./cryptools-rs` with no arguments (or `--help`, or `-h`) to see usage.
|
||||
|
||||
## Contributing
|
||||
|
||||
* Contributors welcome. New authors should add themselves to the `AUTHORS` file.
|
||||
|
||||
* Roadmap and todos: we're working through items in [Issues](https://github.com/scoobybejesus/cryptools-rs/issues); feel free to tackle or add issues.
|
||||
|
||||
## Legal
|
||||
|
||||
See LEGAL.txt
|
|
@ -0,0 +1,249 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::fmt;
|
||||
use std::collections::{HashMap};
|
||||
|
||||
use chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, Utc, TimeZone};
|
||||
use chrono_tz::US::Eastern;
|
||||
use decimal::d128;
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
use crate::transaction::{Transaction, ActionRecord, Polarity, TxType};
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RawAccount {
|
||||
pub account_num: u16,
|
||||
pub name: String,
|
||||
pub ticker: String,
|
||||
pub is_margin: bool,
|
||||
}
|
||||
|
||||
impl RawAccount {
|
||||
pub fn is_home_currency(&self, compare: &String) -> bool {
|
||||
&self.ticker == compare
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Account {
|
||||
pub raw_key: u16,
|
||||
pub list_of_lots: RefCell<Vec<Rc<Lot>>>,
|
||||
// pub vec_of_lot_keys: (RawAccount, u32),
|
||||
}
|
||||
|
||||
impl Account {
|
||||
|
||||
pub fn is_home_currency(&self, compare: &String, raw_acct_map: &HashMap<u16, RawAccount>) -> bool {
|
||||
let raw_acct = raw_acct_map.get(&self.raw_key).unwrap();
|
||||
&raw_acct.ticker == compare
|
||||
}
|
||||
|
||||
pub fn get_sum_of_amts_in_lots(&self) -> d128 {
|
||||
let lots = self.list_of_lots.borrow();
|
||||
let mut total_amount = d128!(0);
|
||||
for lot in lots.iter() {
|
||||
let sum = lot.get_sum_of_amts_in_lot();
|
||||
total_amount += sum;
|
||||
}
|
||||
total_amount
|
||||
}
|
||||
|
||||
pub fn get_sum_of_basis_in_lots(&self) -> d128 {
|
||||
let lots = self.list_of_lots.borrow();
|
||||
let mut total_amount = d128!(0);
|
||||
for lot in lots.iter() {
|
||||
let sum = lot.get_sum_of_basis_in_lot();
|
||||
total_amount += sum;
|
||||
}
|
||||
total_amount
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RawMarginPair (pub Weak<RawAccount>, pub Weak<RawAccount>); // always (base_acct, quote_acct)
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Lot {
|
||||
pub date_as_string: String,
|
||||
pub date_of_first_mvmt_in_lot: NaiveDate,
|
||||
pub date_for_basis_purposes: NaiveDate,
|
||||
pub lot_number: u32, // Does NOT start at zero. First lot is lot 1.
|
||||
pub account_key: u16,
|
||||
pub movements: RefCell<Vec<Rc<Movement>>>,
|
||||
}
|
||||
|
||||
impl Lot {
|
||||
pub fn get_sum_of_amts_in_lot(&self) -> d128 {
|
||||
let mut amts = d128!(0);
|
||||
self.movements.borrow().iter().for_each(|movement| amts += movement.amount);
|
||||
amts
|
||||
}
|
||||
pub fn sum_of_amts_in_lot_is_zero(&self) -> bool {
|
||||
d128!(0) == Self::get_sum_of_amts_in_lot(&self)
|
||||
}
|
||||
pub fn get_sum_of_basis_in_lot(&self) -> d128 {
|
||||
let mut amts = d128!(0);
|
||||
self.movements.borrow().iter().for_each(|movement| amts += movement.cost_basis.get());
|
||||
amts
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Movement {
|
||||
pub amount: d128,
|
||||
pub date_as_string: String,
|
||||
pub date: NaiveDate,
|
||||
pub transaction_key: u32,
|
||||
pub action_record_key: u32,
|
||||
pub cost_basis: Cell<d128>, // Initialized with 0. Set in add_cost_basis_to_movements()
|
||||
pub ratio_of_amt_to_incoming_mvmts_in_a_r: d128, // Set in process_multiple_incoming_lots_and_mvmts() and incoming flow dual actionrecord transactions
|
||||
pub ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell<d128>, // Set in wrap_mvmt_and_push()
|
||||
pub lot_num: u32,
|
||||
pub proceeds: Cell<d128>, // Initialized with 0. Set in add_proceeds_to_movements()
|
||||
}
|
||||
|
||||
impl Movement {
|
||||
|
||||
pub fn get_lot(
|
||||
&self,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
ar_map: &HashMap<u32, ActionRecord>
|
||||
) -> Rc<Lot> {
|
||||
let ar = ar_map.get(&self.action_record_key).unwrap();
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let lot = acct.list_of_lots.borrow()[self.lot_num as usize - 1].clone(); // lots start at 1 and indexes at 0
|
||||
lot
|
||||
}
|
||||
|
||||
pub fn ratio_of_amt_to_lots_first_mvmt(
|
||||
&self,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
ar_map: &HashMap<u32, ActionRecord>
|
||||
) -> d128 {
|
||||
// println!("Lot #: {}", self.lot.lot_number);
|
||||
let ar = ar_map.get(&self.action_record_key).unwrap();
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let lot = &acct.list_of_lots.borrow()[self.lot_num as usize - 1]; // lots start at 1 and indexes at 0
|
||||
let borrowed_mvmt_list = lot.movements.borrow();
|
||||
let ratio = self.amount / borrowed_mvmt_list.first().unwrap().amount;
|
||||
// println!("ratio_of_amt_to_lots_first_mvmt: {}", ratio.abs());
|
||||
ratio.abs()
|
||||
}
|
||||
|
||||
pub fn get_gain_or_loss(&self) -> d128 {
|
||||
self.proceeds.get() + self.cost_basis.get()
|
||||
}
|
||||
|
||||
pub fn get_term(&self, acct_map: &HashMap<u16, Account>, ar_map: &HashMap<u32, ActionRecord>,) -> Term {
|
||||
|
||||
use time::Duration;
|
||||
let ar = ar_map.get(&self.action_record_key).unwrap();
|
||||
let lot = Self::get_lot(&self, acct_map, ar_map);
|
||||
|
||||
match ar.direction() {
|
||||
Polarity::Incoming => {
|
||||
let today = Utc::now();
|
||||
let utc_lot_date = Self::create_date_time_from_atlantic(lot.date_for_basis_purposes, NaiveTime::from_hms_milli(12, 34, 56, 789));
|
||||
// if today.signed_duration_since(self.lot.date_for_basis_purposes) > 365
|
||||
if (today - utc_lot_date) > Duration::days(365) { // TODO: figure out how to instantiate today's date and convert it to compare to NaiveDate
|
||||
Term::LT
|
||||
}
|
||||
else {
|
||||
Term::ST
|
||||
}
|
||||
}
|
||||
Polarity::Outgoing => {
|
||||
let lot_date_for_basis_purposes = lot.date_for_basis_purposes;
|
||||
if self.date.signed_duration_since(lot_date_for_basis_purposes) > Duration::days(365) {
|
||||
return Term::LT
|
||||
}
|
||||
Term::ST
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_date_time_from_atlantic(date: NaiveDate, time: NaiveTime) -> DateTime<Utc> {
|
||||
let naive_datetime = NaiveDateTime::new(date, time);
|
||||
let east_time = Eastern.from_local_datetime(&naive_datetime).unwrap();
|
||||
east_time.with_timezone(&Utc)
|
||||
}
|
||||
|
||||
pub fn get_income(
|
||||
&self,
|
||||
ar_map: &HashMap<u32,
|
||||
ActionRecord>,
|
||||
raw_accts: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
)-> d128 {
|
||||
|
||||
let txn = txns_map.get(&self.transaction_key).expect("Couldn't get txn. Tx num invalid?");
|
||||
match txn.transaction_type(ar_map, raw_accts, acct_map) {
|
||||
TxType::Flow => {
|
||||
let ar = ar_map.get(&self.action_record_key).unwrap();
|
||||
if ar.direction() == Polarity::Incoming {
|
||||
self.proceeds.get()
|
||||
}
|
||||
else { d128!(0) }
|
||||
}
|
||||
TxType::Exchange => { d128!(0) }
|
||||
TxType::ToSelf => { d128!(0) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_expense(
|
||||
&self,
|
||||
ar_map: &HashMap<u32, ActionRecord>,
|
||||
raw_accts: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
)-> d128 {
|
||||
|
||||
let txn = txns_map.get(&self.transaction_key).expect("Couldn't get txn. Tx num invalid?");
|
||||
match txn.transaction_type(ar_map, raw_accts, acct_map) {
|
||||
TxType::Flow => {
|
||||
let ar = ar_map.get(&self.action_record_key).unwrap();
|
||||
if ar.direction() == Polarity::Outgoing {
|
||||
self.proceeds.get()
|
||||
}
|
||||
else { d128!(0) }
|
||||
}
|
||||
TxType::Exchange => { d128!(0) }
|
||||
TxType::ToSelf => { d128!(0) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn direction(&self) -> Polarity {
|
||||
if self.amount < d128!(0.0) { Polarity::Outgoing }
|
||||
else { Polarity::Incoming }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Term {
|
||||
LT,
|
||||
ST,
|
||||
}
|
||||
|
||||
impl Term {
|
||||
|
||||
pub fn abbr_string(&self) -> String {
|
||||
match *self {
|
||||
Term::LT => "LT".to_string(),
|
||||
Term::ST => "ST".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Term {
|
||||
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Term::LT => write!(f, "LT"),
|
||||
Term::ST => write!(f, "ST"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::process;
|
||||
use std::collections::{HashMap};
|
||||
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use crate::account::{Account, RawAccount, Lot};
|
||||
use crate::transaction::{Transaction, ActionRecord};
|
||||
use crate::user_choices::{LotProcessingChoices, LikeKindSettings};
|
||||
use crate::import_accts_txns;
|
||||
use crate::import_cost_proceeds_etc;
|
||||
use crate::create_lots_mvmts;
|
||||
|
||||
pub fn import_and_process_final(
|
||||
input_file_path: PathBuf,
|
||||
settings: &LotProcessingChoices,
|
||||
) -> (
|
||||
HashMap<u16, Account>,
|
||||
HashMap<u16, RawAccount>,
|
||||
HashMap<u32, ActionRecord>,
|
||||
HashMap<u32, Transaction>,
|
||||
Option<LikeKindSettings>
|
||||
) {
|
||||
|
||||
let mut transactions_map: HashMap<u32, Transaction> = HashMap::new();
|
||||
let mut action_records_map: HashMap<u32, ActionRecord> = HashMap::new();
|
||||
let mut raw_account_map: HashMap<u16, RawAccount> = HashMap::new();
|
||||
let mut account_map: HashMap<u16, Account> = HashMap::new();
|
||||
let mut lot_map: HashMap<(RawAccount, u32), Lot> = HashMap::new();
|
||||
|
||||
match import_from_csv(
|
||||
input_file_path,
|
||||
&mut transactions_map,
|
||||
&mut action_records_map,
|
||||
&mut raw_account_map,
|
||||
&mut account_map
|
||||
) {
|
||||
Ok(()) => { println!("Successfully imported csv file."); }
|
||||
Err(err) => { println!("\nFailed to import accounts and transactions from CSV."); println!("{}", err); process::exit(1); }
|
||||
};
|
||||
|
||||
pub fn import_from_csv(
|
||||
import_file_path: PathBuf,
|
||||
transactions_map: &mut HashMap<u32, Transaction>,
|
||||
action_records: &mut HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &mut HashMap<u16, RawAccount>,
|
||||
acct_map: &mut HashMap<u16, Account>,
|
||||
) -> Result<(), Box<Error>> {
|
||||
|
||||
let file = File::open(import_file_path)?; println!("CSV ledger file opened successfully.\n");
|
||||
|
||||
let mut rdr = csv::ReaderBuilder::new()
|
||||
.has_headers(true)
|
||||
.from_reader(file);
|
||||
|
||||
match import_accts_txns::import_accounts(&mut rdr, raw_acct_map, acct_map) {
|
||||
Ok(()) => {}
|
||||
Err(err) => { println!("\nFailed to import accounts from CSV."); println!("{}", err); }
|
||||
};
|
||||
|
||||
match import_accts_txns::import_transactions(
|
||||
&mut rdr,
|
||||
transactions_map,
|
||||
action_records,
|
||||
raw_acct_map,
|
||||
acct_map
|
||||
) {
|
||||
Ok(()) => {}
|
||||
Err(err) => { println!("\nFailed to import transactions from CSV."); println!("{}", err); }
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// println!("like_kind_cutoff_date is: {}...", like_kind_cutoff_date);
|
||||
|
||||
let likekind_settings: Option<LikeKindSettings> = if settings.enable_like_kind_treatment {
|
||||
|
||||
let like_kind_cutoff_date = &settings.lk_cutoff_date_string;
|
||||
|
||||
Some(
|
||||
LikeKindSettings {
|
||||
like_kind_cutoff_date: NaiveDate::parse_from_str(&like_kind_cutoff_date, "%y-%m-%d")
|
||||
.unwrap_or(NaiveDate::parse_from_str(&like_kind_cutoff_date, "%Y-%m-%d")
|
||||
.expect("Found date string with improper format")),
|
||||
like_kind_basis_date_preserved: true,
|
||||
}
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
transactions_map = create_lots_mvmts::create_lots_and_movements(
|
||||
transactions_map,
|
||||
&settings,
|
||||
&likekind_settings,
|
||||
&action_records_map,
|
||||
&mut raw_account_map,
|
||||
&mut account_map,
|
||||
&mut lot_map,
|
||||
);
|
||||
|
||||
println!(" Successfully created lots and movements.");
|
||||
|
||||
import_cost_proceeds_etc::add_cost_basis_to_movements(
|
||||
&settings,
|
||||
&action_records_map,
|
||||
&raw_account_map,
|
||||
&account_map,
|
||||
&transactions_map
|
||||
);
|
||||
|
||||
println!(" Successfully added cost basis to movements.");
|
||||
|
||||
import_cost_proceeds_etc::add_proceeds_to_movements(
|
||||
&action_records_map,
|
||||
&raw_account_map,
|
||||
&account_map,
|
||||
&transactions_map
|
||||
);
|
||||
|
||||
println!(" Successfully added proceeds to movements.");
|
||||
|
||||
|
||||
if let Some(lk_settings) = &likekind_settings {
|
||||
|
||||
let cutoff_date = lk_settings.like_kind_cutoff_date;
|
||||
println!(" Applying like-kind treatment for cut-off date: {}.", cutoff_date);
|
||||
|
||||
import_cost_proceeds_etc::apply_like_kind_treatment(
|
||||
cutoff_date,
|
||||
&settings,
|
||||
&action_records_map,
|
||||
&raw_account_map,
|
||||
&account_map,
|
||||
&transactions_map
|
||||
);
|
||||
|
||||
println!(" Successfully applied like-kind treatment.");
|
||||
};
|
||||
|
||||
(account_map, raw_account_map, action_records_map, transactions_map, likekind_settings)
|
||||
}
|
|
@ -0,0 +1,889 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::rc::{Rc};
|
||||
use std::cell::{RefCell, Ref, Cell};
|
||||
use std::collections::{HashMap};
|
||||
|
||||
use decimal::d128;
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use crate::transaction::{Transaction, ActionRecord, TxType, Polarity, TxHasMargin};
|
||||
use crate::account::{Account, RawAccount, Lot, Movement};
|
||||
use crate::user_choices::{LotProcessingChoices, InventoryCostingMethod, LikeKindSettings};
|
||||
use crate::utils::{round_d128_1e8};
|
||||
|
||||
pub fn create_lots_and_movements(
|
||||
txns_map: HashMap<u32, Transaction>,
|
||||
settings: &LotProcessingChoices,
|
||||
likekind_settings: &Option<LikeKindSettings>,
|
||||
ar_map: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
lot_map: &HashMap<(RawAccount, u32), Lot>,
|
||||
) -> HashMap<u32,Transaction> {
|
||||
|
||||
// Set values to be referred to repeatedly, potentially, in Incoming Exchange transactions
|
||||
let multiple_incoming_mvmts_per_ar = match &likekind_settings {
|
||||
Some(likekind_settings) => {
|
||||
likekind_settings.like_kind_basis_date_preserved
|
||||
}
|
||||
None => {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let mut like_kind_cutoff_date: NaiveDate = NaiveDate::parse_from_str("1/1/1", "%m/%d/%y")
|
||||
.expect("NaiveDate string parsing failed. It shouldn't have. Try again.");
|
||||
// Above sets date never to be used. Below modifies date in case it will be used.
|
||||
if likekind_settings.is_some() {
|
||||
let likekind_settings_clone = likekind_settings.clone().unwrap();
|
||||
like_kind_cutoff_date = likekind_settings_clone.like_kind_cutoff_date;
|
||||
}
|
||||
|
||||
// On with the creating of lots and movements.
|
||||
let length = txns_map.len();
|
||||
for num in 1..=length {
|
||||
|
||||
let txn_num = num as u32;
|
||||
let txn = txns_map.get(&(txn_num)).expect("Couldn't get txn. Tx num invalid?");
|
||||
if txn.marginness(&ar_map, &raw_acct_map, &acct_map) == TxHasMargin::TwoARs {
|
||||
assert_eq!(txn.transaction_type(&ar_map, &raw_acct_map, &acct_map), TxType::Exchange);
|
||||
assert_eq!(txn.action_record_idx_vec.len(), 2);
|
||||
|
||||
let the_raw_pair_keys = txn.get_base_and_quote_raw_acct_keys(&ar_map, &raw_acct_map, &acct_map);
|
||||
let base_acct = acct_map.get(&the_raw_pair_keys.0).expect("Couldn't get acct. Raw pair keys invalid?");
|
||||
let quote_acct = acct_map.get(&the_raw_pair_keys.1).expect("Couldn't get acct. Raw pair keys invalid?");
|
||||
|
||||
let (base_ar_idx, quote_ar_idx) = get_base_and_quote_ar_idxs(
|
||||
the_raw_pair_keys,
|
||||
&txn,
|
||||
&ar_map,
|
||||
&raw_acct_map,
|
||||
&acct_map
|
||||
);
|
||||
|
||||
let base_ar = ar_map.get(&base_ar_idx).unwrap();
|
||||
let quote_ar = ar_map.get("e_ar_idx).unwrap();
|
||||
|
||||
let mut base_acct_lot_list = base_acct.list_of_lots.borrow_mut();
|
||||
let mut quote_acct_lot_list = quote_acct.list_of_lots.borrow_mut();
|
||||
|
||||
let base_number_of_lots = base_acct_lot_list.len() as u32;
|
||||
let quote_number_of_lots = quote_acct_lot_list.len() as u32;
|
||||
assert_eq!(base_number_of_lots, quote_number_of_lots, "");
|
||||
|
||||
let acct_balances_are_zero: bool;
|
||||
|
||||
if !base_acct_lot_list.is_empty() && !quote_acct_lot_list.is_empty() {
|
||||
let base_balance_is_zero = base_acct_lot_list.last().unwrap().get_sum_of_amts_in_lot() == d128!(0);
|
||||
let quote_balance_is_zero = quote_acct_lot_list.last().unwrap().get_sum_of_amts_in_lot() == d128!(0);
|
||||
if base_balance_is_zero && quote_balance_is_zero {
|
||||
acct_balances_are_zero = true
|
||||
} else {
|
||||
acct_balances_are_zero = false
|
||||
}
|
||||
} else {
|
||||
assert_eq!(true, base_acct_lot_list.is_empty(),
|
||||
"One margin account's list_of_lots is empty, but its pair's isn't.");
|
||||
assert_eq!(true, quote_acct_lot_list.is_empty(),
|
||||
"One margin account's list_of_lots is empty, but its pair's isn't.");
|
||||
acct_balances_are_zero = true
|
||||
}
|
||||
|
||||
let mut base_lot: Rc<Lot>;
|
||||
let mut quote_lot: Rc<Lot>;
|
||||
|
||||
if acct_balances_are_zero {
|
||||
base_lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: txn.date,
|
||||
lot_number: base_number_of_lots + 1,
|
||||
account_key: the_raw_pair_keys.0,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
)
|
||||
;
|
||||
quote_lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: txn.date,
|
||||
lot_number: quote_number_of_lots + 1,
|
||||
account_key: the_raw_pair_keys.1,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
)
|
||||
;
|
||||
} else {
|
||||
base_lot = base_acct_lot_list.last().expect("Couldn't get lot. Base acct lot list empty?").clone();
|
||||
quote_lot = quote_acct_lot_list.last().expect("Couldn't get lot. Quote acct lot list empty?").clone();
|
||||
}
|
||||
|
||||
let base_mvmt = Movement {
|
||||
amount: base_ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: base_ar_idx,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: base_lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(base_mvmt, &base_ar, &base_lot, &settings, &raw_acct_map, &acct_map);
|
||||
|
||||
let quote_mvmt = Movement {
|
||||
amount: quote_ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: quote_ar_idx,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: quote_lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(quote_mvmt, "e_ar, "e_lot, &settings, &raw_acct_map, &acct_map);
|
||||
|
||||
if acct_balances_are_zero {
|
||||
base_acct_lot_list.push(base_lot);
|
||||
quote_acct_lot_list.push(quote_lot);
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
for ar_num in txn.action_record_idx_vec.iter() {
|
||||
let ar = ar_map.get(ar_num).unwrap();
|
||||
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
let length_of_list_of_lots = acct.list_of_lots.borrow().len();
|
||||
|
||||
if raw_acct.is_home_currency(&settings.home_currency) {
|
||||
if length_of_list_of_lots == 0 {
|
||||
let lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: txn.date,
|
||||
lot_number: 1,
|
||||
account_key: acct.raw_key,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
)
|
||||
;
|
||||
let whole_mvmt = Movement {
|
||||
amount: ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
acct.list_of_lots.borrow_mut().push(lot);
|
||||
continue
|
||||
}
|
||||
else {
|
||||
assert_eq!(1, length_of_list_of_lots); // Only true for home currency
|
||||
let lot = acct.list_of_lots.borrow_mut()[0 as usize].clone();
|
||||
let whole_mvmt = Movement {
|
||||
amount: ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Note: a_r is not in home currency if here or below
|
||||
let polarity = ar.direction();
|
||||
let tx_type = txn.transaction_type(&ar_map, &raw_acct_map, &acct_map);
|
||||
|
||||
match polarity {
|
||||
Polarity::Outgoing => {
|
||||
// println!("Txn: {}, outgoing {:?}-type of {} {}",
|
||||
// txn.tx_number, txn.transaction_type(), ar.amount, acct.ticker);
|
||||
//
|
||||
if raw_acct.is_margin {
|
||||
let this_acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let lot = this_acct.list_of_lots.borrow().last()
|
||||
.expect("Couldn't get lot. Acct lot list empty?").clone();
|
||||
let whole_mvmt = Movement {
|
||||
amount: ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
continue
|
||||
} else {
|
||||
let list_of_lots_to_use = acct.list_of_lots.clone();
|
||||
|
||||
// the following returns vec to be iterated from beginning to end, which provides the index for the correct lot
|
||||
let vec_of_ordered_index_values = match settings.costing_method {
|
||||
InventoryCostingMethod::LIFObyLotCreationDate => {
|
||||
get_lifo_by_creation_date(&list_of_lots_to_use.borrow())}
|
||||
InventoryCostingMethod::LIFObyLotBasisDate => {
|
||||
get_lifo_by_lot_basis_date(&list_of_lots_to_use.borrow())}
|
||||
InventoryCostingMethod::FIFObyLotCreationDate => {
|
||||
get_fifo_by_creation_date(&list_of_lots_to_use.borrow())}
|
||||
InventoryCostingMethod::FIFObyLotBasisDate => {
|
||||
get_fifo_by_lot_basis_date(&list_of_lots_to_use.borrow())}
|
||||
};
|
||||
|
||||
fn get_lifo_by_creation_date(list_of_lots: &Ref<Vec<Rc<Lot>>>) -> Vec<usize> {
|
||||
let mut vec_of_indexes = [].to_vec();
|
||||
for (idx, lot) in list_of_lots.iter().enumerate() {
|
||||
vec_of_indexes.insert(0, idx)
|
||||
}
|
||||
let vec = vec_of_indexes;
|
||||
vec
|
||||
}
|
||||
|
||||
fn get_lifo_by_lot_basis_date(list_of_lots: &Ref<Vec<Rc<Lot>>>) -> Vec<usize> {
|
||||
let mut reordered_vec = list_of_lots.clone().to_vec();
|
||||
let length = reordered_vec.len();
|
||||
for _ in 0..length {
|
||||
for j in 0..length-1 {
|
||||
if reordered_vec[j].date_for_basis_purposes > reordered_vec[j+1].date_for_basis_purposes {
|
||||
reordered_vec.swap(j, j+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut vec_of_indexes = [].to_vec();
|
||||
for (idx, lot) in reordered_vec.iter().enumerate() {
|
||||
vec_of_indexes.insert(0, idx)
|
||||
}
|
||||
let vec = vec_of_indexes;
|
||||
vec
|
||||
}
|
||||
|
||||
fn get_fifo_by_creation_date(list_of_lots: &Ref<Vec<Rc<Lot>>>) -> Vec<usize> {
|
||||
let mut vec_of_indexes = [].to_vec();
|
||||
for (idx, lot) in list_of_lots.iter().enumerate() {
|
||||
vec_of_indexes.push(idx)
|
||||
}
|
||||
let vec = vec_of_indexes;
|
||||
vec
|
||||
}
|
||||
|
||||
fn get_fifo_by_lot_basis_date(list_of_lots: &Ref<Vec<Rc<Lot>>>) -> Vec<usize> {
|
||||
let mut reordered_vec = list_of_lots.clone().to_vec();
|
||||
let length = reordered_vec.len();
|
||||
for _ in 0..length {
|
||||
for j in 0..length-1 {
|
||||
if reordered_vec[j].date_for_basis_purposes > reordered_vec[j+1].date_for_basis_purposes {
|
||||
reordered_vec.swap(j, j+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut vec_of_indexes = [].to_vec();
|
||||
for (idx, lot) in reordered_vec.iter().enumerate() {
|
||||
vec_of_indexes.push(idx)
|
||||
}
|
||||
let vec = vec_of_indexes;
|
||||
vec
|
||||
}
|
||||
|
||||
let index_position: usize = 0;
|
||||
let lot_index = vec_of_ordered_index_values[index_position];
|
||||
|
||||
let lot_to_use = list_of_lots_to_use.borrow()[lot_index].clone();
|
||||
let whole_mvmt = Movement {
|
||||
amount: ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot_to_use.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
|
||||
fit_into_lots(
|
||||
acct.raw_key,
|
||||
txn_num,
|
||||
*ar_num,
|
||||
whole_mvmt,
|
||||
list_of_lots_to_use,
|
||||
vec_of_ordered_index_values,
|
||||
index_position,
|
||||
&settings,
|
||||
&ar_map,
|
||||
&raw_acct_map,
|
||||
&acct_map,
|
||||
);
|
||||
continue
|
||||
}
|
||||
}
|
||||
Polarity::Incoming => {
|
||||
// println!("Txn: {}, Incoming {:?}-type of {} {}",
|
||||
// txn.tx_number, txn.transaction_type(), ar.amount, acct.ticker);
|
||||
match tx_type {
|
||||
TxType::Flow => {
|
||||
let mut lot: Rc<Lot>;
|
||||
if raw_acct.is_margin {
|
||||
let this_acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let lot_list = this_acct.list_of_lots.borrow_mut();
|
||||
lot = lot_list.last().unwrap().clone();
|
||||
|
||||
let mvmt = Movement {
|
||||
amount: ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
continue
|
||||
} else {
|
||||
let mvmt: Movement;
|
||||
if txn.action_record_idx_vec.len() == 1 {
|
||||
lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: txn.date,
|
||||
|
||||
lot_number: length_of_list_of_lots as u32 + 1,
|
||||
account_key: acct.raw_key,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
)
|
||||
;
|
||||
mvmt = Movement {
|
||||
amount: ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
} else {
|
||||
assert_eq!(txn.action_record_idx_vec.len(), 2);
|
||||
// create list of incoming/positive amounts(mvmts) in margin lot, add them
|
||||
let mut positive_mvmt_list: Vec<Rc<Movement>> = [].to_vec();
|
||||
let mut total_positive_amounts = d128!(0);
|
||||
|
||||
let (base_acct_key, quote_acct_key) = get_base_and_quote_acct_for_dual_actionrecord_flow_tx(
|
||||
txn_num,
|
||||
&ar_map,
|
||||
&raw_acct_map,
|
||||
&acct_map,
|
||||
&txns_map,
|
||||
);
|
||||
|
||||
let base_acct = acct_map.get(&base_acct_key).unwrap();
|
||||
let base_acct_lot = base_acct.list_of_lots.borrow().last().unwrap().clone();
|
||||
// TODO: generalize this to work with margin shorts as well
|
||||
for mvmt in base_acct_lot.movements.borrow().iter() {
|
||||
if mvmt.amount > d128!(0) {
|
||||
// println!("In lot# {}, positive mvmt amount: {} {},",
|
||||
// base_acct_lot.lot_number,
|
||||
// mvmt.borrow().amount,
|
||||
// base_acct_lot.account.raw.ticker);
|
||||
total_positive_amounts += mvmt.amount;
|
||||
positive_mvmt_list.push(mvmt.clone())
|
||||
}
|
||||
}
|
||||
let mut amounts_used = d128!(0);
|
||||
let mut percentages_used = d128!(0);
|
||||
|
||||
for pos_mvmt in positive_mvmt_list.iter().take(positive_mvmt_list.len()-1) {
|
||||
let inner_lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: pos_mvmt.date,
|
||||
lot_number: acct.list_of_lots.borrow().len() as u32 + 1,
|
||||
account_key: acct.raw_key,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
);
|
||||
let percentage_used = round_d128_1e8(&(pos_mvmt.amount/&total_positive_amounts));
|
||||
let amount_used = round_d128_1e8(&(ar.amount*percentage_used));
|
||||
let inner_mvmt = Movement {
|
||||
amount: amount_used,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: percentage_used,
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: inner_lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(inner_mvmt, &ar, &inner_lot, &settings, &raw_acct_map, &acct_map);
|
||||
acct.list_of_lots.borrow_mut().push(inner_lot);
|
||||
// acct.push_lot(inner_lot);
|
||||
amounts_used += amount_used;
|
||||
percentages_used += percentage_used;
|
||||
}
|
||||
|
||||
let final_mvmt = positive_mvmt_list.last().unwrap();
|
||||
lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: final_mvmt.date,
|
||||
lot_number: acct.list_of_lots.borrow().len() as u32 + 1,
|
||||
account_key: acct.raw_key,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
)
|
||||
;
|
||||
mvmt = Movement {
|
||||
amount: round_d128_1e8(&(ar.amount - amounts_used)),
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: round_d128_1e8(&(d128!(1.0) - percentages_used)),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
}
|
||||
wrap_mvmt_and_push(mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
acct.list_of_lots.borrow_mut().push(lot);
|
||||
continue
|
||||
}
|
||||
}
|
||||
TxType::Exchange => {
|
||||
let both_are_non_home_curr: bool;
|
||||
let og_ar = ar_map.get(txn.action_record_idx_vec.first().unwrap()).unwrap();
|
||||
let og_acct = acct_map.get(&og_ar.account_key).unwrap();
|
||||
let og_raw_acct = raw_acct_map.get(&og_acct.raw_key).unwrap();
|
||||
let ic_ar = ar;
|
||||
let ic_raw_acct = raw_acct;
|
||||
both_are_non_home_curr = !og_raw_acct.is_home_currency(&settings.home_currency)
|
||||
&& !ic_raw_acct.is_home_currency(&settings.home_currency);
|
||||
|
||||
if both_are_non_home_curr && multiple_incoming_mvmts_per_ar && (txn.date <= like_kind_cutoff_date) {
|
||||
process_multiple_incoming_lots_and_mvmts(
|
||||
txn_num,
|
||||
&og_ar,
|
||||
&ic_ar,
|
||||
&settings,
|
||||
*ar_num,
|
||||
&raw_acct_map,
|
||||
&acct_map,
|
||||
&txns_map,
|
||||
&ar_map,
|
||||
);
|
||||
continue
|
||||
}
|
||||
|
||||
else {
|
||||
let lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: txn.date,
|
||||
lot_number: length_of_list_of_lots as u32 + 1,
|
||||
account_key: acct.raw_key,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
)
|
||||
;
|
||||
let whole_mvmt = Movement {
|
||||
amount: ar.amount,
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: *ar_num,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
acct.list_of_lots.borrow_mut().push(lot);
|
||||
continue
|
||||
}
|
||||
}
|
||||
TxType::ToSelf => {
|
||||
if raw_acct.is_margin {
|
||||
{ println!("\n Found margin actionrecord in toself txn # {} \n", txn.tx_number); use std::process::exit; exit(1) };
|
||||
} else {
|
||||
process_multiple_incoming_lots_and_mvmts(
|
||||
txn_num,
|
||||
&ar_map.get(txn.action_record_idx_vec.first().unwrap()).unwrap(), // outgoing
|
||||
&ar, // incoming
|
||||
&settings,
|
||||
*ar_num,
|
||||
&raw_acct_map,
|
||||
&acct_map,
|
||||
&txns_map,
|
||||
&ar_map,
|
||||
);
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end for ar in txn.actionrecords
|
||||
} // end of tx does not have marginness of TwoARs
|
||||
} // end for txn in transactions
|
||||
txns_map
|
||||
}
|
||||
|
||||
fn get_base_and_quote_acct_for_dual_actionrecord_flow_tx(
|
||||
txn_num: u32,
|
||||
ar_map: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) -> (u16, u16) {
|
||||
|
||||
let txn = txns_map.get(&txn_num).expect("Couldn't get txn. Tx num invalid?");
|
||||
let og_flow_ar = ar_map.get(txn.action_record_idx_vec.first().unwrap()).unwrap();
|
||||
// println!("Acct: {}, Amount: {}, Tx: {}, ar: {}",
|
||||
// outgoing_flow_ar.account_key, outgoing_flow_ar.amount, outgoing_flow_ar.tx_key, outgoing_flow_ar.self_ar_key);
|
||||
let og_ar_mvmts_list = &og_flow_ar.get_mvmts_in_ar(acct_map, txns_map); // TODO: ... in margin profit, this just takes a list of one mvmt
|
||||
let og_ar_list_first_mvmt = &og_ar_mvmts_list.first().unwrap(); // TODO: then this takes the one mvmt
|
||||
let og_ar_list_first_mvmt_ar = ar_map.get(&og_ar_list_first_mvmt.action_record_key).unwrap();
|
||||
let og_ar_list_first_mvmt_ar_acct = acct_map.get(&og_ar_list_first_mvmt_ar.account_key).unwrap();
|
||||
let og_mvmt_lot = &og_ar_list_first_mvmt_ar_acct.list_of_lots.borrow()[(og_ar_list_first_mvmt.lot_num - 1) as usize];
|
||||
// let og_mvmt_lot_strong = &og_mvmt_lot;
|
||||
let og_mvmt_lot_mvmts = og_mvmt_lot.movements.borrow();
|
||||
let og_mvmt_lot_first_mvmt = &og_mvmt_lot_mvmts.first().unwrap();
|
||||
let txn_of_og_mvmt_lot_first_mvmt = txns_map.get(&og_mvmt_lot_first_mvmt.transaction_key).unwrap();
|
||||
let (base_key,quote_key) = txn_of_og_mvmt_lot_first_mvmt.get_base_and_quote_raw_acct_keys(
|
||||
ar_map,
|
||||
&raw_acct_map,
|
||||
&acct_map); // TODO: should this panic on margin loss?
|
||||
(base_key, quote_key)
|
||||
}
|
||||
|
||||
fn get_base_and_quote_ar_idxs(
|
||||
pair_keys: (u16,u16),
|
||||
txn: &Transaction,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>
|
||||
) -> (u32, u32){
|
||||
|
||||
let incoming_ar = ars.get(&txn.action_record_idx_vec[0]).unwrap();
|
||||
let incoming_acct = acct_map.get(&incoming_ar.account_key).unwrap();
|
||||
let raw_ic_acct = raw_acct_map.get(&incoming_acct.raw_key).unwrap();
|
||||
let compare = raw_acct_map.get(&pair_keys.0).unwrap(); // key.0 is base, and key.1 is quote
|
||||
|
||||
if raw_ic_acct == compare {
|
||||
(txn.action_record_idx_vec[0], txn.action_record_idx_vec[1])
|
||||
} else {
|
||||
(txn.action_record_idx_vec[1], txn.action_record_idx_vec[0])
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_mvmt_and_push(
|
||||
this_mvmt: Movement,
|
||||
ar: &ActionRecord,
|
||||
lot: &Lot,
|
||||
settings: &LotProcessingChoices,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
) {
|
||||
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
|
||||
if ar.direction() == Polarity::Outgoing && !raw_acct.is_home_currency(&settings.home_currency) {
|
||||
let ratio = this_mvmt.amount / ar.amount;
|
||||
this_mvmt.ratio_of_amt_to_outgoing_mvmts_in_a_r.set(round_d128_1e8(&ratio));
|
||||
}
|
||||
|
||||
let amt = this_mvmt.amount;
|
||||
let amt2 = round_d128_1e8(&amt);
|
||||
assert_eq!(amt, amt2);
|
||||
// println!("Unrounded: {}; Rounded: {}; on {}", amt, amt2, mvmt_ref.borrow().date);
|
||||
|
||||
let mvmt = Rc::from(this_mvmt);
|
||||
lot.movements.borrow_mut().push(mvmt.clone());
|
||||
ar.movements.borrow_mut().push(mvmt);
|
||||
}
|
||||
|
||||
fn fit_into_lots(
|
||||
acct_key: u16,
|
||||
txn_num: u32,
|
||||
spawning_ar_key: u32,
|
||||
mvmt_to_fit: Movement,
|
||||
list_of_lots_to_use: RefCell<Vec<Rc<Lot>>>,
|
||||
vec_of_ordered_index_values: Vec<usize>,
|
||||
index_position: usize,
|
||||
settings: &LotProcessingChoices,
|
||||
ar_map: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
) {
|
||||
|
||||
let ar = ar_map.get(&spawning_ar_key).unwrap();
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
assert_eq!(raw_acct.is_home_currency(&settings.home_currency), false);
|
||||
|
||||
let spawning_ar = ar_map.get(&spawning_ar_key).unwrap();
|
||||
let mut current_index_position = index_position;
|
||||
|
||||
let lot = mvmt_to_fit.get_lot(acct_map, ar_map);
|
||||
|
||||
let mut mut_sum_of_mvmts_in_lot: d128 = d128!(0.0);
|
||||
for movement in lot.movements.borrow().iter() {
|
||||
mut_sum_of_mvmts_in_lot += movement.amount;
|
||||
}
|
||||
let sum_of_mvmts_in_lot = mut_sum_of_mvmts_in_lot;
|
||||
assert!(sum_of_mvmts_in_lot >= d128!(0.0));
|
||||
|
||||
if sum_of_mvmts_in_lot == d128!(0.0) { // If the lot is "full", go to the next
|
||||
current_index_position += 1;
|
||||
assert!(current_index_position < vec_of_ordered_index_values.len());
|
||||
let lot_index = vec_of_ordered_index_values[current_index_position];
|
||||
let newly_chosen_lot = list_of_lots_to_use.borrow()[lot_index].clone();
|
||||
let possible_mvmt_to_fit = Movement {
|
||||
amount: mvmt_to_fit.amount,
|
||||
date_as_string: mvmt_to_fit.date_as_string.clone(),
|
||||
date: mvmt_to_fit.date,
|
||||
transaction_key: mvmt_to_fit.transaction_key,
|
||||
action_record_key: mvmt_to_fit.action_record_key,
|
||||
cost_basis: mvmt_to_fit.cost_basis,
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0), // Outgoing mvmt, so it's irrelevant
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: newly_chosen_lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
fit_into_lots(
|
||||
acct.raw_key,
|
||||
txn_num,
|
||||
spawning_ar_key,
|
||||
possible_mvmt_to_fit,
|
||||
list_of_lots_to_use,
|
||||
vec_of_ordered_index_values,
|
||||
current_index_position,
|
||||
&settings,
|
||||
&ar_map,
|
||||
&raw_acct_map,
|
||||
&acct_map
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert!(sum_of_mvmts_in_lot > d128!(0.0));
|
||||
let remainder_amt = mvmt_to_fit.amount;
|
||||
// println!("Sum of mvmts in lot: {}; Remainder amount: {}; Net: {}",
|
||||
// sum_of_mvmts_in_lot, remainder_amt, sum_of_mvmts_in_lot + remainder_amt);
|
||||
|
||||
let does_remainder_fit: bool = (sum_of_mvmts_in_lot + remainder_amt) >= d128!(0.0);
|
||||
|
||||
if does_remainder_fit {
|
||||
let remainder_that_fits = Movement {
|
||||
amount: mvmt_to_fit.amount,
|
||||
date_as_string: mvmt_to_fit.date_as_string.clone(),
|
||||
date: mvmt_to_fit.date,
|
||||
transaction_key: mvmt_to_fit.transaction_key,
|
||||
action_record_key: mvmt_to_fit.action_record_key,
|
||||
cost_basis: mvmt_to_fit.cost_basis,
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0), // Outgoing mvmt, so it's irrelevant
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(remainder_that_fits, &spawning_ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
return // And we're done
|
||||
}
|
||||
// Note: at this point, we know the movement doesn't fit in a single lot & sum_of_mvmts_in_lot > 0
|
||||
let mvmt = RefCell::new(mvmt_to_fit);
|
||||
let mvmt_rc = Rc::from(mvmt);
|
||||
|
||||
let mvmt_that_fits_in_lot = Movement {
|
||||
amount: (-sum_of_mvmts_in_lot).reduce(),
|
||||
date_as_string: mvmt_rc.borrow().date_as_string.clone(),
|
||||
date: mvmt_rc.borrow().date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: spawning_ar_key,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0), // Outgoing mvmt, so it's irrelevant
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
wrap_mvmt_and_push(mvmt_that_fits_in_lot, &spawning_ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
let remainder_amt_to_recurse = remainder_amt + sum_of_mvmts_in_lot;
|
||||
// println!("Remainder amount to recurse: {}", remainder_amt_to_recurse);
|
||||
current_index_position += 1;
|
||||
let lot_index = vec_of_ordered_index_values[current_index_position];
|
||||
let newly_chosen_lot = list_of_lots_to_use.borrow()[lot_index].clone();
|
||||
|
||||
let remainder_mvmt_to_recurse = Movement {
|
||||
amount: remainder_amt_to_recurse.reduce(),
|
||||
date_as_string: mvmt_rc.borrow().date_as_string.clone(),
|
||||
date: mvmt_rc.borrow().date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: spawning_ar_key,
|
||||
cost_basis: Cell::new(d128!(0.0)), // This acts as a dummy value.
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0), // Outgoing mvmt, so it's irrelevant
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)), // This acts as a dummy value.
|
||||
lot_num: newly_chosen_lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
assert!(current_index_position < vec_of_ordered_index_values.len());
|
||||
fit_into_lots(
|
||||
acct.raw_key,
|
||||
txn_num,
|
||||
spawning_ar_key,
|
||||
remainder_mvmt_to_recurse,
|
||||
list_of_lots_to_use,
|
||||
vec_of_ordered_index_values,
|
||||
current_index_position,
|
||||
&settings,
|
||||
&ar_map,
|
||||
&raw_acct_map,
|
||||
&acct_map
|
||||
);
|
||||
}
|
||||
|
||||
fn process_multiple_incoming_lots_and_mvmts(
|
||||
txn_num: u32,
|
||||
outgoing_ar: &ActionRecord,
|
||||
incoming_ar: &ActionRecord,
|
||||
settings: &LotProcessingChoices,
|
||||
incoming_ar_key: u32,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
ar_map: &HashMap<u32, ActionRecord>,
|
||||
) {
|
||||
|
||||
let round_to_places = d128::from(1).scaleb(d128::from(-8));
|
||||
let txn = txns_map.get(&txn_num).expect("Couldn't get txn. Tx num invalid?");
|
||||
|
||||
let acct_of_incoming_ar = acct_map.get(&incoming_ar.account_key).unwrap();
|
||||
|
||||
let mut all_but_last_incoming_mvmt_amt = d128!(0.0);
|
||||
let mut all_but_last_incoming_mvmt_ratio = d128!(0.0);
|
||||
// println!("Txn date: {}. Outgoing mvmts: {}, Outgoing amount: {}", txn.date, outgoing_ar.movements.borrow().len(), outgoing_ar.amount);
|
||||
let list_of_mvmts_of_outgoing_ar = outgoing_ar.get_mvmts_in_ar(acct_map, txns_map);
|
||||
let final_mvmt = list_of_mvmts_of_outgoing_ar.last().unwrap();
|
||||
// First iteration, for all but final movement
|
||||
for outgoing_mvmt in list_of_mvmts_of_outgoing_ar
|
||||
.iter()
|
||||
.take(outgoing_ar.get_mvmts_in_ar(acct_map, txns_map).len() - 1) {
|
||||
let ratio_of_outgoing_mvmt_to_total_ar = outgoing_mvmt.amount / outgoing_ar.amount; // Negative divided by negative is positive
|
||||
// println!("Ratio of outgoing amt to total actionrecord amt: {:.8}", ratio_of_outgoing_to_total_ar);
|
||||
let tentative_incoming_amt = ratio_of_outgoing_mvmt_to_total_ar * incoming_ar.amount;
|
||||
// println!("Unrounded incoming amt: {}", tentative_incoming_amt);
|
||||
let corresponding_incoming_amt = tentative_incoming_amt.quantize(round_to_places);
|
||||
// println!("Rounded incoming amt: {}", corresponding_incoming_amt);
|
||||
if corresponding_incoming_amt == d128!(0) { continue } // Due to rounding, this could be zero.
|
||||
assert!(corresponding_incoming_amt > d128!(0.0));
|
||||
let this_acct = acct_of_incoming_ar;
|
||||
let length_of_list_of_lots: usize = this_acct.list_of_lots.borrow().len();
|
||||
let inherited_date = outgoing_mvmt.get_lot(acct_map, ar_map).date_of_first_mvmt_in_lot;
|
||||
let lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: inherited_date,
|
||||
lot_number: length_of_list_of_lots as u32 + 1,
|
||||
account_key: this_acct.raw_key,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
)
|
||||
;
|
||||
let incoming_mvmt = Movement {
|
||||
amount: corresponding_incoming_amt.reduce(),
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: incoming_ar_key,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: round_d128_1e8(&ratio_of_outgoing_mvmt_to_total_ar),
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
// println!("From first set of incoming movements, amount: {} {} to account: {}",
|
||||
// incoming_mvmt.amount, acct_incoming_ar.ticker, acct_incoming_ar.account_num);
|
||||
all_but_last_incoming_mvmt_ratio += round_d128_1e8(&ratio_of_outgoing_mvmt_to_total_ar);
|
||||
all_but_last_incoming_mvmt_amt += incoming_mvmt.amount;
|
||||
wrap_mvmt_and_push(incoming_mvmt, &incoming_ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
this_acct.list_of_lots.borrow_mut().push(lot);
|
||||
}
|
||||
// Second iteration, for final movement
|
||||
let corresponding_incoming_amt = incoming_ar.amount - all_but_last_incoming_mvmt_amt;
|
||||
assert!(corresponding_incoming_amt > d128!(0.0));
|
||||
let this_acct = acct_of_incoming_ar;
|
||||
let length_of_list_of_lots = this_acct.list_of_lots.borrow().len();
|
||||
let inherited_date = final_mvmt.get_lot(acct_map, ar_map).date_of_first_mvmt_in_lot;
|
||||
let lot =
|
||||
Rc::new(
|
||||
Lot {
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date_of_first_mvmt_in_lot: txn.date,
|
||||
date_for_basis_purposes: inherited_date,
|
||||
lot_number: length_of_list_of_lots as u32 + 1,
|
||||
account_key: this_acct.raw_key,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
}
|
||||
)
|
||||
;
|
||||
let incoming_mvmt = Movement {
|
||||
amount: corresponding_incoming_amt.reduce(),
|
||||
date_as_string: txn.date_as_string.clone(),
|
||||
date: txn.date,
|
||||
transaction_key: txn_num,
|
||||
action_record_key: incoming_ar_key,
|
||||
cost_basis: Cell::new(d128!(0.0)),
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0) - all_but_last_incoming_mvmt_ratio,
|
||||
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
|
||||
lot_num: lot.lot_number,
|
||||
proceeds: Cell::new(d128!(0.0)),
|
||||
};
|
||||
// println!("Final incoming mvmt for this actionrecord, amount: {} {} to account: {}",
|
||||
// incoming_mvmt.amount, acct_incoming_ar.ticker, acct_incoming_ar.account_num);
|
||||
wrap_mvmt_and_push(incoming_mvmt, &incoming_ar, &lot, &settings, &raw_acct_map, &acct_map);
|
||||
this_acct.list_of_lots.borrow_mut().push(lot);
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::rc::{Rc};
|
||||
use std::fs::File;
|
||||
use std::collections::{HashMap};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use decimal::d128;
|
||||
|
||||
use crate::transaction::{Transaction, ActionRecord, Polarity, TxType};
|
||||
use crate::account::{Account, RawAccount, Term};
|
||||
use crate::user_choices::{LotProcessingChoices};
|
||||
|
||||
|
||||
pub fn _1_account_sums_to_csv(
|
||||
settings: &LotProcessingChoices,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>
|
||||
) {
|
||||
|
||||
let mut rows: Vec<Vec<String>> = [].to_vec();
|
||||
let mut header: Vec<String> = [].to_vec();
|
||||
|
||||
header.extend_from_slice(&[
|
||||
"Account".to_string(),
|
||||
"Balance".to_string(),
|
||||
"Ticker".to_string(),
|
||||
"Cost Basis".to_string(),
|
||||
"Total lots".to_string(),
|
||||
]);
|
||||
rows.push(header);
|
||||
|
||||
let length = acct_map.len();
|
||||
|
||||
for j in 1..=length {
|
||||
|
||||
let acct = acct_map.get(&(j as u16)).unwrap();
|
||||
let mut row: Vec<String> = [].to_vec();
|
||||
|
||||
let balance: String;
|
||||
let tentative_balance = acct.get_sum_of_amts_in_lots();
|
||||
|
||||
if tentative_balance == d128!(0) {
|
||||
balance = "0.00".to_string()
|
||||
} else { balance = tentative_balance.to_string() }
|
||||
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
let cost_basis: String;
|
||||
|
||||
if raw_acct.is_margin { cost_basis = "0.00".to_string() } else {
|
||||
let tentative_cost_basis = acct.get_sum_of_basis_in_lots();
|
||||
if tentative_cost_basis == d128!(0) {
|
||||
cost_basis = "0.00".to_string()
|
||||
} else { cost_basis = tentative_cost_basis.to_string() }
|
||||
}
|
||||
|
||||
row.push(raw_acct.name.to_string());
|
||||
row.push(balance);
|
||||
row.push(raw_acct.ticker.to_string());
|
||||
row.push(cost_basis);
|
||||
row.push(acct.list_of_lots.borrow().len().to_string());
|
||||
rows.push(row);
|
||||
}
|
||||
let file_name = PathBuf::from("1_Acct_Sum_with_cost_basis.csv");
|
||||
let path = PathBuf::from(&settings.export_path.clone());
|
||||
|
||||
let full_path: PathBuf = [path, file_name].iter().collect();
|
||||
let buffer = File::create(full_path).unwrap();
|
||||
let mut wtr = csv::Writer::from_writer(buffer);
|
||||
|
||||
for row in rows.iter() {
|
||||
wtr.write_record(row).expect("Could not write row to CSV file");
|
||||
}
|
||||
wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
|
||||
}
|
||||
|
||||
pub fn _2_account_sums_nonzero_to_csv(
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
settings: &LotProcessingChoices,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>
|
||||
) {
|
||||
|
||||
let mut rows: Vec<Vec<String>> = [].to_vec();
|
||||
let mut header: Vec<String> = [].to_vec();
|
||||
|
||||
header.extend_from_slice(&[
|
||||
"Account".to_string(),
|
||||
"Balance".to_string(),
|
||||
"Ticker".to_string(),
|
||||
"Cost basis".to_string(),
|
||||
"Total lots".to_string(),
|
||||
]);
|
||||
rows.push(header);
|
||||
|
||||
let length = acct_map.len();
|
||||
|
||||
for j in 1..=length {
|
||||
|
||||
let acct = acct_map.get(&(j as u16)).unwrap();
|
||||
let mut row: Vec<String> = [].to_vec();
|
||||
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
let name = raw_acct.name.to_string();
|
||||
|
||||
let balance: String;
|
||||
let mut balance_d128 = d128!(0);
|
||||
let tentative_balance = acct.get_sum_of_amts_in_lots();
|
||||
|
||||
if tentative_balance == d128!(0) {
|
||||
balance = "0.00".to_string()
|
||||
} else { balance_d128 += tentative_balance; balance = tentative_balance.to_string() }
|
||||
|
||||
let cost_basis: String;
|
||||
|
||||
if raw_acct.is_margin { cost_basis = "0.00".to_string() } else {
|
||||
let tentative_cost_basis = acct.get_sum_of_basis_in_lots();
|
||||
if tentative_cost_basis == d128!(0) {
|
||||
cost_basis = "0.00".to_string()
|
||||
} else { cost_basis = tentative_cost_basis.to_string() }
|
||||
}
|
||||
|
||||
if balance_d128 != d128!(0) {
|
||||
row.push(name);
|
||||
row.push(balance);
|
||||
row.push(raw_acct.ticker.to_string());
|
||||
row.push(cost_basis);
|
||||
row.push(acct.list_of_lots.borrow().len().to_string());
|
||||
rows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
let file_name = PathBuf::from("2_Acct_Sum_with_nonzero_cost_basis.csv");
|
||||
let path = PathBuf::from(&settings.export_path.clone());
|
||||
|
||||
let full_path: PathBuf = [path, file_name].iter().collect();
|
||||
let buffer = File::create(full_path).unwrap();
|
||||
let mut wtr = csv::Writer::from_writer(buffer);
|
||||
|
||||
for row in rows.iter() {
|
||||
wtr.write_record(row).expect("Could not write row to CSV file");
|
||||
}
|
||||
wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
|
||||
}
|
||||
|
||||
// pub fn transactions_to_csv(
|
||||
// transactions: &[Rc<Transaction>],
|
||||
// ars: &HashMap<u32, ActionRecord>,
|
||||
// raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
// acct_map: &HashMap<u16, Account>,
|
||||
// txns_map: &HashMap<u32, Transaction>,) {
|
||||
|
||||
// let mut rows: Vec<Vec<String>> = [].to_vec();
|
||||
// let mut header: Vec<String> = [].to_vec();
|
||||
// header.extend_from_slice(&[
|
||||
// "Date".to_string(),
|
||||
// "Txn#".to_string(),
|
||||
// "Type".to_string(),
|
||||
// "Memo".to_string(),
|
||||
// "Amount".to_string(),
|
||||
// "Ticker".to_string(),
|
||||
// "Proceeds".to_string(),
|
||||
// "Cost basis".to_string(),
|
||||
// "Gain/loss".to_string(),
|
||||
// "Term".to_string(),
|
||||
// "Income".to_string(),
|
||||
// "Expense".to_string(),
|
||||
// ]);
|
||||
// rows.push(header);
|
||||
// for txn in transactions {
|
||||
// for mvmt in txn.flow_or_outgoing_exchange_movements.borrow().iter() {
|
||||
// let lot = mvmt.borrow().get_lot(acct_map, ars);
|
||||
// let acct = acct_map.get(&lot.account_key).unwrap();
|
||||
// let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
// let mut row: Vec<String> = [].to_vec();
|
||||
// row.push(txn.date.format("%Y/%m/%d").to_string());
|
||||
// row.push(txn.tx_number.to_string());
|
||||
// row.push(txn.transaction_type(&ars, &raw_acct_map, &acct_map).to_string());
|
||||
// row.push(txn.memo.to_string());
|
||||
// row.push(mvmt.borrow().amount.to_string());
|
||||
// row.push(raw_acct.ticker.to_string());
|
||||
// row.push(mvmt.borrow().proceeds.to_string());
|
||||
// row.push(mvmt.borrow().cost_basis.to_string());
|
||||
// row.push(mvmt.borrow().get_gain_or_loss().to_string());
|
||||
// row.push(mvmt.borrow().get_term(acct_map, ars).to_string());
|
||||
// row.push(mvmt.borrow().get_income(ars, &raw_acct_map, &acct_map, &txns_map).to_string());
|
||||
// row.push(mvmt.borrow().get_expense(ars, &raw_acct_map, &acct_map, &txns_map).to_string());
|
||||
// rows.push(row);
|
||||
// }
|
||||
// }
|
||||
// let buffer = File::create("/Users/scoob/Documents/Testing/rust_exports/test/txns-3rd-try.csv").unwrap();
|
||||
// let mut wtr = csv::Writer::from_writer(buffer);
|
||||
// for row in rows.iter() {
|
||||
// wtr.write_record(row).expect("Could not write row to CSV file");
|
||||
// }
|
||||
// wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
|
||||
// }
|
||||
|
||||
pub fn _5_transaction_mvmt_summaries_to_csv(
|
||||
settings: &LotProcessingChoices,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) {
|
||||
|
||||
let mut rows: Vec<Vec<String>> = [].to_vec();
|
||||
let mut header: Vec<String> = [].to_vec();
|
||||
|
||||
header.extend_from_slice(&[
|
||||
"Date".to_string(),
|
||||
"Txn#".to_string(),
|
||||
"Type".to_string(),
|
||||
"Memo".to_string(),
|
||||
"Amount".to_string(),
|
||||
"Ticker".to_string(),
|
||||
"Term".to_string(),
|
||||
"Proceeds".to_string(),
|
||||
"Cost basis".to_string(),
|
||||
"Gain/loss".to_string(),
|
||||
"Income".to_string(),
|
||||
"Expense".to_string(),
|
||||
]);
|
||||
rows.push(header);
|
||||
|
||||
let length = txns_map.len();
|
||||
|
||||
for txn_num in 1..=length {
|
||||
|
||||
let txn_num = txn_num as u32;
|
||||
let txn = txns_map.get(&(txn_num)).unwrap();
|
||||
let txn_date_string = txn.date.format("%Y/%m/%d").to_string();
|
||||
let tx_num_string = txn.tx_number.to_string();
|
||||
let tx_type_string = txn.transaction_type(ars, &raw_acct_map, &acct_map).to_string();
|
||||
let tx_memo_string = txn.memo.to_string();
|
||||
let mut term_st: Option<Term> = None;
|
||||
let mut term_lt: Option<Term> = None;
|
||||
let mut ticker: Option<String> = None;
|
||||
let mut polarity: Option<Polarity> = None;
|
||||
|
||||
let mut amount_st = d128!(0);
|
||||
let mut proceeds_st = d128!(0);
|
||||
let mut cost_basis_st = d128!(0);
|
||||
|
||||
let mut income_st = d128!(0);
|
||||
let mut expense_st = d128!(0);
|
||||
|
||||
let mut amount_lt = d128!(0);
|
||||
let mut proceeds_lt = d128!(0);
|
||||
let mut cost_basis_lt = d128!(0);
|
||||
|
||||
let mut income_lt = d128!(0);
|
||||
let mut expense_lt = d128!(0);
|
||||
|
||||
let flow_or_outgoing_exchange_movements = txn.get_outgoing_exchange_and_flow_mvmts(
|
||||
settings,
|
||||
ars,
|
||||
raw_acct_map,
|
||||
acct_map,
|
||||
txns_map
|
||||
);
|
||||
|
||||
for mvmt in flow_or_outgoing_exchange_movements.iter() {
|
||||
let lot = mvmt.get_lot(acct_map, ars);
|
||||
let acct = acct_map.get(&lot.account_key).unwrap();
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
|
||||
if let None = ticker { ticker = Some(raw_acct.ticker.clone()) };
|
||||
|
||||
if let None = polarity {
|
||||
polarity = if mvmt.amount > d128!(0) {
|
||||
Some(Polarity::Incoming)
|
||||
} else { Some(Polarity::Outgoing)
|
||||
};
|
||||
}
|
||||
|
||||
let term = mvmt.get_term(acct_map, ars);
|
||||
|
||||
if term == Term::LT {
|
||||
amount_lt += mvmt.amount;
|
||||
proceeds_lt += mvmt.proceeds.get();
|
||||
cost_basis_lt += mvmt.cost_basis.get();
|
||||
match term_lt {
|
||||
None => { term_lt = Some(term)}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
assert_eq!(term, Term::ST);
|
||||
amount_st += mvmt.amount;
|
||||
proceeds_st += mvmt.proceeds.get();
|
||||
cost_basis_st += mvmt.cost_basis.get();
|
||||
if term_st == None {
|
||||
term_st = Some(term);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (txn.transaction_type(ars, &raw_acct_map, &acct_map) == TxType::Flow) & (polarity == Some(Polarity::Incoming)) {
|
||||
// println!("Incoming flow {}", txn.tx_number);
|
||||
income_st = proceeds_st;
|
||||
proceeds_st = d128!(0);
|
||||
cost_basis_st = d128!(0);
|
||||
income_lt = proceeds_lt;
|
||||
proceeds_lt = d128!(0);
|
||||
cost_basis_lt = d128!(0);
|
||||
}
|
||||
|
||||
if (txn.transaction_type(ars, &raw_acct_map, &acct_map) == TxType::Flow) & (polarity == Some(Polarity::Outgoing)) {
|
||||
// println!("Outgoing flow {}, proceeds_st {}, proceeds_lt {}", txn.tx_number, proceeds_st, proceeds_lt);
|
||||
expense_st -= proceeds_st;
|
||||
expense_lt -= proceeds_lt;
|
||||
}
|
||||
|
||||
if let Some(term) = term_st {
|
||||
|
||||
let mut row: Vec<String> = [].to_vec();
|
||||
|
||||
row.push(txn_date_string.clone());
|
||||
row.push(tx_num_string.clone());
|
||||
row.push(tx_type_string.clone());
|
||||
row.push(tx_memo_string.clone());
|
||||
row.push(amount_st.to_string());
|
||||
row.push(ticker.clone().unwrap());
|
||||
row.push(term.abbr_string());
|
||||
row.push(proceeds_st.to_string());
|
||||
row.push(cost_basis_st.to_string());
|
||||
row.push((proceeds_st + cost_basis_st).to_string());
|
||||
row.push(income_st.to_string());
|
||||
row.push(expense_st.to_string());
|
||||
|
||||
rows.push(row);
|
||||
}
|
||||
if let Some(term) = term_lt {
|
||||
|
||||
let mut row: Vec<String> = [].to_vec();
|
||||
|
||||
row.push(txn_date_string);
|
||||
row.push(tx_num_string);
|
||||
row.push(tx_type_string);
|
||||
row.push(tx_memo_string);
|
||||
row.push(amount_lt.to_string());
|
||||
row.push(ticker.unwrap());
|
||||
row.push(term.abbr_string());
|
||||
row.push(proceeds_lt.to_string());
|
||||
row.push(cost_basis_lt.to_string());
|
||||
row.push((proceeds_lt + cost_basis_lt).to_string());
|
||||
row.push(income_lt.to_string());
|
||||
row.push(expense_lt.to_string());
|
||||
|
||||
rows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
let file_name = PathBuf::from("5_Txns_mvmts_summary.csv");
|
||||
let path = PathBuf::from(&settings.export_path);
|
||||
|
||||
let full_path: PathBuf = [path, file_name].iter().collect();
|
||||
let buffer = File::create(full_path).unwrap();
|
||||
let mut wtr = csv::Writer::from_writer(buffer);
|
||||
|
||||
for row in rows.iter() {
|
||||
wtr.write_record(row).expect("Could not write row to CSV file");
|
||||
}
|
||||
wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
|
||||
}
|
||||
|
||||
pub fn accounts_to_csv(
|
||||
accounts: &[Rc<Account>],
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) {
|
||||
|
||||
let mut rows: Vec<Vec<String>> = [].to_vec();
|
||||
let mut header: Vec<String> = [].to_vec();
|
||||
|
||||
header.extend_from_slice(&[
|
||||
"#".to_string(),
|
||||
"Account".to_string(),
|
||||
"Ticker".to_string(),
|
||||
"Margin".to_string(),
|
||||
"Date".to_string(),
|
||||
"Txn#".to_string(),
|
||||
"Type".to_string(),
|
||||
"Memo".to_string(),
|
||||
"Amount".to_string(),
|
||||
"Proceeds".to_string(),
|
||||
"Cost basis\n".to_string(),
|
||||
"Gain/loss".to_string(),
|
||||
"Term".to_string(),
|
||||
"Income".to_string(),
|
||||
"Expense".to_string(),
|
||||
]);
|
||||
rows.push(header);
|
||||
|
||||
for acct in accounts {
|
||||
for lot in acct.list_of_lots.borrow().iter() {
|
||||
for mvmt in lot.movements.borrow().iter() {
|
||||
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
let txn = txns_map.get(&mvmt.transaction_key).unwrap();
|
||||
let mut row: Vec<String> = [].to_vec();
|
||||
|
||||
row.push(raw_acct.account_num.to_string());
|
||||
row.push(raw_acct.name.to_string());
|
||||
row.push(raw_acct.ticker.to_string());
|
||||
row.push(raw_acct.is_margin.to_string());
|
||||
row.push(mvmt.date.format("%Y/%m/%d").to_string());
|
||||
row.push(txn.tx_number.to_string());
|
||||
row.push(txn.transaction_type(ars, &raw_acct_map, &acct_map).to_string());
|
||||
row.push(txn.memo.to_string());
|
||||
row.push(mvmt.amount.to_string());
|
||||
row.push(mvmt.proceeds.get().to_string());
|
||||
row.push(mvmt.cost_basis.get().to_string());
|
||||
row.push(mvmt.get_gain_or_loss().to_string());
|
||||
row.push(mvmt.get_term(acct_map, ars).to_string());
|
||||
row.push(mvmt.get_income(ars, &raw_acct_map, &acct_map, &txns_map).to_string());
|
||||
row.push(mvmt.get_expense(ars, &raw_acct_map, &acct_map, &txns_map).to_string());
|
||||
|
||||
rows.push(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let buffer = File::create("/Users/scoob/Documents/Testing/rust_exports/test/accts-1st-try.csv").unwrap();
|
||||
let mut wtr = csv::Writer::from_writer(buffer);
|
||||
|
||||
for row in rows.iter() {
|
||||
wtr.write_record(row).expect("Could not write row to CSV file");
|
||||
}
|
||||
wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::error::Error;
|
||||
use std::process;
|
||||
use std::fs::File;
|
||||
use std::cell::{RefCell};
|
||||
use std::collections::{HashMap};
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use decimal::d128;
|
||||
|
||||
use crate::transaction::{Transaction, ActionRecord};
|
||||
use crate::account::{Account, RawAccount};
|
||||
use crate::utils::{round_d128_1e8};
|
||||
|
||||
|
||||
pub fn import_accounts(
|
||||
rdr: &mut csv::Reader<File>,
|
||||
raw_acct_map: &mut HashMap<u16, RawAccount>,
|
||||
acct_map: &mut HashMap<u16, Account>,
|
||||
) -> Result<(), Box<Error>> {
|
||||
|
||||
let header1: csv::StringRecord;
|
||||
header1 = rdr.headers()?.clone(); // account_num
|
||||
|
||||
// Declare remaining headers, and initialize with None
|
||||
let mut header2: Option<csv::StringRecord> = None; // name
|
||||
let mut header3: Option<csv::StringRecord> = None; // ticker
|
||||
let header4: csv::StringRecord; // is_margin
|
||||
|
||||
// Declare string vector (array) to which [all but the first three] Strings of the header row will be appended
|
||||
let mut headerstrings: Vec<String> = [].to_vec();
|
||||
|
||||
// Basically, append three empty strings, and then the list of account numbers one by one
|
||||
for element in header1.into_iter() {
|
||||
match element { // Previously had `let element_str: String = element.to_string();` and then `match &*element_str`
|
||||
"txDate" => { headerstrings.push("".to_string()) },
|
||||
"proceeds" => { headerstrings.push("".to_string()) },
|
||||
"memo" => { headerstrings.push("".to_string()) },
|
||||
_ => headerstrings.push(element.to_string())
|
||||
};
|
||||
} // End result is headerstrings = ["","","",1,2,3...n]
|
||||
|
||||
// Account Creation loop. Iterate through 'data' records. We set hasheaders() to true above, so the first record here is the second row of the CSV
|
||||
for result in rdr.records() {
|
||||
// This initial iteration through records will break after the 4th row, after accounts have been created
|
||||
let record = result?;
|
||||
if header2 == None {
|
||||
header2 = Some(record.clone());
|
||||
continue // After header2 is set, continue to next record
|
||||
}
|
||||
else if header3 == None {
|
||||
header3 = Some(record.clone());
|
||||
continue // After header3 is set, continue to next record
|
||||
}
|
||||
else {
|
||||
header4 = record.clone();
|
||||
// println!("Assigned last header, record: {:?}", record);
|
||||
|
||||
let warn = "FATAL: Transactions will not import correctly if account numbers in the CSV import file aren't ordered chronologically (i.e., beginning in column 4 - the 1st account column - the values should be 1. The next column should be 2, then 3, etc, until the final account).";
|
||||
|
||||
// We've got all our header rows. It's now that we set up the accounts.
|
||||
println!("Attempting to create accounts...");
|
||||
|
||||
let mut no_dup_acct_nums = HashMap::new();
|
||||
let length = &headerstrings.len();
|
||||
|
||||
for num in headerstrings[3..*length].iter().enumerate() {
|
||||
let counter = no_dup_acct_nums.entry(num).or_insert(0);
|
||||
*counter += 1;
|
||||
}
|
||||
|
||||
for acct_num in no_dup_acct_nums.keys() {
|
||||
assert_eq!(no_dup_acct_nums[acct_num], 1, "Found accounts with duplicate numbers during import.");
|
||||
}
|
||||
|
||||
for (idx, item) in headerstrings[3..*length].iter().enumerate() {
|
||||
|
||||
// println!("Headerstrings value: {:?}", item);
|
||||
let ind = idx+3; // Add three because the idx skips the first three 'key' columns
|
||||
let account_num = item.parse::<u16>()?;
|
||||
assert_eq!((idx + 1) as u16, account_num, "Found improper Account Number usage: {}", warn);
|
||||
let name:String = header2.clone().unwrap()[ind].trim().to_string();
|
||||
let ticker:String = header3.clone().unwrap()[ind].trim().to_string();
|
||||
let margin_string = &header4.clone()[ind];
|
||||
|
||||
let is_margin:bool = match margin_string.trim().to_lowercase().as_str() {
|
||||
"no" | "non" | "false" => false,
|
||||
"yes" | "margin" | "true" => true,
|
||||
_ => { println!("\n Couldn't parse margin value for acct {} {} \n",account_num, name); process::exit(1) }
|
||||
};
|
||||
|
||||
let just_account: RawAccount = RawAccount {
|
||||
account_num: account_num,
|
||||
name: name,
|
||||
ticker: ticker,
|
||||
is_margin: is_margin,
|
||||
};
|
||||
|
||||
raw_acct_map.insert(account_num, just_account);
|
||||
|
||||
let account: Account = Account {
|
||||
raw_key: account_num,
|
||||
list_of_lots: RefCell::new([].to_vec())
|
||||
};
|
||||
|
||||
acct_map.insert(account_num, account);
|
||||
}
|
||||
break // This `break` exits this scope so `accounts` can be accessed in `import_transactions`. The rdr stays put.
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn import_transactions(
|
||||
rdr: &mut csv::Reader<File>,
|
||||
txns_map: &mut HashMap<u32, Transaction>,
|
||||
action_records: &mut HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &mut HashMap<u16, RawAccount>,
|
||||
acct_map: &mut HashMap<u16, Account>,
|
||||
) -> Result<(), Box<Error>> {
|
||||
|
||||
let mut this_tx_number = 0;
|
||||
let mut this_ar_number = 0;
|
||||
let mut changed_action_records = 0;
|
||||
let mut changed_txn_num = Vec::new();
|
||||
|
||||
println!("Attempting to create transactions...");
|
||||
|
||||
for result in rdr.records() {
|
||||
|
||||
// rdr's cursor is at row 5, which is the first transaction row
|
||||
let record = result?;
|
||||
this_tx_number += 1;
|
||||
|
||||
// First, initialize metadata fields.
|
||||
let mut this_tx_date: &str = "";
|
||||
let mut this_proceeds: &str = "";
|
||||
let mut this_memo: &str = "";
|
||||
let mut this: String = "".to_string();
|
||||
|
||||
// Next, create action_records.
|
||||
let mut action_records_map_keys_vec: Vec<u32> = [].to_vec();
|
||||
let mut outgoing_ar: Option<ActionRecord> = None;
|
||||
let mut incoming_ar: Option<ActionRecord> = None;
|
||||
let mut outgoing_ar_num: Option<u32> = None;
|
||||
let mut incoming_ar_num: Option<u32> = None;
|
||||
|
||||
for (idx, field) in record.iter().enumerate() {
|
||||
|
||||
// Set metadata fields on first three fields.
|
||||
if idx == 0 { this_tx_date = field; }
|
||||
else if idx == 1 {
|
||||
this = field.replace(",", "");
|
||||
this_proceeds = this.as_str();
|
||||
}
|
||||
else if idx == 2 { this_memo = field; }
|
||||
|
||||
// Check for empty strings. If not empty, it's a value for an action_record.
|
||||
else if field != "" {
|
||||
this_ar_number += 1;
|
||||
let ind = idx; // starts at 3, which is the fourth field
|
||||
let acct_idx = ind - 2; // acct_num and acct_key would be idx + 1, so subtract 2 from ind to get 1
|
||||
let account_key = acct_idx as u16;
|
||||
|
||||
let amount_str = field.replace(",", "");
|
||||
let amount = amount_str.parse::<d128>().unwrap();
|
||||
let amount_rounded = round_d128_1e8(&amount);
|
||||
if amount != amount_rounded { changed_action_records += 1; changed_txn_num.push(this_tx_number); }
|
||||
|
||||
let action_record = ActionRecord {
|
||||
account_key: account_key,
|
||||
amount: amount_rounded,
|
||||
tx_key: this_tx_number,
|
||||
self_ar_key: this_ar_number,
|
||||
movements: RefCell::new([].to_vec()),
|
||||
};
|
||||
|
||||
if amount > d128!(0.0) {
|
||||
incoming_ar = Some(action_record);
|
||||
incoming_ar_num = Some(this_ar_number);
|
||||
action_records_map_keys_vec.push(incoming_ar_num.unwrap())
|
||||
} else {
|
||||
outgoing_ar = Some(action_record);
|
||||
outgoing_ar_num = Some(this_ar_number);
|
||||
action_records_map_keys_vec.insert(0, outgoing_ar_num.unwrap())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
match incoming_ar {
|
||||
Some(incoming_ar) => {
|
||||
let x = incoming_ar_num.unwrap();
|
||||
action_records.insert(x, incoming_ar);
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
match outgoing_ar {
|
||||
Some(outgoing_ar) => {
|
||||
let y = outgoing_ar_num.unwrap();
|
||||
action_records.insert(y, outgoing_ar);
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
let tx_date = NaiveDate::parse_from_str(this_tx_date, "%m/%d/%y")
|
||||
.unwrap_or(NaiveDate::parse_from_str(this_tx_date, "%m/%d/%Y")
|
||||
.expect("%m/%d/%y (or %Y) format required for ledger import")
|
||||
);
|
||||
|
||||
let transaction = Transaction {
|
||||
tx_number: this_tx_number,
|
||||
date_as_string: this_tx_date.to_string(),
|
||||
date: tx_date,
|
||||
memo: this_memo.to_string(),
|
||||
proceeds: this_proceeds.parse::<f32>()?,
|
||||
action_record_idx_vec: action_records_map_keys_vec,
|
||||
};
|
||||
|
||||
txns_map.insert(this_tx_number, transaction);
|
||||
};
|
||||
|
||||
if changed_action_records > 0 {
|
||||
println!(" Changed actionrecord amounts: {}. Changed txn numbers: {:?}.", changed_action_records, changed_txn_num);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::collections::{HashMap};
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use decimal::d128;
|
||||
|
||||
use crate::transaction::{Transaction, TxType, ActionRecord, Polarity};
|
||||
use crate::account::{Account, RawAccount};
|
||||
use crate::utils::{round_d128_1e2};
|
||||
use crate::user_choices::{LotProcessingChoices};
|
||||
|
||||
pub fn add_cost_basis_to_movements(
|
||||
settings: &LotProcessingChoices,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) {
|
||||
|
||||
let length = txns_map.len();
|
||||
|
||||
for txn_num in 1..=length {
|
||||
|
||||
let txn_num = txn_num as u32;
|
||||
let txn = txns_map.get(&(txn_num)).unwrap();
|
||||
|
||||
for ar_num in txn.action_record_idx_vec.iter() {
|
||||
|
||||
let ar = ars.get(ar_num).unwrap();
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
|
||||
|
||||
for mvmt in movements.iter() {
|
||||
|
||||
let polarity = ar.direction();
|
||||
let tx_type = txn.transaction_type(ars, raw_acct_map, acct_map);
|
||||
let is_home_curr = raw_acct.is_home_currency(&settings.home_currency);
|
||||
let mvmt_copy = mvmt.clone();
|
||||
let borrowed_mvmt = mvmt_copy.clone();
|
||||
// println!("Txn: {} on {} of type: {:?}",txn.tx_number,txn.date, txn.transaction_type());
|
||||
|
||||
if !raw_acct.is_margin {
|
||||
match polarity {
|
||||
Polarity::Outgoing => {
|
||||
if is_home_curr {
|
||||
let mvmts_amt = mvmt_copy.amount;
|
||||
mvmt.cost_basis.set(mvmts_amt);
|
||||
} else {
|
||||
let mvmt_lot = mvmt_copy.get_lot(acct_map, ars);
|
||||
let borrowed_mvmt_list = mvmt_lot.movements.borrow().clone();
|
||||
let lots_first_mvmt = borrowed_mvmt_list.first().unwrap().clone();
|
||||
let cb_of_lots_first_mvmt = lots_first_mvmt.cost_basis.get();
|
||||
let ratio_of_amt_to_lots_first_mvmt = borrowed_mvmt.ratio_of_amt_to_lots_first_mvmt(acct_map, ars);
|
||||
let unrounded_basis = -(cb_of_lots_first_mvmt * ratio_of_amt_to_lots_first_mvmt);
|
||||
let rounded_basis = round_d128_1e2(&unrounded_basis);
|
||||
mvmt.cost_basis.set(rounded_basis);
|
||||
// if txn.tx_number == 5 {
|
||||
// println!("Txn#: {}, ratio: {}, cb of lot's 1st mvmt: {}, cost basis: {}", txn.tx_number, ratio_of_amt_to_lots_first_mvmt, cb_of_lots_first_mvmt, cost_basis)
|
||||
// };
|
||||
}
|
||||
// println!("Outgoing a_r from txn: {} of type: {:?}, with {} {} with cost basis: {}",
|
||||
// txn.tx_number, txn.transaction_type(), borrowed_mvmt.amount, ar.account.ticker, cost_basis);
|
||||
assert!(mvmt.cost_basis.get() <= d128!(0));
|
||||
continue
|
||||
}
|
||||
Polarity::Incoming => {
|
||||
if is_home_curr {
|
||||
let mvmts_amt = mvmt_copy.amount;
|
||||
mvmt.cost_basis.set(mvmts_amt);
|
||||
} else {
|
||||
match tx_type {
|
||||
TxType::Exchange => {
|
||||
let other_ar = ars.get(&txn.action_record_idx_vec[0]).unwrap();
|
||||
let other_acct = acct_map.get(&other_ar.account_key).unwrap();
|
||||
let raw_other_acct = raw_acct_map.get(&other_acct.raw_key).unwrap();
|
||||
assert_eq!(other_ar.direction(), Polarity::Outgoing);
|
||||
let other_ar_is_home_curr = raw_other_acct.is_home_currency(&settings.home_currency);
|
||||
|
||||
if other_ar_is_home_curr {
|
||||
mvmt.cost_basis.set(-(other_ar.amount));
|
||||
} else {
|
||||
|
||||
let ratio_of_amt_to_incoming_mvmts_in_a_r =
|
||||
borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r;
|
||||
let txn_proceeds = txn.proceeds.to_string().parse::<d128>().unwrap();
|
||||
let unrounded_basis = txn_proceeds * ratio_of_amt_to_incoming_mvmts_in_a_r;
|
||||
let rounded_basis = round_d128_1e2(&unrounded_basis);
|
||||
mvmt.cost_basis.set(rounded_basis);
|
||||
}
|
||||
}
|
||||
TxType::ToSelf => {
|
||||
let cb_outgoing_ar = retrieve_cost_basis_from_corresponding_outgoing_toself(
|
||||
txn_num, &ars, txns_map, acct_map);
|
||||
let ratio_of_amt_to_incoming_mvmts_in_a_r = borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r;
|
||||
let this_ratio = (ratio_of_amt_to_incoming_mvmts_in_a_r)
|
||||
.to_string()
|
||||
.parse::<d128>()
|
||||
.unwrap();
|
||||
let unrounded_basis = cb_outgoing_ar * this_ratio;
|
||||
let rounded_basis = round_d128_1e2(&unrounded_basis);
|
||||
mvmt.cost_basis.set(-rounded_basis);
|
||||
}
|
||||
TxType::Flow => {
|
||||
let txn_proceeds = txn.proceeds.to_string().parse::<d128>().unwrap();
|
||||
let mvmt_proceeds = round_d128_1e2(&(txn_proceeds *
|
||||
borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r));
|
||||
mvmt.cost_basis.set(mvmt_proceeds);
|
||||
}
|
||||
}
|
||||
}
|
||||
// println!("Incoming a_r from txn: {} of type: {:?}, with {} {} with cost basis: {}",
|
||||
// txn.tx_number, txn.transaction_type(), borrowed_mvmt.amount, ar.account.ticker, cost_basis);
|
||||
assert!(mvmt.cost_basis.get() >= d128!(0));
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn retrieve_cost_basis_from_corresponding_outgoing_toself(
|
||||
txn_num: u32,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
) -> d128 {
|
||||
|
||||
let txn = txns_map.get(&txn_num).unwrap();
|
||||
let other_ar_borrowed = &ars.get(&txn.action_record_idx_vec[0]).unwrap();
|
||||
|
||||
assert_eq!(other_ar_borrowed.direction(), Polarity::Outgoing);
|
||||
|
||||
let mut basis = d128!(0);
|
||||
let movements = other_ar_borrowed.get_mvmts_in_ar(acct_map, txns_map);
|
||||
|
||||
for mvmt in movements.iter() {
|
||||
basis += mvmt.cost_basis.get();
|
||||
}
|
||||
|
||||
basis
|
||||
};
|
||||
}
|
||||
|
||||
pub fn add_proceeds_to_movements(
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) {
|
||||
|
||||
let length = txns_map.len();
|
||||
|
||||
for txn_num in 1..=length {
|
||||
|
||||
let txn_num = txn_num as u32;
|
||||
let txn = txns_map.get(&(txn_num)).unwrap();
|
||||
|
||||
for ar_num in txn.action_record_idx_vec.iter() {
|
||||
|
||||
let ar = ars.get(ar_num).unwrap();
|
||||
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
|
||||
|
||||
for mvmt in movements.iter() {
|
||||
|
||||
let polarity = ar.direction();
|
||||
let tx_type = txn.transaction_type(ars, raw_acct_map, acct_map);
|
||||
let mvmt_copy = mvmt.clone();
|
||||
let borrowed_mvmt = mvmt_copy.clone();
|
||||
|
||||
match tx_type {
|
||||
TxType::Exchange => {
|
||||
match polarity {
|
||||
Polarity::Outgoing => {
|
||||
let ratio = borrowed_mvmt.amount / ar.amount;
|
||||
let proceeds_unrounded = txn.proceeds.to_string().parse::<d128>().unwrap() * ratio;
|
||||
let proceeds_rounded = round_d128_1e2(&proceeds_unrounded);
|
||||
mvmt.proceeds.set(proceeds_rounded);
|
||||
}
|
||||
Polarity::Incoming => {}
|
||||
}
|
||||
}
|
||||
TxType::Flow => {
|
||||
let ratio = borrowed_mvmt.amount / ar.amount;
|
||||
let proceeds_unrounded = txn.proceeds.to_string().parse::<d128>().unwrap() * ratio;
|
||||
let proceeds_rounded = round_d128_1e2(&proceeds_unrounded);
|
||||
mvmt.proceeds.set(proceeds_rounded);
|
||||
}
|
||||
TxType::ToSelf => {}
|
||||
}
|
||||
// println!("Txn: {}, type: {:?} of {} {} w/ proceeds: {} & basis: {}",
|
||||
// txn.tx_number, txn.transaction_type(), borrowed_mvmt.amount, ar.account.ticker, proceeds, borrowed_mvmt.cost_basis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_like_kind_treatment(
|
||||
cutoff_date: NaiveDate,
|
||||
settings: &LotProcessingChoices,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) {
|
||||
|
||||
let length = txns_map.len();
|
||||
for txn_num in 1..=length {
|
||||
|
||||
let txn_num = txn_num as u32;
|
||||
let txn = txns_map.get(&(txn_num)).unwrap();
|
||||
|
||||
update_current_txn_for_prior_likekind_treatment(txn_num, &settings, &ars, &raw_acct_map, &acct_map, &txns_map);
|
||||
|
||||
if txn.date <= cutoff_date {
|
||||
perform_likekind_treatment_on_txn(txn_num, &settings, &ars, &raw_acct_map, &acct_map, &txns_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_current_txn_for_prior_likekind_treatment(
|
||||
txn_num: u32,
|
||||
settings: &LotProcessingChoices,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) {
|
||||
|
||||
let mut sum_of_outgoing_cost_basis_in_ar = d128!(0);
|
||||
let txn = txns_map.get(&txn_num).unwrap();
|
||||
|
||||
for ar_num in txn.action_record_idx_vec.iter() {
|
||||
|
||||
let ar = ars.get(ar_num).unwrap();
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
|
||||
|
||||
for mvmt in movements.iter() {
|
||||
|
||||
let polarity = ar.direction();
|
||||
let tx_type = txn.transaction_type(ars, raw_acct_map, acct_map);
|
||||
let is_home_curr = raw_acct.is_home_currency(&settings.home_currency);
|
||||
|
||||
let mvmt_copy = mvmt.clone();
|
||||
let borrowed_mvmt = mvmt_copy.clone();
|
||||
|
||||
if !raw_acct.is_margin {
|
||||
match polarity {
|
||||
Polarity::Outgoing => {
|
||||
if !is_home_curr {
|
||||
let borrowed_mvmt_lot = borrowed_mvmt.get_lot(acct_map, ars);
|
||||
let borrowed_mvmt_list = borrowed_mvmt_lot.movements.borrow();
|
||||
let cb_of_lots_first_mvmt = borrowed_mvmt_list.first().unwrap().cost_basis.get();
|
||||
let ratio_of_amt_to_lots_first_mvmt = borrowed_mvmt.ratio_of_amt_to_lots_first_mvmt(acct_map, ars);
|
||||
let unrounded_basis = -(cb_of_lots_first_mvmt * ratio_of_amt_to_lots_first_mvmt);
|
||||
let rounded_basis = round_d128_1e2(&unrounded_basis);
|
||||
mvmt.cost_basis.set(rounded_basis);
|
||||
}
|
||||
sum_of_outgoing_cost_basis_in_ar += mvmt.cost_basis.get()
|
||||
}
|
||||
Polarity::Incoming => {
|
||||
match tx_type {
|
||||
TxType::Exchange => {}
|
||||
TxType::Flow => {}
|
||||
TxType::ToSelf => {
|
||||
if !is_home_curr {
|
||||
let ratio_of_amt_to_incoming_mvmts_in_a_r =
|
||||
borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r;
|
||||
let unrounded_basis = sum_of_outgoing_cost_basis_in_ar *
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r;
|
||||
let rounded_basis = round_d128_1e2(&unrounded_basis);
|
||||
mvmt.cost_basis.set(-rounded_basis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_likekind_treatment_on_txn(
|
||||
txn_num: u32,
|
||||
settings: &LotProcessingChoices,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) {
|
||||
|
||||
let txn = txns_map.get(&txn_num).unwrap();
|
||||
let tx_type = txn.transaction_type(ars, raw_acct_map, acct_map);
|
||||
|
||||
let og_ar = ars.get(&txn.action_record_idx_vec.first().unwrap()).unwrap();
|
||||
let ic_ar = ars.get(&txn.action_record_idx_vec.last().unwrap()).unwrap();
|
||||
let og_acct = acct_map.get(&og_ar.account_key).unwrap();
|
||||
let ic_acct = acct_map.get(&ic_ar.account_key).unwrap();
|
||||
let raw_og_acct = raw_acct_map.get(&og_acct.raw_key).unwrap();
|
||||
let raw_ic_acct = raw_acct_map.get(&ic_acct.raw_key).unwrap();
|
||||
|
||||
fn both_are_non_home_curr(raw_og_acct: &RawAccount, raw_ic_acct: &RawAccount, settings: &LotProcessingChoices) -> bool {
|
||||
let og_is_home_curr = raw_og_acct.is_home_currency(&settings.home_currency);
|
||||
let ic_is_home_curr = raw_ic_acct.is_home_currency(&settings.home_currency);
|
||||
let both_are_non_home_curr = !ic_is_home_curr && !og_is_home_curr;
|
||||
both_are_non_home_curr
|
||||
}
|
||||
|
||||
match tx_type {
|
||||
TxType::Exchange => {
|
||||
if both_are_non_home_curr(raw_og_acct, raw_ic_acct, settings) {
|
||||
let mut sum_of_outgoing_cost_basis_in_ar = d128!(0);
|
||||
for ar_num in txn.action_record_idx_vec.iter() {
|
||||
let ar = ars.get(ar_num).unwrap();
|
||||
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
|
||||
for mvmt in movements.iter() {
|
||||
|
||||
let polarity = ar.direction();
|
||||
|
||||
let mvmt_copy = mvmt.clone();
|
||||
let borrowed_mvmt = mvmt_copy.clone();
|
||||
|
||||
match polarity {
|
||||
Polarity::Outgoing => {
|
||||
let cb = borrowed_mvmt.cost_basis.get();
|
||||
sum_of_outgoing_cost_basis_in_ar += cb;
|
||||
mvmt.proceeds.set(-cb);
|
||||
}
|
||||
Polarity::Incoming => {
|
||||
let ratio_of_amt_to_incoming_mvmts_in_a_r =
|
||||
borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r;
|
||||
let unrounded_basis = sum_of_outgoing_cost_basis_in_ar *
|
||||
ratio_of_amt_to_incoming_mvmts_in_a_r;
|
||||
let rounded_basis = round_d128_1e2(&unrounded_basis);
|
||||
mvmt.cost_basis.set(-rounded_basis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TxType::Flow => {
|
||||
if txn.action_record_idx_vec.len() == 2 {
|
||||
for ar_num in txn.action_record_idx_vec.iter() {
|
||||
|
||||
let ar = ars.get(ar_num).unwrap();
|
||||
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
|
||||
|
||||
let polarity = ar.direction();
|
||||
|
||||
for mvmt in movements.iter() {
|
||||
|
||||
match polarity {
|
||||
Polarity::Outgoing => {}
|
||||
Polarity::Incoming => {
|
||||
// Reminder: May need extra logic here if margin exchange trades get cost_basis and proceeds
|
||||
mvmt.cost_basis.set(d128!(0));
|
||||
mvmt.proceeds.set(d128!(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TxType::ToSelf => {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(unused_assignments)]
|
||||
// Note: the above are possibly temporary, to silence "x was not used" warnings.
|
||||
// #[warn(dead_code)] is the default (same for unused_variables)
|
||||
|
||||
|
||||
use std::ffi::OsString;
|
||||
use structopt::StructOpt;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::io::{self, BufRead};
|
||||
use std::error::Error;
|
||||
|
||||
mod account;
|
||||
mod transaction;
|
||||
mod core_functions;
|
||||
mod import_accts_txns;
|
||||
mod create_lots_mvmts;
|
||||
mod import_cost_proceeds_etc;
|
||||
mod user_choices;
|
||||
mod export;
|
||||
mod utils;
|
||||
mod tests;
|
||||
|
||||
use crate::user_choices::LotProcessingChoices;
|
||||
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(name = "cryptools-rs")]
|
||||
struct Cli {
|
||||
|
||||
/// File to be imported. (Currently, the only supported date format is %m/%d/%y.)
|
||||
#[structopt(name = "file", parse(from_os_str))]
|
||||
file_to_import: Option<PathBuf>,
|
||||
|
||||
/// Output directory for exported reports.
|
||||
#[structopt(name = "output directory", short, long = "output", default_value = ".", parse(from_os_str))]
|
||||
output_dir_path: PathBuf,
|
||||
|
||||
/// When the 'accept' flag is set, this program will ultimately export CSV files to either the default or chosen 'output directory' unless this flag is set.
|
||||
#[structopt(name = "suppress reports", short, long = "suppress")]
|
||||
suppress_reports: bool,
|
||||
|
||||
/// Cutoff date through which like-kind exchange treatment should be applied.
|
||||
/// Please use %y-%m-%d (or %Y-%m-%d) format for like-kind cutoff date entry.
|
||||
#[structopt(name = "like-kind cutoff date", short, long = "cutoff", parse(from_os_str))]
|
||||
cutoff_date: Option<OsString>,
|
||||
|
||||
/// Inventory costing method (in terms of lot selection, i.e., LIFO, FIFO, etc.). There are currently four options (1 through 4).
|
||||
#[structopt(name = "method", short, long, default_value = "1", parse(from_os_str), long_help =
|
||||
r" 1. LIFO according to the order the lot was created.
|
||||
2. LIFO according to the basis date of the lot.
|
||||
3. FIFO according to the order the lot was created.
|
||||
4. FIFO according to the basis date of the lot.
|
||||
")]
|
||||
inv_costing_method: OsString,
|
||||
|
||||
/// Home currency (currency in which all resulting reports are denominated). (Only available as a command line setting.)
|
||||
#[structopt(name = "home currency", short = "c", long = "currency", default_value = "USD", parse(from_os_str))]
|
||||
home_currency: OsString,
|
||||
|
||||
/// User is instructing the program to use the command line flags/options/arg they provide without confirming them during runtime. (Faster)
|
||||
#[structopt(name = "accept args", short, long = "accept")]
|
||||
accept_args: bool,
|
||||
}
|
||||
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let args = Cli::from_args();
|
||||
|
||||
println!(
|
||||
"
|
||||
Hello, crypto-folk! Welcome to cryptools-rs!
|
||||
|
||||
This software will import your csv file's ledger of cryptocurrency transactions.
|
||||
It will then process it by creating 'lots' and posting 'movements' to those lots.
|
||||
Along the way, it will keep track of income, expenses, gains, and losses.
|
||||
|
||||
Note: it is designed to import a full history. Gains and losses may be incorrect otherwise.
|
||||
");
|
||||
|
||||
let input_file_path;
|
||||
let output_dir_path = args.output_dir_path;
|
||||
let should_export;
|
||||
|
||||
let account_map;
|
||||
let raw_acct_map;
|
||||
let action_records_map;
|
||||
let transactions_map;
|
||||
let mut settings;
|
||||
let like_kind_settings;
|
||||
|
||||
let home_currency_choice = args.home_currency.into_string().expect("Home currency should be in the form of a ticker in CAPS.");
|
||||
let costing_method_choice;
|
||||
|
||||
|
||||
if !args.accept_args {
|
||||
|
||||
shall_we_proceed();
|
||||
|
||||
fn shall_we_proceed() {
|
||||
|
||||
println!("Shall we proceed? [Y/n] ");
|
||||
|
||||
match _proceed() {
|
||||
Ok(()) => {}
|
||||
Err(err) => { println!("Failure to proceed. {}", err); process::exit(1); }
|
||||
};
|
||||
|
||||
fn _proceed() -> Result<(), Box<Error>> {
|
||||
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut input)?;
|
||||
|
||||
match input.trim().to_ascii_lowercase().as_str() {
|
||||
"y" | "ye" | "yes" | "" => { Ok(()) },
|
||||
"n" | "no" => { println!("We have NOT proceeded..."); process::exit(0); },
|
||||
_ => { println!("Please respond with 'y' or 'n' (or 'yes' or 'no')."); _proceed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(file) = args.file_to_import {
|
||||
input_file_path = file
|
||||
} else {
|
||||
input_file_path = user_choices::choose_file_for_import();
|
||||
}
|
||||
|
||||
costing_method_choice = LotProcessingChoices::choose_inventory_costing_method();
|
||||
|
||||
let lk_cutoff_date_opt_string;
|
||||
|
||||
if let Some(lk_cutoff) = args.cutoff_date {
|
||||
lk_cutoff_date_opt_string = Some(lk_cutoff.into_string().unwrap())
|
||||
} else {
|
||||
lk_cutoff_date_opt_string = None
|
||||
};
|
||||
|
||||
let (like_kind_election, like_kind_cutoff_date) = LotProcessingChoices::elect_like_kind_treatment(&lk_cutoff_date_opt_string);
|
||||
|
||||
settings = LotProcessingChoices {
|
||||
export_path: output_dir_path,
|
||||
home_currency: home_currency_choice,
|
||||
costing_method: costing_method_choice,
|
||||
enable_like_kind_treatment: like_kind_election,
|
||||
lk_cutoff_date_string: like_kind_cutoff_date,
|
||||
};
|
||||
|
||||
let (
|
||||
account_map1,
|
||||
raw_acct_map1,
|
||||
action_records_map1,
|
||||
transactions_map1,
|
||||
like_kind_settings1
|
||||
) = core_functions::import_and_process_final(input_file_path, &settings);
|
||||
|
||||
account_map = account_map1;
|
||||
raw_acct_map = raw_acct_map1;
|
||||
action_records_map = action_records_map1;
|
||||
transactions_map = transactions_map1;
|
||||
like_kind_settings = like_kind_settings1;
|
||||
|
||||
should_export = export_reports_to_output_dir(&mut settings);
|
||||
|
||||
fn export_reports_to_output_dir(settings: &mut LotProcessingChoices) -> bool {
|
||||
|
||||
println!("\nThe directory currently selected for exporting reports is: {}", settings.export_path.to_str().unwrap());
|
||||
|
||||
if &settings.export_path.to_str().unwrap() == &"." {
|
||||
println!(" (A 'dot' denotes the default value: current working directory.)");
|
||||
}
|
||||
println!("\nExport reports to selected directory? [Y/n/c] ('c' to 'change') ");
|
||||
|
||||
let choice = match _export(settings) {
|
||||
Ok(choice) => { choice }
|
||||
Err(err) => { println!("Export choice error. {}", err); process::exit(1); }
|
||||
};
|
||||
|
||||
fn _export(settings: &mut LotProcessingChoices) -> Result<(bool), Box<Error>> {
|
||||
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut input)?;
|
||||
|
||||
match input.trim().to_ascii_lowercase().as_str() {
|
||||
|
||||
"y" | "ye" | "yes" | "" => { println!("Creating reports now."); Ok(true) },
|
||||
"n" | "no" => { println!("Okay, no reports were created."); Ok(false) },
|
||||
"c" | "change" => {
|
||||
let new_dir = user_choices::choose_export_dir();
|
||||
settings.export_path = PathBuf::from(new_dir);
|
||||
println!("Creating reports now in newly chosen path.");
|
||||
Ok(true)
|
||||
},
|
||||
_ => { println!("Please respond with 'y', 'n', or 'c' (or 'yes' or 'no' or 'change').");
|
||||
_export(settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
choice
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if let Some(file) = args.file_to_import {
|
||||
input_file_path = file
|
||||
} else {
|
||||
println!("Flag to 'accept args' was set, but 'file' is missing, though it is a required field. Exiting.");
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
let like_kind_election;
|
||||
let like_kind_cutoff_date_string: String;
|
||||
|
||||
if let Some(date) = args.cutoff_date {
|
||||
like_kind_election = true;
|
||||
like_kind_cutoff_date_string = date.into_string().unwrap();
|
||||
} else {
|
||||
like_kind_election = false;
|
||||
like_kind_cutoff_date_string = "1-1-1".to_string();
|
||||
};
|
||||
|
||||
match args.inv_costing_method.clone().into_string().expect("Invalid choice on costing method. Aborting.").trim() {
|
||||
"1" | "2" | "3" | "4" => {}
|
||||
_ => { println!("Invalid choice for inventory costing method. Exiting."); process::exit(0); }
|
||||
}
|
||||
|
||||
let costing_method_choice = LotProcessingChoices::inv_costing_from_cmd_arg(args.inv_costing_method.into_string().unwrap());
|
||||
|
||||
settings = LotProcessingChoices {
|
||||
export_path: output_dir_path,
|
||||
home_currency: home_currency_choice,
|
||||
costing_method: costing_method_choice,
|
||||
enable_like_kind_treatment: like_kind_election,
|
||||
lk_cutoff_date_string: like_kind_cutoff_date_string,
|
||||
};
|
||||
|
||||
let (
|
||||
account_map1,
|
||||
raw_acct_map1,
|
||||
action_records_map1,
|
||||
transactions_map1,
|
||||
like_kind_settings1
|
||||
) = core_functions::import_and_process_final(input_file_path, &settings);
|
||||
|
||||
account_map = account_map1;
|
||||
raw_acct_map = raw_acct_map1;
|
||||
action_records_map = action_records_map1;
|
||||
transactions_map = transactions_map1;
|
||||
like_kind_settings = like_kind_settings1;
|
||||
|
||||
should_export = !args.suppress_reports;
|
||||
}
|
||||
|
||||
if should_export {
|
||||
|
||||
export::_1_account_sums_to_csv(
|
||||
&settings,
|
||||
&raw_acct_map,
|
||||
&account_map
|
||||
);
|
||||
|
||||
export::_2_account_sums_nonzero_to_csv(
|
||||
&account_map,
|
||||
&settings,
|
||||
&raw_acct_map
|
||||
);
|
||||
|
||||
export::_5_transaction_mvmt_summaries_to_csv(
|
||||
&settings,
|
||||
&action_records_map,
|
||||
&raw_acct_map,
|
||||
&account_map,
|
||||
&transactions_map
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// use tests::test;
|
||||
// test::run_tests(
|
||||
// &transactions_map,
|
||||
// &action_records_map,
|
||||
// &account_map
|
||||
// );
|
||||
|
||||
|
||||
Ok(())
|
||||
// // export::transactions_to_csv(&transactions);
|
||||
// // println!("\nReturned from `fn transactions_to_csv`. It worked!! Right?");
|
||||
|
||||
// export::accounts_to_csv(&accounts);
|
||||
// println!("\nReturned from `fn accounts_to_csv`. It worked!! Right?");
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
pub mod test;
|
|
@ -0,0 +1,243 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::fs;
|
||||
use std::collections::{HashMap};
|
||||
|
||||
use decimal::d128;
|
||||
|
||||
use crate::account::{Account};
|
||||
use crate::transaction::{Transaction, ActionRecord};
|
||||
use crate::utils::*;
|
||||
|
||||
pub fn run_tests(
|
||||
transactions_map: &HashMap<u32, Transaction>,
|
||||
action_records_map: &HashMap<u32, ActionRecord>,
|
||||
account_map: &HashMap<u16, Account>,
|
||||
) {
|
||||
|
||||
compare_movements_across_implementations(
|
||||
&transactions_map,
|
||||
&action_records_map,
|
||||
&account_map
|
||||
);
|
||||
|
||||
do_mvmts_know_what_lot_they_are_in(&account_map);
|
||||
|
||||
test_action_records_amts_vs_mvmt_amts(
|
||||
&transactions_map,
|
||||
&action_records_map,
|
||||
&account_map
|
||||
);
|
||||
|
||||
test_quantize_from_incoming_multiple_lots_fn(d128!(20), d128!(200), d128!(50));
|
||||
test_quantize_from_incoming_multiple_lots_fn(d128!(1), d128!(6), d128!(1234567.1234567896));
|
||||
// test_dec_rounded("123456789.123456789");
|
||||
// test_dec_rounded("123456.123456");
|
||||
// test_dec_rounded("1234567891234.1234567891234");
|
||||
// test_dec_rounded_1e8("123456789.123456789");
|
||||
// test_dec_rounded_1e8("123456.123456");
|
||||
// test_dec_rounded_1e8("1234567891234.1234567891234");
|
||||
// test_dec_rounded_1e2("123456789.123456789");
|
||||
// test_dec_rounded_1e2("123456.123456");
|
||||
// test_dec_rounded_1e2("1234567891234.1234567891234");
|
||||
|
||||
}
|
||||
|
||||
fn compare_movements_across_implementations(
|
||||
transactions_map: &HashMap<u32, Transaction>,
|
||||
action_records_map: &HashMap<u32, ActionRecord>,
|
||||
account_map: &HashMap<u16, Account>,
|
||||
) {
|
||||
// THIS BIG ASS TEST CREATES TWO FILES TO COMPARE actionrecords.movements with ar.get_mvmts_in_ar()
|
||||
// DELETE THIS WHEN movements FIELD OF ActionRecords IS DELETED
|
||||
|
||||
let mut line: String = "".to_string();
|
||||
|
||||
for tx_num in 1..=transactions_map.len() {
|
||||
let txn_num = tx_num as u32;
|
||||
line += &("Transaction ".to_string() + &txn_num.to_string() + &"\n".to_string());
|
||||
let txn = transactions_map.get(&(txn_num)).unwrap();
|
||||
for (idx, ar_num) in txn.action_record_idx_vec.iter().enumerate() {
|
||||
line += &("ActionRecord index: ".to_string() + &idx.to_string());
|
||||
let ar = action_records_map.get(&(ar_num)).unwrap();
|
||||
line += &(
|
||||
" with actionRecord key: ".to_string()
|
||||
+ &ar_num.to_string()
|
||||
+ " with amount: "
|
||||
+ &ar.amount.to_string() + &"\n".to_string()
|
||||
);
|
||||
let mvmts = ar.get_mvmts_in_ar(&account_map, &transactions_map);
|
||||
let mut amts = d128!(0);
|
||||
for mvmt in mvmts {
|
||||
amts += mvmt.amount;
|
||||
line += &("Movement ".to_string() +
|
||||
&mvmt.amount.to_string() +
|
||||
&" on ".to_string() +
|
||||
&mvmt.date_as_string +
|
||||
&"\n".to_string());
|
||||
}
|
||||
line += &("Amount total: ".to_string() + &amts.to_string() + &"\n".to_string());
|
||||
if amts - ar.amount != d128!(0) {
|
||||
line += &("&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&".to_string());
|
||||
println!("Movement amounts via get_mvmts_in_ar() different from actionRecord.amount. Aborting.");
|
||||
use std::process::exit; exit(1)
|
||||
};
|
||||
}
|
||||
}
|
||||
fs::write("/tmp/foo", &line).expect("Unable to write file");
|
||||
|
||||
let mut line2: String = "".to_string();
|
||||
|
||||
for tx_num in 1..=transactions_map.len() {
|
||||
let txn_num = tx_num as u32;
|
||||
line2 += &("Transaction ".to_string() + &txn_num.to_string() + &"\n".to_string());
|
||||
let txn = transactions_map.get(&(txn_num)).unwrap();
|
||||
for (idx, ar_num) in txn.action_record_idx_vec.iter().enumerate() {
|
||||
line2 += &("ActionRecord index: ".to_string() + &idx.to_string());
|
||||
let ar = action_records_map.get(&(ar_num)).unwrap();
|
||||
line2 += &(
|
||||
" with actionRecord key: ".to_string()
|
||||
+ &ar_num.to_string()
|
||||
+ " with amount: "
|
||||
+ &ar.amount.to_string()
|
||||
+ &"\n".to_string()
|
||||
);
|
||||
// let mvmts = ar.get_mvmts_in_ar(&account_map);
|
||||
let mut amts = d128!(0);
|
||||
for mvmt in ar.movements.borrow().iter() {
|
||||
amts += mvmt.amount;
|
||||
line2 += &("Movement ".to_string() +
|
||||
&mvmt.amount.to_string() +
|
||||
&" on ".to_string() +
|
||||
&mvmt.date_as_string +
|
||||
&"\n".to_string());
|
||||
}
|
||||
line2 += &("Amount total: ".to_string() + &amts.to_string() + &"\n".to_string());
|
||||
if amts - ar.amount != d128!(0) {
|
||||
line2 += &("&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&".to_string());
|
||||
println!("Movement amounts via ar.movements different from actionRecord.amount. Aborting.");
|
||||
use std::process::exit; exit(1)
|
||||
};
|
||||
}
|
||||
}
|
||||
fs::write("/tmp/foo2", &line2).expect("Unable to write file");
|
||||
}
|
||||
|
||||
fn do_mvmts_know_what_lot_they_are_in(account_map: &HashMap<u16, Account>,) {
|
||||
|
||||
for (acct_num, acct) in account_map.iter() {
|
||||
for lot in acct.list_of_lots.borrow().iter() {
|
||||
for mvmt in lot.movements.borrow().iter() {
|
||||
if mvmt.lot_num != lot.lot_number {
|
||||
println!("ERROR: For txn {} on {}, movement.lot_num != lot.lot_number.",
|
||||
mvmt.transaction_key, mvmt.date);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_action_records_amts_vs_mvmt_amts(
|
||||
transactions_map: &HashMap<u32, Transaction>,
|
||||
action_records_map: &HashMap<u32, ActionRecord>,
|
||||
account_map: &HashMap<u16, Account>,
|
||||
) {
|
||||
|
||||
let mut mvmt_amt_acct_lot: d128 = d128!(0);
|
||||
let mut mvmt_amt_ar: d128 = d128!(0);
|
||||
let mut ar_amts: d128 = d128!(0);
|
||||
|
||||
for tx_num in 1..=transactions_map.len() {
|
||||
|
||||
let txn_num = tx_num as u32;
|
||||
let txn = transactions_map.get(&(txn_num)).unwrap();
|
||||
|
||||
for ar_num in &txn.action_record_idx_vec {
|
||||
|
||||
let ar = action_records_map.get(&(ar_num)).unwrap();
|
||||
let mvmts = ar.get_mvmts_in_ar(&account_map, &transactions_map);
|
||||
for mvmt in mvmts {
|
||||
mvmt_amt_ar += mvmt.amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for acct_num in 1..=account_map.len() {
|
||||
|
||||
let acct = account_map.get(&(acct_num as u16)).unwrap();
|
||||
|
||||
for lot in acct.list_of_lots.borrow().iter() {
|
||||
for mvmt in lot.movements.borrow().iter() {
|
||||
mvmt_amt_acct_lot += mvmt.amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for tx_num in 1..=transactions_map.len() {
|
||||
|
||||
let txn_num = tx_num as u32;
|
||||
let txn = transactions_map.get(&(txn_num)).unwrap();
|
||||
|
||||
for ar_num in &txn.action_record_idx_vec {
|
||||
let ar = action_records_map.get(&(ar_num)).unwrap();
|
||||
ar_amts += ar.amount
|
||||
}
|
||||
}
|
||||
|
||||
println!(" \n\t\t\tActionRecord amounts pulled from iterating over transactions: {}
|
||||
\n\t\t\tMovement amounts pulled from actionRecords dynamically after: {}
|
||||
\n\t\t\tMovement amounts where movements are recorded directly in lots: {}\n",
|
||||
ar_amts,
|
||||
mvmt_amt_ar,
|
||||
mvmt_amt_acct_lot
|
||||
);
|
||||
}
|
||||
|
||||
fn test_quantize_from_incoming_multiple_lots_fn (
|
||||
outgoing_mvmt_amt: d128,
|
||||
outgoing_ar_amt: d128,
|
||||
incoming_ar_amt: d128,
|
||||
) {
|
||||
let rounded_example = d128::from(1).scaleb(d128::from(-8));
|
||||
//
|
||||
println!("Og mvmt amt: {:?}, Og ar amt: {:?}, Ic ar amt: {:?}", outgoing_mvmt_amt, outgoing_ar_amt, incoming_ar_amt);
|
||||
let ratio_of_outgoing_to_total_ar = outgoing_mvmt_amt / outgoing_ar_amt; // Negative divided by negative is positive
|
||||
println!("ratio_of_outgoing: {:.20}", ratio_of_outgoing_to_total_ar);
|
||||
let tentative_incoming_amt = ratio_of_outgoing_to_total_ar * incoming_ar_amt;
|
||||
println!("tentative_inc_amt: {:.20}", tentative_incoming_amt);
|
||||
let corresponding_incoming_amt = tentative_incoming_amt.quantize(rounded_example);
|
||||
println!("corresponding_inc_amt: {}", corresponding_incoming_amt);
|
||||
}
|
||||
|
||||
// Yields:
|
||||
// Og mvmt amt: 20, Og ar amt: 200, Ic ar amt: 50
|
||||
// ratio_of_outgoing: 0.1
|
||||
// tentative_inc_amt: 5.0
|
||||
// corresponding_inc_amt: 5.00000000
|
||||
// Og mvmt amt: 1, Og ar amt: 6, Ic ar amt: 1234567.1234567896
|
||||
// ratio_of_outgoing: 0.166666666666666666
|
||||
// tentative_inc_amt: 205761.1872427982666
|
||||
// corresponding_inc_amt: 205761.18724280
|
||||
|
||||
fn test_dec_rounded(random_float_string: &str) {
|
||||
let places_past_decimal = d128!(8);
|
||||
let amt = random_float_string.parse::<d128>().unwrap();
|
||||
let amt2 = round_d128_generalized(&amt, places_past_decimal);
|
||||
println!("Float into d128: {:?}; d128 rounded to {}: {:?}", amt, places_past_decimal, amt2);
|
||||
// Results of this test suggest that quantize() is off by one. round_d128_1e8() was adjusted accordingly.
|
||||
}
|
||||
|
||||
fn test_dec_rounded_1e8(random_float_string: &str) {
|
||||
let amt = random_float_string.parse::<d128>().unwrap();
|
||||
let amt2 = round_d128_1e8(&amt);
|
||||
println!("Float into d128: {:?}; d128 rounded to 8 places: {:?}", amt, amt2);
|
||||
// Results of this test suggest that quantize() is off by one. round_d128_1e8() was adjusted accordingly.
|
||||
}
|
||||
|
||||
fn test_dec_rounded_1e2(random_float_string: &str) {
|
||||
let amt = random_float_string.parse::<d128>().unwrap();
|
||||
let amt2 = round_d128_1e2(&amt);
|
||||
println!("String into d128: {:?}; d128 rounded to 2 places: {:?}", amt, amt2);
|
||||
// Results of this test suggest that quantize() is off by one. round_d128_1e8() was adjusted accordingly.
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::rc::{Rc};
|
||||
use std::cell::{RefCell};
|
||||
use std::process::exit;
|
||||
use std::fmt;
|
||||
use std::collections::{HashMap};
|
||||
|
||||
use decimal::d128;
|
||||
use chrono::NaiveDate;
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
use crate::user_choices::LotProcessingChoices;
|
||||
use crate::account::{Account, Movement, RawAccount};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Transaction {
|
||||
pub tx_number: u32, // Does NOT start at zero. First txn is 1.
|
||||
pub date_as_string: String,
|
||||
pub date: NaiveDate,
|
||||
pub memo: String,
|
||||
pub proceeds: f32,
|
||||
pub action_record_idx_vec: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
|
||||
pub fn transaction_type(
|
||||
&self,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
) -> TxType {
|
||||
|
||||
if self.action_record_idx_vec.len() == 1 {
|
||||
TxType::Flow
|
||||
}
|
||||
else if self.action_record_idx_vec.len() == 2 {
|
||||
// This exercise of splitting the strings is because of margin accounts, where BTC borrowed to buy XMR would reflect as BTC_xmr
|
||||
let first_ar = ars.get(&self.action_record_idx_vec[0]).unwrap();
|
||||
let second_ar = ars.get(&self.action_record_idx_vec[1]).unwrap();
|
||||
let first_acct = acct_map.get(&first_ar.account_key).unwrap();
|
||||
let second_acct = acct_map.get(&second_ar.account_key).unwrap();
|
||||
let ar1_raw_acct = raw_acct_map.get(&first_acct.raw_key).unwrap();
|
||||
let ar2_raw_acct = raw_acct_map.get(&second_acct.raw_key).unwrap();
|
||||
let ar1_ticker_full = ar1_raw_acct.ticker.clone();
|
||||
let ar2_ticker_full = ar2_raw_acct.ticker.clone();
|
||||
let ar1_ticker_comp: Vec<&str> = ar1_ticker_full.split('_').collect();
|
||||
let ar2_ticker_comp: Vec<&str> = ar2_ticker_full.split('_').collect();
|
||||
let ar1_ticker = ar1_ticker_comp[0];
|
||||
let ar2_ticker = ar2_ticker_comp[0];
|
||||
|
||||
if first_ar.direction() == second_ar.direction() {
|
||||
println!("Program exiting. Found transaction with two actionRecords with the same polarity: {:?}", self); exit(1);
|
||||
}
|
||||
if ar1_ticker == ar2_ticker {
|
||||
if ar1_raw_acct.is_margin != ar2_raw_acct.is_margin {
|
||||
TxType::Flow
|
||||
}
|
||||
else {
|
||||
TxType::ToSelf
|
||||
}
|
||||
}
|
||||
else {
|
||||
TxType::Exchange
|
||||
}
|
||||
}
|
||||
else if self.action_record_idx_vec.len() > 2 {
|
||||
println!("Program exiting. Found transaction with too many actionRecords: {:?}", self); exit(1);
|
||||
}
|
||||
else {
|
||||
println!("Program exiting. Found transaction with no actionRecords: {:?}", self); exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn marginness(
|
||||
&self,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>
|
||||
) -> TxHasMargin {
|
||||
|
||||
if self.action_record_idx_vec.len() == 1 {
|
||||
let ar = ars.get(&self.action_record_idx_vec[0]).unwrap();
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
if raw_acct.is_margin {
|
||||
TxHasMargin::OneAR
|
||||
} else {
|
||||
TxHasMargin::NoARs
|
||||
}
|
||||
} else {
|
||||
assert_eq!(self.action_record_idx_vec.len(),2);
|
||||
let first_ar = ars.get(&self.action_record_idx_vec[0]).unwrap();
|
||||
let second_ar = ars.get(&self.action_record_idx_vec[1]).unwrap();
|
||||
|
||||
let first_acct = acct_map.get(&first_ar.account_key).unwrap();
|
||||
let second_acct = acct_map.get(&second_ar.account_key).unwrap();
|
||||
|
||||
let first_raw_acct = &raw_acct_map.get(&first_acct.raw_key).unwrap();
|
||||
let second_raw_acct = &raw_acct_map.get(&second_acct.raw_key).unwrap();
|
||||
|
||||
if first_raw_acct.is_margin {
|
||||
if second_raw_acct.is_margin {TxHasMargin::TwoARs} else {TxHasMargin::OneAR}
|
||||
} else if second_raw_acct.is_margin {TxHasMargin::OneAR} else {TxHasMargin::NoARs}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_base_and_quote_raw_acct_keys(
|
||||
&self,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_accts: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>
|
||||
) -> (u16, u16) {
|
||||
|
||||
assert_eq!(self.transaction_type(ars, raw_accts, acct_map), TxType::Exchange,
|
||||
"This can only be called on exchange transactions.");
|
||||
|
||||
let first_ar = ars.get(&self.action_record_idx_vec[0]).unwrap();
|
||||
let second_ar = ars.get(&self.action_record_idx_vec[1]).unwrap();
|
||||
let first_acct = acct_map.get(&first_ar.account_key).unwrap();
|
||||
let second_acct = acct_map.get(&second_ar.account_key).unwrap();
|
||||
let first_acct_raw_key = first_acct.raw_key;
|
||||
let second_acct_raw_key = second_acct.raw_key;
|
||||
let first_raw_acct = raw_accts.get(&first_acct_raw_key).unwrap();
|
||||
let second_raw_acct = raw_accts.get(&second_acct_raw_key).unwrap();
|
||||
|
||||
assert_eq!(first_raw_acct.is_margin, true, "First actionrecord wasn't a margin account. Both must be.");
|
||||
assert_eq!(second_raw_acct.is_margin, true, "Second actionrecord wasn't a margin account. Both must be.");
|
||||
|
||||
let quote: u16;
|
||||
let base: u16;
|
||||
|
||||
if first_raw_acct.ticker.contains('_') {
|
||||
quote = first_acct_raw_key;
|
||||
base = second_acct_raw_key;
|
||||
(base, quote)
|
||||
} else if second_raw_acct.ticker.contains('_') {
|
||||
base = first_acct_raw_key;
|
||||
quote = second_acct_raw_key;
|
||||
(base, quote)
|
||||
} else {
|
||||
println!("{}", VariousErrors::MarginNoUnderbar); use std::process::exit; exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_outgoing_exchange_and_flow_mvmts(
|
||||
&self,
|
||||
settings: &LotProcessingChoices,
|
||||
ars: &HashMap<u32, ActionRecord>,
|
||||
raw_acct_map: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) -> Vec<Rc<Movement>> {
|
||||
|
||||
let mut flow_or_outgoing_exchange_movements = [].to_vec();
|
||||
|
||||
for ar_num in self.action_record_idx_vec.iter() {
|
||||
|
||||
let ar = ars.get(ar_num).unwrap();
|
||||
let acct = acct_map.get(&ar.account_key).unwrap();
|
||||
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
|
||||
|
||||
if !raw_acct.is_home_currency(&settings.home_currency) & !raw_acct.is_margin {
|
||||
|
||||
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
|
||||
|
||||
match self.transaction_type(ars, raw_acct_map, acct_map) {
|
||||
TxType::Exchange => {
|
||||
if Polarity::Outgoing == ar.direction() {
|
||||
for mvmt in movements.iter() {
|
||||
flow_or_outgoing_exchange_movements.push(mvmt.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
TxType::Flow => {
|
||||
for mvmt in movements.iter() {
|
||||
flow_or_outgoing_exchange_movements.push(mvmt.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
flow_or_outgoing_exchange_movements
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ActionRecord {
|
||||
pub account_key: u16,
|
||||
pub amount: d128,
|
||||
pub tx_key: u32,
|
||||
pub self_ar_key: u32,
|
||||
pub movements: RefCell<Vec<Rc<Movement>>>,
|
||||
}
|
||||
|
||||
impl ActionRecord {
|
||||
|
||||
pub fn direction(&self) -> Polarity {
|
||||
if self.amount < d128!(0.0) { Polarity::Outgoing}
|
||||
else { Polarity::Incoming }
|
||||
}
|
||||
|
||||
pub fn is_quote_acct_for_margin_exch(
|
||||
&self,
|
||||
raw_accts: &HashMap<u16, RawAccount>,
|
||||
acct_map: &HashMap<u16, Account>
|
||||
) -> bool {
|
||||
|
||||
let acct = acct_map.get(&self.account_key).unwrap();
|
||||
let raw_acct = raw_accts.get(&acct.raw_key).unwrap();
|
||||
raw_acct.ticker.contains('_')
|
||||
}
|
||||
|
||||
pub fn get_mvmts_in_ar(
|
||||
&self,
|
||||
acct_map: &HashMap<u16, Account>,
|
||||
txns_map: &HashMap<u32, Transaction>,
|
||||
) -> Vec<Rc<Movement>> {
|
||||
|
||||
let polarity = Self::direction(self);
|
||||
let txn = txns_map.get(&self.tx_key).unwrap();
|
||||
let mut movements_in_ar = [].to_vec();
|
||||
let acct = acct_map.get(&self.account_key).unwrap();
|
||||
|
||||
for lot in acct.list_of_lots.borrow().iter() {
|
||||
for mvmt in lot.movements.borrow().iter() {
|
||||
if (mvmt.date) <= txn.date {
|
||||
if mvmt.action_record_key == self.self_ar_key {
|
||||
// if polarity == Polarity::Incoming{
|
||||
// movements_in_ar.push(mvmt.clone())
|
||||
// } else {
|
||||
movements_in_ar.insert(0, mvmt.clone())
|
||||
// }
|
||||
// ^^ leaving that ugliness for this commit on purpose
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
movements_in_ar
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum TxType {
|
||||
Exchange,
|
||||
ToSelf,
|
||||
Flow,
|
||||
}
|
||||
|
||||
impl fmt::Display for TxType {
|
||||
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
TxType::Exchange => write!(f, "Exchange"),
|
||||
TxType::ToSelf => write!(f, "ToSelf"),
|
||||
TxType::Flow => write!(f, "Flow"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum TxHasMargin {
|
||||
NoARs,
|
||||
OneAR,
|
||||
TwoARs,
|
||||
}
|
||||
|
||||
impl fmt::Display for TxHasMargin {
|
||||
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
TxHasMargin::NoARs => write!(f, "No actionrecord is a margin account"),
|
||||
TxHasMargin::OneAR => write!(f, "One actionrecord is a margin account"),
|
||||
TxHasMargin::TwoARs => write!(f, "Two actionrecords are margin accounts"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Polarity {
|
||||
Outgoing,
|
||||
Incoming,
|
||||
}
|
||||
|
||||
impl fmt::Display for Polarity {
|
||||
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Polarity::Outgoing => write!(f, "Outgoing"),
|
||||
Polarity::Incoming => write!(f, "Incoming"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum VariousErrors {
|
||||
MarginNoUnderbar,
|
||||
}
|
||||
|
||||
impl fmt::Display for VariousErrors {
|
||||
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
VariousErrors::MarginNoUnderbar => write!(f,
|
||||
"Neither account ticker contained an underbar '_', so the quote account couldn't be determined.
|
||||
For example, for the 'USD/EUR' pair, USD is the base account and EUR is the quote account.
|
||||
In order for this software to function correctly, the quote ticker should be denoted as 'EUR_usd'.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use std::error::Error;
|
||||
use std::io::{self, BufRead};
|
||||
use std::process;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
use rustyline::{CompletionType, Config, Context, EditMode, Editor, Helper};
|
||||
use rustyline::config::OutputStreamType;
|
||||
use rustyline::hint::{Hinter};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::highlight::{Highlighter};
|
||||
use structopt::StructOpt;
|
||||
|
||||
use crate::utils;
|
||||
|
||||
|
||||
pub fn choose_file_for_import() -> PathBuf {
|
||||
|
||||
println!("Please input a file (absolute or relative path) to import: ");
|
||||
|
||||
// PathBuf::from("/Users/scoob/Documents/Repos/cryptools-rs/private/RawTxForImport-pycleaned.csv")
|
||||
|
||||
let file_str = _get_path();
|
||||
PathBuf::from(file_str.unwrap())
|
||||
}
|
||||
|
||||
pub fn choose_export_dir() -> PathBuf {
|
||||
|
||||
println!("Please input a file path for exports: ");
|
||||
|
||||
// PathBuf::from("/Users/scoob/Documents/Testing/rust_exports/")
|
||||
|
||||
let file_str = _get_path();
|
||||
PathBuf::from(file_str.unwrap())
|
||||
}
|
||||
|
||||
fn _get_path() -> Result<(String), Box<Error>> {
|
||||
|
||||
struct MyHelper {
|
||||
completer: FilenameCompleter,
|
||||
colored_prompt: String,
|
||||
}
|
||||
|
||||
impl Completer for MyHelper {
|
||||
|
||||
type Candidate = Pair;
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
ctx: &Context<'_>,
|
||||
) -> Result<(usize, Vec<Pair>), ReadlineError> {
|
||||
self.completer.complete(line, pos, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hinter for MyHelper {}
|
||||
impl Highlighter for MyHelper {}
|
||||
impl Helper for MyHelper {}
|
||||
|
||||
let h = MyHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
colored_prompt: "".to_owned(),
|
||||
};
|
||||
|
||||
let config = Config::builder()
|
||||
.history_ignore_space(true)
|
||||
.completion_type(CompletionType::Circular)
|
||||
.edit_mode(EditMode::Vi)
|
||||
.output_stream(OutputStreamType::Stdout)
|
||||
.build();
|
||||
|
||||
let count = 1;
|
||||
let mut rl = Editor::with_config(config);
|
||||
let p = format!("{}> ", count);
|
||||
rl.set_helper(Some(h));
|
||||
rl.helper_mut().unwrap().colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p);
|
||||
let readline = rl.readline(">> ");
|
||||
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
rl.add_history_entry(line.as_str());
|
||||
println!("");
|
||||
Ok(line)
|
||||
},
|
||||
Err(err) => {
|
||||
println!("Error during Rustyline: {:?}", err);
|
||||
process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, StructOpt)]
|
||||
pub enum InventoryCostingMethod {
|
||||
/// 1. LIFO according to the order the lot was created.
|
||||
LIFObyLotCreationDate,
|
||||
/// 2. LIFO according to the basis date of the lot.
|
||||
LIFObyLotBasisDate,
|
||||
/// 3. FIFO according to the order the lot was created.
|
||||
FIFObyLotCreationDate,
|
||||
/// 4. FIFO according to the basis date of the lot.
|
||||
FIFObyLotBasisDate,
|
||||
}
|
||||
|
||||
// impl std::convert::From<OsStr> for InventoryCostingMethod {
|
||||
// fn from(osstr: OsStr) -> InventoryCostingMethod {
|
||||
// let osstring1 = OsString::from(Box::<osstr>);
|
||||
// let new_string = osstr.into_string().expect("Invalid input. Could not convert to string.");
|
||||
// let method = match new_string.trim() {
|
||||
// "1" => InventoryCostingMethod::LIFObyLotCreationDate,
|
||||
// "2" => InventoryCostingMethod::LIFObyLotBasisDate,
|
||||
// "3" => InventoryCostingMethod::FIFObyLotCreationDate,
|
||||
// "4" => InventoryCostingMethod::FIFObyLotBasisDate,
|
||||
// _ => { println!("Invalid choice. Could not convert."); process::exit(1)
|
||||
// }
|
||||
// };
|
||||
// method
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct LotProcessingChoices {
|
||||
pub export_path: PathBuf,
|
||||
pub home_currency: String,
|
||||
pub enable_like_kind_treatment: bool,
|
||||
pub costing_method: InventoryCostingMethod,
|
||||
pub lk_cutoff_date_string: String,
|
||||
}
|
||||
|
||||
impl LotProcessingChoices {
|
||||
|
||||
pub fn choose_inventory_costing_method() -> InventoryCostingMethod {
|
||||
|
||||
println!("Choose the lot inventory costing method. [Default: 1]");
|
||||
println!("1. LIFO according to the order the lot was created.");
|
||||
println!("2. LIFO according to the basis date of the lot.");
|
||||
println!("3. FIFO according to the order the lot was created.");
|
||||
println!("4. FIFO according to the basis date of the lot.");
|
||||
|
||||
let method = match _costing_method() {
|
||||
Ok(x) => {x},
|
||||
Err(err) => { process::exit(1) }
|
||||
};
|
||||
|
||||
fn _costing_method() -> Result<(InventoryCostingMethod), Box<Error>> {
|
||||
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut input).expect("Failed to read stdin");
|
||||
|
||||
match input.trim() { // Without .trim(), there's a hidden \n or something preventing the match
|
||||
"1" | "" => Ok(InventoryCostingMethod::LIFObyLotCreationDate),
|
||||
"2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate),
|
||||
"3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate),
|
||||
"4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate),
|
||||
_ => { println!("Invalid choice. Please enter a valid number."); _costing_method() }
|
||||
}
|
||||
}
|
||||
|
||||
method
|
||||
}
|
||||
|
||||
pub fn inv_costing_from_cmd_arg(arg: String) -> InventoryCostingMethod {
|
||||
|
||||
let method = match _costing_method(arg) {
|
||||
Ok(x) => {x},
|
||||
Err(err) => { process::exit(1) }
|
||||
};
|
||||
|
||||
fn _costing_method(input: String) -> Result<(InventoryCostingMethod), Box<Error>> {
|
||||
|
||||
match input.trim() { // Without .trim(), there's a hidden \n or something preventing the match
|
||||
"1" => Ok(InventoryCostingMethod::LIFObyLotCreationDate),
|
||||
"2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate),
|
||||
"3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate),
|
||||
"4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate),
|
||||
_ => { println!("Invalid choice. Please enter a valid number."); _costing_method(input) }
|
||||
}
|
||||
}
|
||||
|
||||
method
|
||||
}
|
||||
|
||||
pub fn elect_like_kind_treatment(cutoff_date_arg: &Option<String>) -> (bool, String) {
|
||||
|
||||
let election: bool;
|
||||
let date: String;
|
||||
|
||||
if cutoff_date_arg.is_some() {
|
||||
|
||||
let provided_date = NaiveDate::parse_from_str(&cutoff_date_arg.clone().unwrap(), "%y-%m-%d")
|
||||
.unwrap_or(NaiveDate::parse_from_str(&cutoff_date_arg.clone().unwrap(), "%Y-%m-%d")
|
||||
.expect("Date entered as -c command line arg has an incorrect format."));
|
||||
|
||||
println!("\nUse like-kind exchange treatment through {}? [Y/n/c] ('c' to 'change') ", provided_date);
|
||||
|
||||
let (election, date) = match _elect_like_kind_arg(&cutoff_date_arg, provided_date) {
|
||||
Ok(x) => {x},
|
||||
Err(err) => { println!("Fatal error in fn elect_like_kind_treatment()."); process::exit(1) }
|
||||
};
|
||||
|
||||
fn _elect_like_kind_arg(cutoff_date_arg: &Option<String>, provided_date: NaiveDate) -> Result<(bool, String), Box<Error>> {
|
||||
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut input)?;
|
||||
|
||||
|
||||
match input.trim().to_ascii_lowercase().as_str() {
|
||||
"y" | "ye" | "yes" | "" => {
|
||||
println!(" Using like-kind treatment through {}.\n", provided_date);
|
||||
Ok( (true, cutoff_date_arg.clone().unwrap()) )
|
||||
},
|
||||
"n" | "no" => { println!(" Proceeding without like-kind treatment.\n");
|
||||
Ok( (false, "1-1-1".to_string()) )
|
||||
},
|
||||
"c" | "change" => {
|
||||
println!("Please enter your desired like-kind exchange treatment cutoff date.");
|
||||
println!(" You must use the format %y-%m-%d (e.g., 2017-12-31, 17-12-31, and 9-6-1 are all acceptable).\n");
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut input)?;
|
||||
utils::trim_newline(&mut input);
|
||||
let newly_chosen_date = NaiveDate::parse_from_str(&input, "%y-%m-%d")
|
||||
.unwrap_or(NaiveDate::parse_from_str(&input, "%Y-%m-%d")
|
||||
.expect("Date entered has an incorrect format. Program must abort."));
|
||||
// TODO: figure out how to make this fail gracefully and let the user input the date again
|
||||
println!(" Using like-kind treatment through {}.\n", newly_chosen_date);
|
||||
Ok( (true, input) )
|
||||
},
|
||||
_ => {
|
||||
println!("Please respond with 'y', 'n', or 'c' (or 'yes' or 'no' or 'change').");
|
||||
_elect_like_kind_arg(&cutoff_date_arg, provided_date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (election, date)
|
||||
|
||||
} else {
|
||||
|
||||
println!("\nContinue without like-kind exchange treatment? [Y/n] ");
|
||||
|
||||
let (election, date) = match _no_elect_like_kind_arg() {
|
||||
Ok(x) => {x},
|
||||
Err(err) => { println!("Fatal error in like_kind selection. Perhaps incorrect date format."); process::exit(1) }
|
||||
};
|
||||
|
||||
fn _no_elect_like_kind_arg() -> Result<(bool, String), Box<Error>> {
|
||||
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut input)?;
|
||||
|
||||
match input.trim().to_ascii_lowercase().as_str() {
|
||||
"y" | "ye" | "yes" | "" => { println!(" Proceeding without like-kind treatment.\n");
|
||||
Ok( (false, "1-1-1".to_string()) )
|
||||
},
|
||||
"n" | "no" => {
|
||||
println!("Please enter your desired like-kind exchange treatment cutoff date.");
|
||||
println!(" You must use the format %y-%m-%d (e.g., 2017-12-31, 17-12-31, and 9-6-1 are all acceptable).\n");
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut input)?;
|
||||
utils::trim_newline(&mut input);
|
||||
|
||||
let newly_chosen_date = NaiveDate::parse_from_str(&input, "%y-%m-%d")
|
||||
.unwrap_or(NaiveDate::parse_from_str(&input, "%Y-%m-%d")
|
||||
.expect("Date entered has an incorrect format. Program must abort."));
|
||||
// TODO: figure out how to make this fail gracefully and let the user input the date again
|
||||
println!(" Using like-kind treatment through {}.\n", newly_chosen_date);
|
||||
|
||||
Ok( (true, input) )
|
||||
},
|
||||
_ => { println!("Please respond with 'y' or 'n' (or 'yes' or 'no')."); _no_elect_like_kind_arg() }
|
||||
}
|
||||
}
|
||||
|
||||
return (election, date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LikeKindSettings {
|
||||
pub like_kind_cutoff_date: NaiveDate,
|
||||
pub like_kind_basis_date_preserved: bool,
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2017-2019, scoobybejesus
|
||||
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
|
||||
|
||||
use decimal::d128;
|
||||
|
||||
|
||||
pub fn round_d128_generalized(to_round: &d128, places_past_decimal: d128) -> d128 {
|
||||
let rounded: d128 = ((to_round * d128!(10).scaleb(places_past_decimal)).quantize(d128!(1e1))) / d128!(10).scaleb(places_past_decimal);
|
||||
rounded//.reduce()
|
||||
}
|
||||
pub fn round_d128_1e2(to_round: &d128) -> d128 {
|
||||
let rounded: d128 = ((to_round * d128!(10).scaleb(d128!(2))).quantize(d128!(1e1))) / d128!(10).scaleb(d128!(2));
|
||||
rounded//.reduce()
|
||||
}
|
||||
|
||||
pub fn round_d128_1e8(to_round: &d128) -> d128 {
|
||||
let rounded: d128 = ((to_round * d128!(10).scaleb(d128!(8))).quantize(d128!(1e1))) / d128!(10).scaleb(d128!(8));
|
||||
rounded//.reduce()
|
||||
// Note: quantize() rounds the number to the right of decimal and keeps it, discarding the rest to the right (it appears). See test.
|
||||
// In other words, it's off by one. If you raise 0.123456789 by 10e8, quantize to 1e1 (which is 10), it'll get 12345678.9, round off to 12345679, and end up .12345679
|
||||
// If you quantize the same number to 1e2 (which is 100), it starts back toward the left, so it'll get 12345678.9, round off to 12345680
|
||||
// If you quantize the same number to 1e3 (which is 1000), it starts back toward the left, so it'll get 12345678.9, round off to 12345700
|
||||
// As you can see, the quantize is off by one. Quantizing to 10 rounds off the nearest one. Quantizing to 100 rounds off to nearest 10, etc.
|
||||
}
|
||||
|
||||
|
||||
pub fn trim_newline(s: &mut String) {
|
||||
if s.ends_with('\n') {
|
||||
s.pop();
|
||||
if s.ends_with('\r') {
|
||||
s.pop();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue