use crate::transaction::IncomeSource;
use chrono::{DateTime, Duration, Utc};
use rust_decimal::Decimal;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Term {
Short,
Long,
}
impl Term {
pub fn classify(acquired_at: DateTime<Utc>, disposed_at: DateTime<Utc>) -> Term {
if disposed_at - acquired_at > Duration::days(365) {
Term::Long
} else {
Term::Short
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RealizedGain {
pub asset: String,
pub wallet: String,
pub disposed_at: DateTime<Utc>,
pub acquired_at: Option<DateTime<Utc>>,
pub quantity: Decimal,
pub proceeds: Decimal,
pub cost_basis: Decimal,
pub gain: Decimal,
pub term: Option<Term>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IncomeEvent {
pub asset: String,
pub wallet: String,
pub received_at: DateTime<Utc>,
pub quantity: Decimal,
pub value: Decimal,
pub source: IncomeSource,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Holding {
pub asset: String,
pub wallet: String,
pub quantity: Decimal,
pub cost_basis: Decimal,
pub average_cost: Decimal,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AssetValuation {
pub asset: String,
pub quantity: Decimal,
pub cost_basis: Decimal,
pub price: Decimal,
pub market_value: Decimal,
pub unrealized: Decimal,
pub allocation: Decimal,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PortfolioReport {
pub assets: Vec<AssetValuation>,
pub total_cost: Decimal,
pub total_value: Decimal,
pub total_unrealized: Decimal,
pub total_return: Decimal,
pub missing_prices: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CapitalGainsReport {
pub tax_year: i32,
pub rows: Vec<RealizedGain>,
pub short_term_gain: Decimal,
pub long_term_gain: Decimal,
pub total_gain: Decimal,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IncomeReport {
pub tax_year: i32,
pub events: Vec<IncomeEvent>,
pub total_income: Decimal,
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Duration, TimeZone, Utc};
#[test]
fn term_boundary_365_is_short_366_is_long() {
let acquired = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap();
assert_eq!(
Term::classify(acquired, acquired + Duration::days(365)),
Term::Short
);
assert_eq!(
Term::classify(acquired, acquired + Duration::days(366)),
Term::Long
);
}
}