use crate::lot::Lot;
use rust_decimal::Decimal;
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CostBasisMethod {
Fifo,
Lifo,
Hifo,
Average,
SpecificId,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LotPick {
pub acquisition_index: usize,
pub quantity: Decimal,
}
pub type LotSelection = HashMap<usize, Vec<LotPick>>;
pub(crate) fn order_for(method: CostBasisMethod, lots: &[Lot]) -> Vec<usize> {
let mut idx: Vec<usize> = (0..lots.len()).collect();
match method {
CostBasisMethod::Fifo => {
idx.sort_by(|&a, &b| {
lots[a]
.acquired_at
.cmp(&lots[b].acquired_at)
.then(lots[a].lot_id.cmp(&lots[b].lot_id))
});
}
CostBasisMethod::Lifo => {
idx.sort_by(|&a, &b| {
lots[b]
.acquired_at
.cmp(&lots[a].acquired_at)
.then(lots[b].lot_id.cmp(&lots[a].lot_id))
});
}
CostBasisMethod::Hifo => {
idx.sort_by(|&a, &b| {
lots[b]
.cost_basis_per_unit()
.cmp(&lots[a].cost_basis_per_unit())
.then(lots[a].lot_id.cmp(&lots[b].lot_id))
});
}
CostBasisMethod::Average | CostBasisMethod::SpecificId => {
idx.sort_by(|&a, &b| {
lots[a]
.acquired_at
.cmp(&lots[b].acquired_at)
.then(lots[a].lot_id.cmp(&lots[b].lot_id))
});
}
}
idx
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lot::Lot;
use chrono::{TimeZone, Utc};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
fn lot(id: u64, day: u32, basis: i64, qty: i64) -> Lot {
Lot {
asset: "btc".into(),
wallet: "w".into(),
quantity: dec!(0) + Decimal::from(qty),
cost_basis: Decimal::from(basis),
acquired_at: Utc.with_ymd_and_hms(2021, 1, day, 0, 0, 0).unwrap(),
lot_id: id,
gift: None,
}
}
#[test]
fn fifo_orders_oldest_first() {
let lots = vec![lot(1, 3, 30, 1), lot(2, 1, 10, 1), lot(3, 2, 20, 1)];
assert_eq!(order_for(CostBasisMethod::Fifo, &lots), vec![1, 2, 0]);
}
#[test]
fn lifo_orders_newest_first() {
let lots = vec![lot(1, 1, 10, 1), lot(2, 3, 30, 1), lot(3, 2, 20, 1)];
assert_eq!(order_for(CostBasisMethod::Lifo, &lots), vec![1, 2, 0]);
}
#[test]
fn hifo_orders_highest_unit_cost_first() {
let lots = vec![lot(1, 1, 10, 1), lot(2, 2, 30, 1), lot(3, 3, 20, 1)];
assert_eq!(order_for(CostBasisMethod::Hifo, &lots), vec![1, 2, 0]);
}
#[test]
fn average_and_specific_id_fall_back_to_fifo_order() {
let lots = vec![lot(1, 3, 30, 1), lot(2, 1, 10, 1), lot(3, 2, 20, 1)];
assert_eq!(order_for(CostBasisMethod::Average, &lots), vec![1, 2, 0]);
assert_eq!(order_for(CostBasisMethod::SpecificId, &lots), vec![1, 2, 0]);
}
}