Fixing up a few bugs relating to rare positive liabilities
This commit is contained in:
parent
dcb0212105
commit
570cad39f4
|
@ -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() );
|
||||
|
|
125
src/books.rs
125
src/books.rs
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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")), ¤cy_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")), ¤cy_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")), ¤cy_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")), ¤cy_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
|
||||
|
|
Loading…
Reference in New Issue