Compare commits
3 Commits
999b46a904
...
9a28bfbf64
Author | SHA1 | Date |
---|---|---|
scoobybejesus | 9a28bfbf64 | |
scoobybejesus | 29f84a30d3 | |
scoobybejesus | 4faab11ed3 |
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cryptools"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'."
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
import csv
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
unedited = "DigiTrnx.csv" # To be replaced with a launch arg, presumably
|
||||
|
||||
|
@ -47,6 +48,13 @@ with open(unedited) as fin, open(stage1, 'a') as fout:
|
|||
wtr.writerow(header3)
|
||||
wtr.writerow(header4)
|
||||
|
||||
# First, double check there are no account number duplicates
|
||||
for i, val in enumerate(header):
|
||||
if val != "":
|
||||
if header.count(val) > 1:
|
||||
print("### There is a duplicate account number (" + val +"). Please fix and re-run. ###")
|
||||
sys.exit()
|
||||
|
||||
for row in rdr:
|
||||
if row[0] == "" or row[1] == "":
|
||||
pass
|
||||
|
|
|
@ -242,6 +242,12 @@ pub(crate) fn create_lots_and_movements(
|
|||
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &chosen_home_currency, &raw_acct_map, &acct_map);
|
||||
continue
|
||||
} else {
|
||||
|
||||
if acct.list_of_lots.borrow().len() == 0 {
|
||||
println!("FATAL: There are zero lots to spend from in transaction:\n{:#?}",txn);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -557,7 +563,8 @@ pub(crate) fn create_lots_and_movements(
|
|||
}
|
||||
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) };
|
||||
println!("FATAL: Consult developer. Found margin actionrecord in ToSelf transaction:\n{:#?}", txn);
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
process_multiple_incoming_lots_and_mvmts(
|
||||
txn_num,
|
||||
|
@ -729,7 +736,7 @@ fn fit_into_lots(
|
|||
&chosen_home_currency,
|
||||
&ar_map,
|
||||
&raw_acct_map,
|
||||
&acct_map
|
||||
&acct_map,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -777,6 +784,14 @@ fn fit_into_lots(
|
|||
wrap_mvmt_and_push(mvmt_that_fits_in_lot, &spawning_ar, &lot, &chosen_home_currency, &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);
|
||||
|
||||
if vec_of_ordered_index_values.len() == current_index_position + 1 {
|
||||
println!("FATAL: Txn {} on {} spending {} {} has run out of lots to spend from.",
|
||||
txn_num, lot.date_as_string, ar.amount, raw_acct.ticker);
|
||||
println!("Account balance is only: {}", acct.get_sum_of_amts_in_lots());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -806,7 +821,7 @@ fn fit_into_lots(
|
|||
&chosen_home_currency,
|
||||
&ar_map,
|
||||
&raw_acct_map,
|
||||
&acct_map
|
||||
&acct_map,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -51,67 +51,63 @@ fn import_accounts(
|
|||
) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let header1 = rdr.headers()?.clone(); // account_num
|
||||
let mut header2: Option<csv::StringRecord> = None; // name
|
||||
let mut header3: Option<csv::StringRecord> = None; // ticker
|
||||
let mut header2: csv::StringRecord = csv::StringRecord::new(); // name
|
||||
let mut header3: csv::StringRecord = csv::StringRecord::new(); // ticker
|
||||
let header4: csv::StringRecord; // is_margin
|
||||
|
||||
// A StringRecord doesn't accept the same range indexing we need below, so we create our own
|
||||
let mut headerstrings: Vec<String> = Vec::with_capacity(header1.len());
|
||||
|
||||
for element in header1.into_iter() {
|
||||
headerstrings.push(element.to_string())
|
||||
}
|
||||
|
||||
// Account Creation loop. We set hasheaders() to true above, so the first record here is the second row of the CSV
|
||||
// Account Creation loop. With rdr.has_headers() set to true above, 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());
|
||||
if header2.len() == 0 {
|
||||
header2 = record.clone();
|
||||
continue // After header2 is set, continue to next record
|
||||
}
|
||||
else if header3 == None {
|
||||
header3 = Some(record.clone());
|
||||
else if header3.len() == 0 {
|
||||
header3 = 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
|
||||
// A StringRecord doesn't accept the same range indexing needed below, so a Vec of Strings will be used
|
||||
let mut headerstrings: Vec<String> = Vec::with_capacity(header1.len());
|
||||
for field in header1.into_iter() {
|
||||
headerstrings.push(field.to_string())
|
||||
}
|
||||
|
||||
let acct_num_warn = "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 value should be 1.
|
||||
The next column's value 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.
|
||||
// Header row variables have been set. It's now time to 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 (idx, field) in headerstrings[3..*length].iter().enumerate() {
|
||||
|
||||
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.");
|
||||
}
|
||||
// Parse account numbers.
|
||||
let account_num = field.parse::<u16>().expect("Header row account number should parse into u16.");
|
||||
// For now, their columns aren't remembered. Instead, they must have a particular index. 0th idx is the 1st account, and so on.
|
||||
if account_num != ((idx + 1) as u16) {
|
||||
println!("FATAL: CSV Import: {}", acct_num_warn);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
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(); // no .to_uppercase() b/c margin...
|
||||
let name:String = header2[ind].trim().to_string();
|
||||
let ticker:String = header3[ind].trim().to_string(); // no .to_uppercase() b/c margin...
|
||||
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) }
|
||||
_ => {
|
||||
println!("\n FATAL: CSV Import: Couldn't parse margin value for account {} {} \n",account_num, name);
|
||||
process::exit(1)
|
||||
}
|
||||
};
|
||||
|
||||
let just_account: RawAccount = RawAccount {
|
||||
|
|
|
@ -52,7 +52,8 @@ impl Transaction {
|
|||
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); process::exit(1);
|
||||
println!("FATAL: TxType: Found transaction with two actionRecords with the same polarity: \n{:#?}", self);
|
||||
process::exit(1);
|
||||
}
|
||||
if ar1_ticker == ar2_ticker {
|
||||
if ar1_raw_acct.is_margin != ar2_raw_acct.is_margin {
|
||||
|
@ -67,10 +68,12 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
else if self.action_record_idx_vec.len() > 2 {
|
||||
println!("Program exiting. Found transaction with too many actionRecords: {:?}", self); process::exit(1);
|
||||
println!("FATAL: TxType: Found transaction with too many actionRecords: \n{:#?}", self);
|
||||
process::exit(1);
|
||||
}
|
||||
else {
|
||||
println!("Program exiting. Found transaction with no actionRecords: {:?}", self); process::exit(1);
|
||||
println!("FATAL: TxType: Found transaction with no actionRecords: \n{:#?}", self);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,11 +95,23 @@ impl Transaction {
|
|||
}
|
||||
} else {
|
||||
|
||||
assert_eq!(self.action_record_idx_vec.len(),2,
|
||||
"Each txn can only have one or two ARs. Txn has {} ARs.", self.action_record_idx_vec.len());
|
||||
if self.action_record_idx_vec.len() != 2 {
|
||||
println!("FATAL: Each transaction may have up to two actionrecords, and there are {} actionrecords in transaction:\n{:#?}",
|
||||
self.action_record_idx_vec.len(), self);
|
||||
}
|
||||
|
||||
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_ar = match ars.get(&self.action_record_idx_vec[0]) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
println!("FATAL: ActionRecord not found for: \n{:#?}", self);
|
||||
process::exit(1)}
|
||||
};
|
||||
let second_ar = match ars.get(&self.action_record_idx_vec[1]) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
println!("FATAL: ActionRecord not found for: \n{:#?}", self);
|
||||
process::exit(1)}
|
||||
};
|
||||
|
||||
let first_acct = acct_map.get(&first_ar.account_key).unwrap();
|
||||
let second_acct = acct_map.get(&second_ar.account_key).unwrap();
|
||||
|
@ -144,7 +159,8 @@ impl Transaction {
|
|||
quote = second_acct_raw_key;
|
||||
Ok((base, quote))
|
||||
} else {
|
||||
println!("{}", VariousErrors::MarginNoUnderbar); use std::process::exit; exit(1)
|
||||
println!("FATAL: {}", VariousErrors::MarginNoUnderbar);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,10 @@ pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathB
|
|||
"h" => { "-" }
|
||||
"s" => { "/" }
|
||||
"p" => { "." }
|
||||
_ => { println!("\nFATAL: The date-separator arg requires either an 'h', an 's', or a 'p'.\n"); process::exit(1) }
|
||||
_ => {
|
||||
println!("\nFATAL: ENV: The date-separator arg requires either an 'h', an 's', or a 'p'.\n");
|
||||
process::exit(1)
|
||||
}
|
||||
};
|
||||
|
||||
let input_file_path = match cmd_args.file_to_import {
|
||||
|
|
Loading…
Reference in New Issue