use chrono::{DateTime, Utc};
use paft::domain::{Exchange, MarketState, Period};
use paft::fundamentals::analysis::{RecommendationAction, RecommendationGrade};
use paft::fundamentals::holders::{InsiderPosition, TransactionType};
use paft::fundamentals::profile::FundKind;
use paft::money::{Currency, IsoCurrency, Money};
use rust_decimal::prelude::ToPrimitive;
use std::str::FromStr;
fn f64_to_decimal_safely(value: f64) -> rust_decimal::Decimal {
if !value.is_finite() {
return rust_decimal::Decimal::ZERO;
}
let formatted = format!("{value:.4}");
rust_decimal::Decimal::from_str(&formatted).unwrap_or(rust_decimal::Decimal::ZERO)
}
#[must_use]
pub fn f64_to_money_with_currency(value: f64, currency: Currency) -> Money {
let decimal = f64_to_decimal_safely(value);
Money::new(decimal, currency).expect("currency metadata available")
}
#[must_use]
pub fn i64_to_money_with_currency(value: i64, currency: Currency) -> Money {
let decimal = rust_decimal::Decimal::from_i128_with_scale(i128::from(value), 0);
Money::new(decimal, currency).expect("currency metadata available")
}
#[must_use]
pub fn u64_to_money_with_currency(value: u64, currency: Currency) -> Money {
let decimal = rust_decimal::Decimal::from_i128_with_scale(i128::from(value), 0);
Money::new(decimal, currency).expect("currency metadata available")
}
#[must_use]
pub fn f64_to_money_with_currency_str(value: f64, currency_str: Option<&str>) -> Money {
let currency = currency_str
.and_then(|s| Currency::from_str(s).ok())
.unwrap_or(Currency::Iso(IsoCurrency::USD));
f64_to_money_with_currency(value, currency)
}
#[must_use]
pub fn money_to_f64(money: &Money) -> f64 {
money.amount().to_f64().unwrap_or(0.0)
}
#[must_use]
pub fn money_to_currency_str(money: &Money) -> Option<String> {
Some(money.currency().to_string())
}
#[must_use]
pub fn i64_to_datetime(timestamp: i64) -> DateTime<Utc> {
DateTime::from_timestamp(timestamp, 0).unwrap_or_default()
}
#[must_use]
pub const fn datetime_to_i64(dt: DateTime<Utc>) -> i64 {
dt.timestamp()
}
#[allow(clippy::single_option_map)]
#[must_use]
pub fn string_to_exchange(s: Option<String>) -> Option<Exchange> {
s.and_then(|s| {
match s.as_str() {
"NasdaqGS" | "NasdaqCM" | "NasdaqGM" => Some(Exchange::NASDAQ),
"NYSE" => Some(Exchange::NYSE),
"AMEX" => Some(Exchange::AMEX),
"BATS" => Some(Exchange::BATS),
"OTC" => Some(Exchange::OTC),
"LSE" => Some(Exchange::LSE),
"TSE" => Some(Exchange::TSE),
"HKEX" => Some(Exchange::HKEX),
"SSE" => Some(Exchange::SSE),
"SZSE" => Some(Exchange::SZSE),
"TSX" => Some(Exchange::TSX),
"ASX" => Some(Exchange::ASX),
"Euronext" => Some(Exchange::Euronext),
"XETRA" => Some(Exchange::XETRA),
"SIX" => Some(Exchange::SIX),
"BIT" => Some(Exchange::BIT),
"BME" => Some(Exchange::BME),
"AEX" => Some(Exchange::AEX),
"BRU" => Some(Exchange::BRU),
"LIS" => Some(Exchange::LIS),
"EPA" => Some(Exchange::EPA),
"OSL" => Some(Exchange::OSL),
"STO" => Some(Exchange::STO),
"CPH" => Some(Exchange::CPH),
"WSE" => Some(Exchange::WSE),
"PSE" => Some(Exchange::PSE),
"BSE" => Some(Exchange::BSE),
"MOEX" => Some(Exchange::MOEX),
"BIST" => Some(Exchange::BIST),
"JSE" => Some(Exchange::JSE),
"TASE" => Some(Exchange::TASE),
"BSE_HU" => Some(Exchange::BSE_HU),
"NSE" => Some(Exchange::NSE),
"KRX" => Some(Exchange::KRX),
"SGX" => Some(Exchange::SGX),
"SET" => Some(Exchange::SET),
"KLSE" => Some(Exchange::KLSE),
"PSE_CZ" => Some(Exchange::PSE_CZ),
"IDX" => Some(Exchange::IDX),
"HOSE" => Some(Exchange::HOSE),
_ => Exchange::try_from(s).ok(),
}
})
}
#[must_use]
pub fn exchange_to_string(exchange: Option<Exchange>) -> Option<String> {
exchange.map(|e| e.to_string())
}
#[must_use]
pub fn string_to_market_state(s: Option<String>) -> Option<MarketState> {
s.and_then(|s| s.parse().ok())
}
#[must_use]
pub fn market_state_to_string(state: Option<MarketState>) -> Option<String> {
state.map(|s| s.to_string())
}
#[allow(clippy::single_option_map)]
#[must_use]
pub fn string_to_fund_kind(s: Option<String>) -> Option<FundKind> {
s.and_then(|s| {
match s.as_str() {
"Exchange Traded Fund" => Some(FundKind::Etf),
"Mutual Fund" => Some(FundKind::MutualFund),
"Index Fund" => Some(FundKind::IndexFund),
"Closed-End Fund" => Some(FundKind::ClosedEndFund),
"Money Market Fund" => Some(FundKind::MoneyMarketFund),
"Hedge Fund" => Some(FundKind::HedgeFund),
"Real Estate Investment Trust" => Some(FundKind::Reit),
"Unit Investment Trust" => Some(FundKind::UnitInvestmentTrust),
_ => FundKind::try_from(s).ok(),
}
})
}
#[must_use]
pub fn fund_kind_to_string(kind: Option<FundKind>) -> Option<String> {
kind.map(|k| k.to_string())
}
#[must_use]
pub fn string_to_insider_position(s: &str) -> InsiderPosition {
let token = s.trim();
let token_nonempty = if token.is_empty() { "UNKNOWN" } else { token };
token_nonempty.parse().unwrap_or(InsiderPosition::Officer)
}
#[must_use]
pub fn string_to_transaction_type(s: &str) -> TransactionType {
let token = s.trim();
let token_nonempty = if token.is_empty() { "UNKNOWN" } else { token };
token_nonempty.parse().unwrap_or(TransactionType::Buy)
}
#[must_use]
pub fn string_to_period(s: &str) -> Period {
if s.trim().is_empty() {
return "UNKNOWN".parse().map_or(Period::Year { year: 1970 }, |p| p);
}
s.parse()
.unwrap_or_else(|_| "UNKNOWN".parse().map_or(Period::Year { year: 1970 }, |p| p))
}
#[must_use]
pub fn string_to_recommendation_grade(s: &str) -> RecommendationGrade {
let token = s.trim();
let token_nonempty = if token.is_empty() { "UNKNOWN" } else { token };
token_nonempty.parse().unwrap_or(RecommendationGrade::Hold)
}
#[must_use]
pub fn string_to_recommendation_action(s: &str) -> RecommendationAction {
let token = s.trim();
let token_nonempty = if token.is_empty() { "UNKNOWN" } else { token };
token_nonempty
.parse()
.unwrap_or(RecommendationAction::Maintain)
}