From dfa4b532f020f08189a5384d21a6140c477491cb Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Fri, 9 Feb 2024 17:30:49 -0800 Subject: [PATCH] add account cost-basis command --- src/main.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/main.rs b/src/main.rs index d37dfb2..3aacd2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1959,6 +1959,66 @@ impl AnnualRealizedGain { } } +async fn process_account_cost_basis( + db: &Db, + when: NaiveDate, +) -> Result<(), Box> { + let mut held_tokens = + BTreeMap::>::default(); + + println!("Average Cost Basis on {when}"); + for disposed_lot in db.disposed_lots() { + if disposed_lot.lot.acquisition.when > when || disposed_lot.when < when { + continue; + } + held_tokens + .entry(disposed_lot.token) + .or_insert_with(Vec::new) + .push(( + disposed_lot.lot.amount, + disposed_lot.lot.acquisition.price(), + )); + } + + for account in db.get_accounts() { + let held_token = held_tokens.entry(account.token).or_insert_with(Vec::new); + for lot in account.lots { + if lot.acquisition.when <= when { + held_token.push((lot.amount, lot.acquisition.price())); + } + } + } + + // Merge wSOL lots into SOL + if let Some(mut lots) = held_tokens.remove(&Token::wSOL.into()) { + held_tokens + .entry(MaybeToken::SOL()) + .or_insert_with(Vec::new) + .append(&mut lots); + } + + for (token, lots) in held_tokens { + if lots.is_empty() || token.fiat_fungible() { + continue; + } + + let mut total_amount = 0; + let mut total_price = Decimal::default(); + + for (amount, price) in lots { + total_amount += amount; + total_price += Decimal::from_u64(amount).unwrap() * price; + } + println!( + " {:>7}: {:<20} at ${:.2}", + token.to_string(), + token.format_amount(total_amount), + total_price / Decimal::from_u64(total_amount).unwrap() + ); + } + Ok(()) +} + async fn process_account_list( db: &Db, rpc_client: &RpcClient, @@ -4308,6 +4368,19 @@ async fn main() -> Result<(), Box> { .help("Limit output to summary line"), ), ) + .subcommand( + SubCommand::with_name("cost-basis") + .about("Display average cost basis of holdings") + .arg( + Arg::with_name("when") + .value_name("YY/MM/DD") + .takes_value(true) + .required(false) + .validator(|value| naivedate_of(&value).map(|_| ())) + .default_value(&default_when) + .help("Date to calculate cost basis for") + ) + ) .subcommand( SubCommand::with_name("xls") .about("Export an Excel spreadsheet file") @@ -5796,6 +5869,13 @@ async fn main() -> Result<(), Box> { ) .await?; } + ("cost-basis", Some(arg_matches)) => { + let when = value_t!(arg_matches, "when", String) + .map(|s| naivedate_of(&s).unwrap()) + .unwrap(); + + process_account_cost_basis(&db, when).await?; + } ("xls", Some(arg_matches)) => { let outfile = value_t_or_exit!(arg_matches, "outfile", String); let filter_by_year = value_t!(arg_matches, "year", i32).ok();