Skip to content

Commit 1e891c4

Browse files
committed
Add account subcommand to export cap gains to CSV file
1 parent 308856f commit 1e891c4

File tree

3 files changed

+110
-1
lines changed

3 files changed

+110
-1
lines changed

Cargo.lock

+23-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@ tokio = { version = "1", features = ["macros", "time"] }
7070
#tulipv2-sdk-common = "0.9.5"
7171
uint = "0.9.5"
7272
criterion-stats = "0.3.0"
73+
csv = "1.3.1"

src/main.rs

+86
Original file line numberDiff line numberDiff line change
@@ -2841,6 +2841,69 @@ async fn process_account_xls(
28412841
Ok(())
28422842
}
28432843

2844+
async fn process_account_csv(
2845+
db: &Db,
2846+
outfile: &str,
2847+
filter_by_year: Option<i32>,
2848+
) -> Result<(), Box<dyn std::error::Error>> {
2849+
use csv::Writer;
2850+
2851+
let mut wtr = Writer::from_path(outfile)?;
2852+
let mut disposed_lots = db.disposed_lots();
2853+
disposed_lots.sort_by_key(|lot| lot.when);
2854+
2855+
if let Some(year) = filter_by_year {
2856+
// Exclude disposed lots that were neither acquired nor disposed of in the filter year
2857+
disposed_lots.retain(|disposed_lot| {
2858+
(disposed_lot.lot.acquisition.when.year() == year
2859+
&& disposed_lot.lot.income(disposed_lot.token) > 0.)
2860+
|| disposed_lot.when.year() == year
2861+
})
2862+
}
2863+
wtr.write_record(&[
2864+
"Token",
2865+
"Amount",
2866+
"Income (USD)",
2867+
"Cap Gain (USD)",
2868+
"Acq. Date",
2869+
"Acq. Cost (USD)",
2870+
"Sale Date",
2871+
"Sale Proceedings (USD)",
2872+
"Acquisition Description",
2873+
"Sale Description"
2874+
])?;
2875+
2876+
for disposed_lot in disposed_lots {
2877+
let mut income = disposed_lot.lot.income(disposed_lot.token);
2878+
if let Some(year) = filter_by_year {
2879+
if disposed_lot.lot.acquisition.when.year() != year {
2880+
income = 0. // Exclude income from other years
2881+
}
2882+
}
2883+
let cost = Decimal::from_u64(disposed_lot.lot.amount).unwrap()
2884+
* disposed_lot.lot.acquisition.price() / Decimal::from_f64(1e9).unwrap();
2885+
let proceedings = Decimal::from_u64(disposed_lot.lot.amount).unwrap()
2886+
* disposed_lot.price() / Decimal::from_f64(1e9).unwrap();
2887+
wtr.write_record(&[
2888+
disposed_lot.token.to_string(),
2889+
format!("{:.9}", disposed_lot.token.ui_amount(disposed_lot.lot.amount)),
2890+
format!("{:.9}", income),
2891+
format!("{:.9}", disposed_lot.lot.cap_gain(disposed_lot.token, disposed_lot.price())),
2892+
disposed_lot.lot.acquisition.when.to_string(),
2893+
format!("{:.9}", cost),
2894+
disposed_lot.when.to_string(),
2895+
format!("{:.9}", proceedings),
2896+
disposed_lot.lot.acquisition.kind.to_string(),
2897+
disposed_lot.kind.to_string()
2898+
])?;
2899+
}
2900+
2901+
wtr.flush()?;
2902+
println!("Wrote {outfile}");
2903+
2904+
Ok(())
2905+
}
2906+
28442907
#[allow(clippy::too_many_arguments)]
28452908
async fn process_account_merge<T: Signers>(
28462909
db: &mut Db,
@@ -4684,6 +4747,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
46844747
.help("Limit export to realized gains affecting the given year"),
46854748
),
46864749
)
4750+
.subcommand(
4751+
SubCommand::with_name("csv")
4752+
.about("Export a CSV file")
4753+
.arg(
4754+
Arg::with_name("outfile")
4755+
.value_name("FILEPATH")
4756+
.takes_value(true)
4757+
.help(".csv file to write"),
4758+
)
4759+
.arg(
4760+
Arg::with_name("year")
4761+
.long("year")
4762+
.value_name("YYYY")
4763+
.takes_value(true)
4764+
.validator(is_parsable::<usize>)
4765+
.help("Limit export to realized gains affecting the given year"),
4766+
),
4767+
)
46874768
.subcommand(
46884769
SubCommand::with_name("remove")
46894770
.about("Unregister an account")
@@ -6144,6 +6225,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
61446225
let filter_by_year = value_t!(arg_matches, "year", i32).ok();
61456226
process_account_xls(&db, &outfile, filter_by_year).await?;
61466227
}
6228+
("csv", Some(arg_matches)) => {
6229+
let outfile = value_t_or_exit!(arg_matches, "outfile", String);
6230+
let filter_by_year = value_t!(arg_matches, "year", i32).ok();
6231+
process_account_csv(&db, &outfile, filter_by_year).await?;
6232+
}
61476233
("remove", Some(arg_matches)) => {
61486234
let address = pubkey_of(arg_matches, "address").unwrap();
61496235
let token = MaybeToken::from(value_t!(arg_matches, "token", Token).ok());

0 commit comments

Comments
 (0)