1
0
Fork 1

Fixing up a few bugs relating to rare positive liabilities

This commit is contained in:
Sarah Jamie Lewis 2023-05-18 14:34:56 -07:00
parent dcb0212105
commit 570cad39f4
6 changed files with 225 additions and 101 deletions

View File

@ -1,9 +1,12 @@
use std::borrow::Borrow;
use crate::CommoditiesPriceOracle;
use crate::{CommoditiesPriceOracle, OPENING_DATE};
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::ops::Neg;
use chrono::Month::December;
use rust_decimal::prelude::ToPrimitive;
use spreadsheet_ods::text::Date;
use crate::books::TransactionReference;
#[derive(Clone)]
pub struct AssetCommodity {
@ -23,6 +26,8 @@ pub struct AssetAccount {
name: String,
commodities: HashMap<String, Vec<AssetCommodity>>,
running_total: HashMap<String, Decimal>,
// txid, date, commodity, quantity, cost,
pub(crate) transactions: Vec<(TransactionReference, String, String, Decimal, Decimal, String)>,
opening: HashMap<String, Decimal>,
pub(crate) realized_gains: Decimal,
}
@ -34,6 +39,7 @@ impl AssetAccount {
commodities: HashMap::new(),
realized_gains: Decimal::new(0, 0),
running_total: HashMap::new(),
transactions: vec![],
opening: HashMap::new(),
}
}
@ -41,7 +47,7 @@ impl AssetAccount {
pub fn unrealized_gains(&self, oracle: &CommoditiesPriceOracle, date: &String) -> Vec<(String, Decimal)> {
let mut totals = vec![];
for (name, commodities) in self.commodities.iter() {
if name != "$" && name != "StickerSheets" {
if name != "CAD" && name != "$" && name != "StickerSheets" {
let mut unrealized_gain = Decimal::new(0, 0);
let historical_cost = commodities.iter().fold(Decimal::new(0,0), |accum, x| {
@ -62,11 +68,19 @@ impl AssetAccount {
pub fn total_nominal(&self, oracle: &CommoditiesPriceOracle, date: &String) -> f64 {
let mut total = 0.0;
for (commodity_name, quantity) in self.running_total.iter() {
if commodity_name!= "$" && commodity_name != "StickerSheets" {
total += (oracle.lookup(commodity_name, date) * quantity.abs()).to_f64().unwrap();
} else if commodity_name == "$" {
total += quantity.abs().to_f64().unwrap()
let mut value = if commodity_name != "CAD" && commodity_name != "$" && commodity_name != "StickerSheets" {
(oracle.lookup(commodity_name, date) * quantity.abs()).to_f64().unwrap()
} else if commodity_name == "CAD" || commodity_name == "$" {
quantity.to_f64().unwrap()
} else {
0.0
};
if self.name.starts_with("Liability") {
// flip sign
value = value.neg()
}
total += value;
}
total
}
@ -74,17 +88,26 @@ impl AssetAccount {
pub fn opening_nominal(&self, oracle: &CommoditiesPriceOracle, date: &String) -> f64 {
let mut total = 0.0;
for (commodity_name, quantity) in self.opening.iter() {
if commodity_name!= "$" && commodity_name != "StickerSheets" {
total += (oracle.lookup(commodity_name, date) * quantity.abs()).to_f64().unwrap();
} else if commodity_name == "$" {
total += quantity.abs().to_f64().unwrap()
let mut value = if commodity_name != "CAD" && commodity_name!= "$" && commodity_name != "StickerSheets" {
(oracle.lookup(commodity_name, date) * quantity.abs()).to_f64().unwrap()
} else if commodity_name == "CAD" || commodity_name == "$" {
quantity.to_f64().unwrap()
} else {
0.0
};
if self.name.starts_with("Liability") {
// flip sign
value = value.neg()
}
total += value;
}
total
}
pub fn withdraw(&mut self, name: String, cost: Decimal, quantity: Decimal, date: &String) {
pub fn trade(&mut self, txid: TransactionReference, name: String, cost: Decimal, quantity: Decimal, date: &String, oracle: &CommoditiesPriceOracle) -> Vec<(String, Decimal, String, Decimal)>{
let cost_basis = cost;
let asset_commodity = AssetCommodity {
cost_basis,
@ -96,7 +119,7 @@ impl AssetAccount {
false => {
self.commodities.insert(name.clone(), vec![]);
self.running_total.insert(name.clone(), Decimal::new(0,0));
if date == "2022-02-11" {
if date == OPENING_DATE {
println!("Opening Total: {} {} {}", quantity, name, cost);
self.opening.insert(name.clone(), quantity);
}
@ -109,8 +132,11 @@ impl AssetAccount {
println!("Running Total: {} {} {} {}", quantity, name, cost, *self.running_total.get_mut(&name).unwrap() );
if self.name.starts_with("Assets") == false {
self.transactions.push((txid, date.clone(), name.clone(), cost, quantity, self.name.clone()));
let mut meta_transactions = vec![];
meta_transactions.push((self.name.clone(), quantity, name, cost*quantity));
// we don't track cost-basis for liabilities
return;
return meta_transactions;
}
match self.commodities.get_mut(&name) {
@ -127,8 +153,12 @@ impl AssetAccount {
let mut quantity_needed = quantity.abs();
let mut base_cost = Decimal::new(0,0);
if name != "$" {
for commodity in current_commodities.iter_mut() {
if commodity.quantity.is_zero() {
continue
}
if commodity.quantity <= quantity_needed {
// use all of this commodity....
println!("Taking {} {} @@ {}", commodity.quantity, commodity.name, commodity.cost_basis);
@ -146,23 +176,56 @@ impl AssetAccount {
break;
}
}
let oracle_price = oracle.lookup(&name, date);
let arbitrage_difference = oracle_price - cost;
if (base_cost - (cost * quantity.abs())).is_zero() == false {
println!("Realized Gains: {} {} {} {} {} {}", self.name, (cost * quantity.abs()) - base_cost, base_cost, cost * quantity.abs(), date, cost);
println!("Realized Gains: {} {} {} {} {} {} {} {} {}", self.name, (cost * quantity.abs()) - base_cost, base_cost, cost * quantity.abs(), date, base_cost/quantity, cost, oracle_price, arbitrage_difference);
}
self.realized_gains += (cost * quantity.abs()) - base_cost;
let arbitrage_expense = (arbitrage_difference * quantity).neg();
let actual_gain = ((cost * quantity.abs()) - base_cost) + arbitrage_expense;
self.realized_gains += actual_gain;
self.transactions.push((txid, date.clone(), name.clone(), cost, quantity, self.name.clone()));
let mut meta_transactions = vec![];
meta_transactions.push((self.name.clone(),cost*quantity, name.clone(), base_cost.neg()));
if actual_gain.is_zero() == false {
if actual_gain.is_sign_positive() {
self.transactions.push((txid, date.clone(), format!("$"), Decimal::new(1, 0), actual_gain.neg(), format!("Income:RealizedGains")));
meta_transactions.push((format!("Income:RealizedGains"), actual_gain.neg(), format!("$"), actual_gain.neg() ))
} else if actual_gain.is_sign_negative() {
self.transactions.push((txid, date.clone(), format!("$"), Decimal::new(1, 0), actual_gain.neg(), format!("Expenses:RealizedLoss")));
meta_transactions.push((format!("Expenses:RealizedLoss"), actual_gain.neg(), format!("$"), actual_gain.neg() ))
}
}
if arbitrage_expense.is_zero() == false {
if arbitrage_expense.is_sign_negative() {
self.transactions.push((txid, date.clone(), format!("$"), Decimal::new(1, 0), arbitrage_expense, format!("Income:ExchangeArbitrage")));
meta_transactions.push((format!("Income:ExchangeArbitrage"), arbitrage_expense, format!("$") ,arbitrage_expense))
} else if arbitrage_expense.is_sign_positive() {
self.transactions.push((txid, date.clone(), format!("$"), Decimal::new(1, 0), arbitrage_expense, format!("Expenses:BankingFees:ExchangeArbitrage")));
meta_transactions.push((format!("Expenses:BankingFees:ExchangeArbitrage"), arbitrage_expense, format!("$"),arbitrage_expense ))
}
}
return meta_transactions;
} else {
current_commodities.push(AssetCommodity{
cost_basis: Decimal::new(1,0),
quantity: quantity,
name
})
name: name.clone()
});
let mut meta_transactions = vec![];
self.transactions.push((txid, date.clone(), name.clone(), cost, quantity, self.name.clone()));
meta_transactions.push((self.name.clone(), cost*quantity, name.clone(), cost*quantity));
return meta_transactions;
}
}
}
}
}
pub fn deposit(&mut self, name: String, cost: Decimal, quantity: Decimal, date: &String) {
pub fn deposit(&mut self, txid: TransactionReference, name: String, cost: Decimal, quantity: Decimal, date: &String) {
let cost_basis = cost;
let asset_commodity = AssetCommodity {
cost_basis,
@ -174,7 +237,7 @@ impl AssetAccount {
None => {
self.commodities.insert(name.clone(), vec![asset_commodity]);
self.running_total.insert(name.clone(), Decimal::new(0,0));
if date == "2022-02-11" {
if date == OPENING_DATE {
println!("Opening: {} {} {}", quantity, name, cost);
self.opening.insert(name.clone(), quantity);
}
@ -182,6 +245,7 @@ impl AssetAccount {
Some(account) => account.push(asset_commodity),
}
self.transactions.push((txid, date.clone(), name.clone(), cost, quantity, self.name.clone()));
*self.running_total.get_mut(&name).unwrap() += quantity;
println!("Running Total: {} {} {} {}", quantity, name, cost, *self.running_total.get_mut(&name).unwrap() );

View File

@ -1,5 +1,5 @@
use crate::{AssetAccount, CommoditiesPriceOracle, ExternalAccount};
use ledger_parser::{LedgerItem, PostingAmount};
use ledger_parser::{LedgerItem, Posting, PostingAmount, Transaction};
use rust_decimal::Decimal;
use std::collections::{HashMap, HashSet};
use std::fs::read_to_string;
@ -13,7 +13,7 @@ pub struct Books {
pub(crate) asset_accounts: HashMap<String, AssetAccount>,
pub(crate) external_accounts: HashMap<String, ExternalAccount>,
pub(crate) commodities_oracle: CommoditiesPriceOracle,
pub general_ledger: Vec<(TransactionReference, String, Vec<(String, String, Decimal)>)>,
pub general_ledger: Vec<(TransactionReference, String, Vec<(String, String, Decimal, Decimal)>)>,
}
impl Books {
@ -37,8 +37,8 @@ impl Books {
let mut total = Decimal::new(0, 0);
let mut commodities = HashSet::new();
let mut general_ledger_entry = vec![];
let mut general_ledger_entry = vec![];
if tx.date > NaiveDate::from_str(&to_date).unwrap() {
break;
}
@ -47,25 +47,8 @@ impl Books {
match (&tx.postings[0].amount, &tx.postings[1].amount) {
(Some(a), Some(b)) => {
if a.amount.commodity != b.amount.commodity {
let a_account = tx.postings[0].account.clone();
let b_account = tx.postings[1].account.clone();
if a.amount.quantity.is_sign_negative() {
// Trading a for b
let cost_basis = b.amount.quantity / a.amount.quantity.abs() ;
self.asset_accounts.get_mut(&a_account).unwrap().withdraw(a.amount.commodity.name.clone(), cost_basis, a.amount.quantity, &tx.date.to_string());
self.asset_accounts.get_mut(&b_account).unwrap().deposit(b.amount.commodity.name.clone(), Decimal::new(1,0), b.amount.quantity, &tx.date.to_string());
} else {
panic!("Trade order should have selling first {} {}",a,b);
}
general_ledger_entry.push((a_account, a.amount.commodity.name.clone(), a.amount.quantity));
general_ledger_entry.push((b_account, b.amount.commodity.name.clone(), b.amount.quantity));
self.general_ledger.push((
id as TransactionReference,
tx.date.to_string(),
general_ledger_entry,
));
// handle trading
self.handle_commodities_exchange(id, &tx);
continue
}
}
@ -88,13 +71,8 @@ impl Books {
total += amount.amount.quantity;
commodities.insert(amount.amount.commodity.name.clone());
general_ledger_entry.push((
posting.account.clone(),
amount.amount.commodity.name.clone(),
amount.amount.quantity.clone(),
));
self.update_account(
self.update_account(0,
&mut general_ledger_entry,
id as TransactionReference,
&posting.account,
&tx.date.to_string(),
@ -113,16 +91,14 @@ impl Books {
println!("{},{},{},{}", id, tx.date, posting.account, total);
let commodity: Vec<&String> = commodities.iter().collect();
self.update_account(
self.update_account(0,
&mut general_ledger_entry,
id as TransactionReference,
&posting.account,
&tx.date.to_string(),
commodity[0],
-total,
);
general_ledger_entry.push((posting.account.clone(), commodity[0].clone(), -total));
}
_ => {}
}
@ -144,8 +120,8 @@ impl Books {
}
}
pub fn update_account(&mut self, tx_ref: TransactionReference, account_name: &String, tx_date: &String, commodity_name: &String, commodity_quantity: Decimal) {
if account_name.starts_with("Assets:") || account_name.starts_with("Liability:"){
pub fn update_account(&mut self, depth: usize, general_ledger_entry: &mut Vec<(String, String, Decimal, Decimal)>, tx_ref: TransactionReference, account_name: &String, tx_date: &String, commodity_name: &String, commodity_quantity: Decimal) {
if account_name.starts_with("Assets:") || account_name.starts_with("Liability:") {
match self.asset_accounts.contains_key(account_name) {
false => {
self.asset_accounts.insert(
@ -156,37 +132,29 @@ impl Books {
_ => {}
}
let asset_account = self.asset_accounts.get_mut(account_name).unwrap();
if commodity_name != "$" && commodity_name != "StickerSheets" {
let cost_basis = self.commodities_oracle.lookup(commodity_name, tx_date);
if commodity_quantity.is_sign_positive() {
asset_account.deposit(commodity_name.clone(), cost_basis, commodity_quantity, tx_date);
asset_account.deposit( tx_ref, commodity_name.clone(), cost_basis, commodity_quantity, tx_date);
general_ledger_entry.push((account_name.clone(), commodity_name.clone(), commodity_quantity, commodity_quantity*cost_basis));
} else {
asset_account.withdraw(
let extra_transactions = asset_account.trade(
tx_ref,
commodity_name.clone(),
cost_basis,
commodity_quantity,
tx_date,
&self.commodities_oracle
);
for (eaccount_name, commodity_quantity, commodity, nominal_value) in extra_transactions {
if *account_name != eaccount_name && depth == 0 {
self.update_account(1, general_ledger_entry, tx_ref, &eaccount_name.clone(), tx_date, &commodity, nominal_value)
} else {
general_ledger_entry.push((eaccount_name.clone(), commodity, commodity_quantity, nominal_value));
}
}
}
} else {
if commodity_quantity.is_sign_positive() {
asset_account.deposit(
commodity_name.clone(),
Decimal::new(1, 0),
commodity_quantity,
tx_date
);
} else {
asset_account.withdraw(
commodity_name.clone(),
Decimal::new(1, 0),
commodity_quantity,
tx_date,
);
}
}
} else {
// This is an external account
match self.external_accounts.contains_key(account_name) {
@ -196,8 +164,7 @@ impl Books {
} else if account_name.starts_with("Expenses") {
ExternalAccount::new(account_name.clone(), false)
} else {
// panic!("Unable to track {}", account_name)
return;
ExternalAccount::new(account_name.clone(), false)
};
self.external_accounts
@ -207,15 +174,49 @@ impl Books {
}
let external_account = self.external_accounts.get_mut(account_name).unwrap();
let cost_basis = self.commodities_oracle.lookup(commodity_name, tx_date);
if !external_account.income_account {
external_account.deposit(tx_ref, commodity_name.clone(), commodity_quantity, tx_date)
external_account.deposit(tx_ref, commodity_name.clone(), commodity_quantity, tx_date);
general_ledger_entry.push((account_name.clone(), commodity_name.clone(), commodity_quantity, cost_basis*commodity_quantity));
} else {
external_account.withdraw(tx_ref, commodity_name.clone(), commodity_quantity, tx_date)
external_account.withdraw(tx_ref, commodity_name.clone(), commodity_quantity, tx_date);
general_ledger_entry.push((account_name.clone(), commodity_name.clone(), commodity_quantity, cost_basis*commodity_quantity));
}
}
}
fn handle_commodities_exchange(&mut self, id: usize, tx: &Transaction) {
let mut general_ledger_entry = vec![];
let a = tx.postings[0].amount.as_ref().unwrap();
let b = tx.postings[1].amount.as_ref().unwrap();
let a_account = tx.postings[0].account.clone();
let b_account = tx.postings[1].account.clone();
if a.amount.quantity.is_sign_negative() {
// Trading a for b
let cost_basis = b.amount.quantity / a.amount.quantity.abs() ;
let extra_transactions = self.asset_accounts.get_mut(&a_account).unwrap().trade(id as TransactionReference,a.amount.commodity.name.clone(), cost_basis, a.amount.quantity, &tx.date.to_string(), &self.commodities_oracle);
for (eaccount_name, quantity, commodity, nominal_value ) in extra_transactions {
if eaccount_name == a_account {
general_ledger_entry.push((eaccount_name.clone(), commodity.clone(), quantity, nominal_value));
} else {
self.update_account(1, &mut general_ledger_entry, id as TransactionReference, &eaccount_name, &tx.date.to_string(), &format!("$"), nominal_value)
}
}
self.asset_accounts.get_mut(&b_account).unwrap().deposit(id as TransactionReference, b.amount.commodity.name.clone(), Decimal::new(1,0), b.amount.quantity, &tx.date.to_string());
general_ledger_entry.push((b_account, b.amount.commodity.name.clone(), b.amount.quantity, Decimal::new(1,0) * b.amount.quantity,));
} else {
panic!("Trade order should have selling first {} {}",a,b);
}
self.general_ledger.push((
id as TransactionReference,
tx.date.to_string(),
general_ledger_entry,
));
}
pub fn summarize_accounts(&self) {
for (account_name, account) in self.external_accounts.iter() {
println!("{}", account_name);
@ -244,6 +245,6 @@ impl Books {
"Total Realized + Unrealized Gains: {}",
unrealized_gains + realized_gains
);
return (unrealized_gains+realized_gains).to_f64().unwrap()
return (unrealized_gains).to_f64().unwrap()
}
}

View File

@ -50,9 +50,9 @@ impl ExternalAccount {
pub fn total_nominal(&self, oracle: &CommoditiesPriceOracle) -> f64 {
let mut total = 0.0;
for (txid, date, commodity, quantity) in self.transactions.iter() {
if commodity != "$" && commodity != "StickerSheets" {
if commodity != "CAD" && commodity != "$" && commodity != "StickerSheets" {
total += (oracle.lookup(commodity, date) * quantity.abs()).to_f64().unwrap();
} else if commodity == "$" {
} else if commodity == "CAD" || commodity == "$" {
total += quantity.abs().to_f64().unwrap()
}
}

View File

@ -10,9 +10,11 @@ pub mod external_account;
pub mod oracle;
pub mod spreadsheet;
pub const OPENING_DATE : &str = "2023-02-11";
fn main() {
let mut books = Books::new();
let stop_date = format!("2023-02-10");
books.load_ledger("2022-archive.dat", &stop_date);
export_to_spreadsheet("test.ods", &books, &format!("2022-02-11"), &stop_date);
let stop_date = format!("2023-05-18");
books.load_ledger("2023.dat", &stop_date);
export_to_spreadsheet("test.ods", &books, &format!("{}", OPENING_DATE), &stop_date);
}

View File

@ -30,7 +30,8 @@ impl CommoditiesPriceOracle {
pub fn lookup(&self, commodity: &String, date: &String) -> Decimal {
match self.commodities.get(commodity) {
None => {
panic!("no price history data for commodity: {}", commodity)
println!("no price history data for commodity: {}", commodity);
return Decimal::new(1,0)
}
Some(price_history) => match price_history.get(date) {
None => {

View File

@ -178,10 +178,10 @@ pub fn export_to_spreadsheet(name: &str, books: &Books, opening_date: &String,
balance_statement.set_formula(balance_sheet_summary_row+1,1, format!("B{}+D{}", balance_sheet_summary_row+1,balance_sheet_opening_assets_row+1));
let exchange_gains = books.calculate_gains(close_date);
balance_statement.set_styled_value(balance_sheet_summary_row+2,0, format!("Realized Gains/Losses"), &header_style_ref);
balance_statement.set_styled_value(balance_sheet_summary_row+2,0, format!("Unrealized Gains/Losses (Assets)"), &header_style_ref);
balance_statement.set_styled_value(balance_sheet_summary_row+2,1, Currency(exchange_gains, format!("CAD")), &currency_pretty_print_style);
balance_statement.set_styled_value(balance_sheet_summary_row+3,0, format!("Balance"), &header_style_ref);
balance_statement.set_styled_value(balance_sheet_summary_row+3,0, format!("Opening Assets + Income + Unrealized Gains/Losses (Assets)"), &header_style_ref);
balance_statement.set_formula(balance_sheet_summary_row+3,1, format!("B{}+B{}", balance_sheet_summary_row+2,balance_sheet_summary_row+3));
@ -214,29 +214,39 @@ pub fn export_to_spreadsheet(name: &str, books: &Books, opening_date: &String,
let mut row_index = 2;
// Create a sheet for each tracked account
let mut running_total = Decimal::new(0,0);
for (txid, date, postings) in books.general_ledger.iter() {
general_ledger.set_styled_value(row_index, 0, Value::Number(*txid as f64), &header_style_ref);
let recorded_date = NaiveDate::from_str(date).unwrap();
general_ledger.set_styled_value(row_index, 1, Value::DateTime(recorded_date.and_time(NaiveTime::default())), &header_style_ref);
row_index +=1;
for(account, commodity, quantity) in postings.iter() {
let nominal_value = if commodity != "$" && commodity != "StickerSheets" {
books.commodities_oracle.lookup(commodity, date) * quantity
} else {
Decimal::new(1,0 ) * quantity
};
for(account, commodity, quantity, nominal_value) in postings.iter() {
running_total += nominal_value;
general_ledger.set_styled_value(row_index, 2, Value::Text(account.clone()), &general_text_style);
general_ledger.set_styled_value(row_index, 3, Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")), &currency_pretty_print_style);
general_ledger.set_styled_value(row_index, 4, Value::Currency(quantity.to_f64().unwrap(), commodity.clone()),&general_text_style);
general_ledger.set_styled_value(row_index, 5, Value::Text(commodity.clone().replace("$", "CAD")), &general_text_style);
row_index +=1;
}
general_ledger.set_styled_value(row_index, 3, Value::Currency(running_total.to_f64().unwrap(), String::from("CAD")), &currency_pretty_print_style);
row_index+=2;
}
wb.push_sheet(general_ledger);
// Create a sheet for each tracked account
for (external_account, account) in external_accounts.iter() {
// Create a sheet for each asset account
// We first need a list of all of our incomes and expenses, ordered by value...
let mut asset_accounts : Vec<(String, AssetAccount)> = books.asset_accounts.iter().map(|(a,b)| (a.clone(), b.clone())).collect();
asset_accounts.sort_by(|a,b| {
if a.0.starts_with("Assets") && b.0.starts_with("Liability") {
return Ordering::Less
} if a.0.starts_with("Liability") && b.0.starts_with("Assets") {
return Ordering::Greater
} else {
return b.1.total_nominal(&books.commodities_oracle, close_date).total_cmp(&a.1.total_nominal(&books.commodities_oracle, close_date))
}
});
for (external_account, account) in asset_accounts.iter() {
let mut sheet = Sheet::new(format!("{}", external_account.to_lowercase().replace(":", "")));
sheet.set_styled_value(0, 0, Value::Text(format!("Account: {}", external_account)), &header_style_ref);
@ -262,10 +272,56 @@ pub fn export_to_spreadsheet(name: &str, books: &Books, opening_date: &String,
sheet.set_header_rows(start_row,1);
let start_row = 2;
for(row, (txref, date, commodity, cost, quantity, booked_account)) in account.transactions.iter().enumerate() {
let index = start_row+(row as u32);
let recorded_date = NaiveDate::from_str(date).unwrap();
let nominal_value = if commodity != "CAD" && commodity != "$" && commodity != "StickerSheets" {
cost * quantity
} else {
Decimal::new(1,0 ) * quantity
};
sheet.set_styled_value(index, 0, Value::Number(*txref as f64), &header_style_ref);
sheet.set_styled_value(index, 1, Value::DateTime(recorded_date.and_time(NaiveTime::default())), &general_text_style);
sheet.set_styled_value(index, 2, Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")), &currency_pretty_print_style);
sheet.set_styled_value(index, 3, Value::Currency(quantity.to_f64().unwrap(), commodity.clone()), &general_text_style);
sheet.set_styled_value(index, 4, Value::Text(commodity.clone().replace("$", "CAD")), &general_text_style);
sheet.set_styled_value(index, 5, Value::Text(booked_account.clone()), &general_text_style);
}
wb.push_sheet(sheet);
}
// Create a sheet for each tracked account
for (external_account, account) in external_accounts.iter() {
let mut sheet = Sheet::new(format!("{}", external_account.to_lowercase().replace(":", "")));
sheet.set_styled_value(0, 0, Value::Text(format!("Account: {}", external_account)), &header_style_ref);
let mut start_row = 1;
sheet.set_styled_value(start_row, 0, Value::Text(String::from("Ref")), &header_style_ref);
sheet.set_col_width(0, Length::Pc(15.0));
sheet.set_styled_value(start_row, 1, Value::Text(String::from("Date")), &header_style_ref);
sheet.set_col_width(1, Length::Pc(15.0));
sheet.set_styled_value(start_row, 2, Value::Text(String::from("Recorded CAD Value")), &header_style_ref);
sheet.set_col_width(2, Length::Pc(15.0));
sheet.set_styled_value(start_row, 3, Value::Text(String::from("Recorded Commodity Value")), &header_style_ref);
sheet.set_col_width(3, Length::Pc(15.0));
sheet.set_styled_value(start_row, 4, Value::Text(String::from("Commodity")), &header_style_ref);
sheet.set_col_width(4, Length::Pc(15.0));
sheet.set_header_rows(start_row,1);
start_row += 1;
for(row, (txref, date, commodity, quantity)) in account.transactions.iter().enumerate() {
let index = start_row+(row as u32);
let recorded_date = NaiveDate::from_str(date).unwrap();
let nominal_value = if commodity != "$" && commodity != "StickerSheets" {
let nominal_value = if commodity != "CAD" && commodity != "$" && commodity != "StickerSheets" {
books.commodities_oracle.lookup(commodity, date) * quantity
} else {
Decimal::new(1,0 ) * quantity