1
0
Fork 1
ledgerneo/src/asset_account.rs

397 lines
15 KiB
Rust

use crate::books::TransactionReference;
use crate::CommoditiesPriceOracle;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::ops::Neg;
#[derive(Clone)]
pub struct AssetCommodity {
cost_basis: Decimal,
quantity: Decimal,
name: String,
}
impl AssetCommodity {
pub fn cost_basis_total(&self) -> Decimal {
self.cost_basis * self.quantity
}
}
#[derive(Clone)]
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,
opening_date: String,
}
impl AssetAccount {
pub fn new(name: String, opening_date: &String) -> AssetAccount {
AssetAccount {
name,
commodities: HashMap::new(),
realized_gains: Decimal::new(0, 0),
running_total: HashMap::new(),
transactions: vec![],
opening: HashMap::new(),
opening_date: opening_date.clone(),
}
}
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 != "CAD" && name != "$" && name != "StickerSheets" {
println!("[UG] Calculating Unrealized Gains for {}", self.name);
let mut unrealized_gain = Decimal::new(0, 0);
let historical_cost = commodities.iter().fold(Decimal::new(0, 0), |accum, x| {
println!("[UG] Historial {} {}", x.cost_basis, x.quantity);
accum + (x.cost_basis * x.quantity)
});
let current_cost = commodities.iter().fold(Decimal::new(0, 0), |accum, x| {
println!(
"[UG] Current {} {}",
oracle.lookup(&x.name, date),
x.quantity
);
accum + (oracle.lookup(&x.name, date) * x.quantity)
});
unrealized_gain += current_cost - historical_cost;
totals.push((name.clone(), unrealized_gain))
}
}
totals
}
pub fn total_nominal(&self, oracle: &CommoditiesPriceOracle, date: &String) -> f64 {
let mut total = 0.0;
for (commodity_name, quantity) in self.running_total.iter() {
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 opening_nominal(&self, oracle: &CommoditiesPriceOracle, date: &String) -> f64 {
let mut total = 0.0;
for (commodity_name, quantity) in self.opening.iter() {
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 trade(&mut self, txid: TransactionReference, name: String, cost: Decimal, quantity: Decimal, date: &String, oracle: &mut CommoditiesPriceOracle) -> Vec<(String, Decimal, String, Decimal)> {
let cost_basis = cost;
let asset_commodity = AssetCommodity {
cost_basis,
quantity,
name: name.clone(),
};
match self.commodities.contains_key(&name) {
false => {
self.commodities.insert(name.clone(), vec![]);
self.running_total.insert(name.clone(), Decimal::new(0, 0));
if *date == self.opening_date {
println!("Opening Total: {} {} {}", quantity, name, cost);
self.opening.insert(name.clone(), quantity);
}
}
_ => {}
}
*self.running_total.get_mut(&name).unwrap() += quantity;
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 meta_transactions;
}
match self.commodities.get_mut(&name) {
None => {
panic!();
}
Some(current_commodities) => {
current_commodities.sort_by(|a, b| a.cost_basis.cmp(&b.cost_basis));
current_commodities.reverse();
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!(
"[REALIZED GAIN CALC] Taking {} {} @@ {}",
commodity.quantity, commodity.name, commodity.cost_basis
);
base_cost += commodity.quantity * commodity.cost_basis;
quantity_needed -= commodity.quantity;
commodity.quantity = Decimal::new(0, 0);
} else {
// only use what we need from this...and then we are done...
println!(
"Taking {} {} @@ {}",
quantity_needed, commodity.name, commodity.cost_basis
);
base_cost += quantity_needed * commodity.cost_basis;
commodity.quantity -= quantity_needed;
break;
}
if quantity_needed.is_zero() {
break;
}
}
let mut oracle_price = oracle.lookup(&name, date);
let mut buying = false;
let arbitrage_difference = if oracle_price == Decimal::new(1, 0) {
buying = true;
println!(
"[WARNING] NO Price found for {} {} {}",
name, date, cost_basis
);
// oracle.insert(name.clone(), date.clone(), cost_basis);
Decimal::new(0, 0)
} else {
oracle_price - cost
};
if (base_cost - (cost * quantity.abs())).is_zero() == false {
println!(
"[DEBUG] Realized Gains: {} {} {} {} {} {} {} {} {}",
self.name,
(cost * quantity.abs()) - base_cost,
base_cost,
cost * quantity.abs(),
date,
base_cost / quantity,
cost,
oracle_price,
arbitrage_difference
);
}
let arbitrage_expense = (arbitrage_difference * quantity).neg();
let mut actual_gain = ((cost * quantity.abs()) - base_cost) + arbitrage_expense;
if buying {
actual_gain = Decimal::new(0, 0);
}
self.realized_gains += actual_gain;
println!(
"[REALIZED GAIN CALC] Realized {} base: {} oracle price: {} cost: {} quantity: {} ae: {} {} ",
name, base_cost, oracle_price, cost, quantity, arbitrage_expense, 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 {
println!("[UG] DEBUG ASSET TRADE {} {} {}", cost, quantity, name);
current_commodities.push(AssetCommodity {
cost_basis: cost_basis * quantity,
quantity: quantity,
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(), quantity, name.clone(), cost * quantity));
return meta_transactions;
}
}
}
}
pub fn deposit(&mut self, txid: TransactionReference, name: String, cost: Decimal, quantity: Decimal, date: &String, cost_basis: Decimal) {
let asset_commodity = AssetCommodity {
cost_basis,
quantity,
name: name.clone(),
};
match self.commodities.get_mut(&name) {
None => {
self.commodities.insert(name.clone(), vec![asset_commodity]);
self.running_total.insert(name.clone(), Decimal::new(0, 0));
if *date == self.opening_date {
println!(
"[REALIZED GAIN CALC] Opening: {} {} {}",
quantity, name, cost
);
self.opening.insert(name.clone(), quantity);
}
}
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()
);
}
}