pub mod analytics;
pub mod charts;
pub mod data;
pub mod error;
pub mod models;
pub mod reports;
pub mod utils;
pub mod prelude {
pub use crate::error::FinalyticsError;
pub use crate::error::{
column_to_vec_f64, require_min_length, series_to_optional_vec_f64, series_to_vec_f64,
};
pub use crate::models::kline::KLINE;
pub use crate::models::portfolio::Portfolio;
pub use crate::models::screener::Screener;
pub use crate::models::ticker::Ticker;
pub use crate::models::tickers::Tickers;
pub use crate::analytics::performance::TickerPerformance;
pub use crate::analytics::stochastics::VolatilitySurface;
pub use crate::analytics::technicals::TechnicalIndicators;
pub use crate::charts::ticker::TickerCharts;
pub use crate::charts::tickers::TickersCharts;
pub use crate::data::ticker::TickerData;
pub use crate::data::tickers::TickersData;
pub use crate::charts::portfolio::PortfolioCharts;
pub use crate::analytics::optimization::CategoricalWeights;
pub use crate::analytics::optimization::Constraints;
pub use crate::analytics::optimization::ObjectiveFunction;
pub use crate::models::portfolio::CashFlowAllocation;
pub use crate::models::portfolio::RebalanceStrategy;
pub use crate::models::portfolio::ScheduleFrequency;
pub use crate::models::portfolio::ScheduledCashFlow;
pub use crate::models::portfolio::Transaction;
pub use crate::analytics::performance::PortfolioData;
pub use crate::analytics::performance::PortfolioOptimizationResult;
pub use crate::analytics::performance::PortfolioPerformanceStats;
pub use crate::analytics::statistics::DatedCashFlow;
pub use crate::analytics::statistics::RebalanceConfig;
pub use crate::analytics::statistics::RebalanceEvent;
pub use crate::analytics::statistics::TransactionEvent;
pub use crate::analytics::statistics::TransactionEventType;
pub use crate::analytics::statistics::PerformancePeriod;
pub use crate::analytics::statistics::ReturnsFrequency;
pub use crate::analytics::statistics::ShrinkageMethod;
pub use crate::analytics::statistics::ShrunkCovariance;
pub use crate::utils::date_utils::IntervalDays;
pub use crate::analytics::technicals::Column;
pub use crate::data::yahoo::config::Interval;
pub use crate::data::yahoo::config::StatementFrequency;
pub use crate::data::yahoo::config::StatementType;
pub use crate::reports::report::Report;
pub use crate::reports::report::ReportType;
pub use crate::reports::table::DataTable;
pub use crate::reports::table::DataTableDisplay;
pub use crate::reports::table::DataTableFormat;
pub use crate::data::yahoo::screeners::{
CryptoScreener, EquityScreener, EtfScreener, Exchange, FundCategory, FundFamily,
FutureScreener, IndexScreener, Industry, MutualFundScreener, PeerGroup, QuoteType, Region,
ScreenerBuilder, ScreenerFilter, ScreenerMetric, Sector,
};
pub use strum::{EnumProperty, IntoEnumIterator, VariantArray, VariantIterator, VariantNames};
#[cfg(feature = "kaleido")]
pub use crate::utils::chart_utils::PlotImage;
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use std::error::Error;
#[tokio::test]
async fn test_screener() -> Result<(), Box<dyn Error>> {
let screener = Screener::builder()
.quote_type(QuoteType::Equity)
.add_filter(ScreenerFilter::EqStr(
ScreenerMetric::Equity(EquityScreener::Exchange),
Exchange::NASDAQ.as_ref(),
))
.add_filter(ScreenerFilter::EqStr(
ScreenerMetric::Equity(EquityScreener::Sector),
Sector::Technology.as_ref(),
))
.add_filter(ScreenerFilter::Gte(
ScreenerMetric::Equity(EquityScreener::MarketCapIntraday),
10_000_000_000.0,
))
.add_filter(ScreenerFilter::Gte(
ScreenerMetric::Equity(EquityScreener::ReturnOnEquity),
0.15,
))
.sort_by(
ScreenerMetric::Equity(EquityScreener::MarketCapIntraday),
true,
)
.size(10)
.build()
.await?;
screener.overview().show()?;
screener.metrics().await?.show()?;
Ok(())
}
#[tokio::test]
async fn test_ticker() -> Result<(), Box<dyn Error>> {
let ticker = Ticker::builder()
.ticker("AAPL")
.start_date("2023-01-01")
.end_date("2024-12-31")
.interval(Interval::OneDay)
.benchmark_symbol("^GSPC")
.confidence_level(0.95)
.risk_free_rate(0.02)
.build();
ticker.report(Some(ReportType::Performance)).await?.show()?;
ticker.report(Some(ReportType::Financials)).await?.show()?;
ticker.report(Some(ReportType::Options)).await?.show()?;
ticker.report(Some(ReportType::News)).await?.show()?;
Ok(())
}
#[tokio::test]
async fn test_tickers() -> Result<(), Box<dyn Error>> {
let tickers = Tickers::builder()
.tickers(vec!["NVDA", "GOOG", "AAPL", "MSFT", "BTC-USD"])
.start_date("2023-01-01")
.end_date("2024-12-31")
.interval(Interval::OneDay)
.benchmark_symbol("^GSPC")
.confidence_level(0.95)
.risk_free_rate(0.02)
.build();
tickers
.report(Some(ReportType::Performance))
.await?
.show()?;
Ok(())
}
#[tokio::test]
async fn test_portfolio_optimization_oos() -> Result<(), Box<dyn Error>> {
let mut portfolio = Portfolio::builder()
.ticker_symbols(vec!["NVDA", "GOOG", "AAPL", "MSFT", "BTC-USD"])
.benchmark_symbol("^GSPC")
.start_date("2023-01-01")
.end_date("2024-12-31")
.interval(Interval::OneDay)
.confidence_level(0.95)
.risk_free_rate(0.02)
.objective_function(ObjectiveFunction::MaxSharpe)
.build()
.await?;
portfolio.optimize()?;
portfolio
.report(Some(ReportType::Optimization))
.await?
.show()?;
portfolio.update_dates("2025-01-01", "2026-01-01").await?;
portfolio.performance_stats()?;
portfolio
.report(Some(ReportType::Performance))
.await?
.show()?;
Ok(())
}
#[tokio::test]
async fn test_portfolio_optimization_constraints() -> Result<(), Box<dyn Error>> {
let constraints = Constraints {
asset_weights: Some(vec![
(0.05, 0.40), (0.05, 0.40), (0.05, 0.40), (0.05, 0.30), (0.05, 0.20), (0.05, 0.25), ]),
categorical_weights: Some(vec![
CategoricalWeights {
name: "Sector".to_string(),
category_per_symbol: vec![
"Tech".to_string(), "Tech".to_string(), "Tech".to_string(), "Finance".to_string(), "Energy".to_string(), "Crypto".to_string(), ],
weight_per_category: vec![
("Tech".to_string(), 0.30, 0.60),
("Finance".to_string(), 0.05, 0.30),
("Energy".to_string(), 0.05, 0.20),
("Crypto".to_string(), 0.05, 0.25),
],
},
CategoricalWeights {
name: "Asset Class".to_string(),
category_per_symbol: vec![
"Equity".to_string(), "Equity".to_string(), "Equity".to_string(), "Equity".to_string(), "Equity".to_string(), "Crypto".to_string(), ],
weight_per_category: vec![
("Equity".to_string(), 0.70, 0.95),
("Crypto".to_string(), 0.05, 0.30),
],
},
]),
};
let mut portfolio = Portfolio::builder()
.ticker_symbols(vec!["AAPL", "MSFT", "NVDA", "JPM", "XOM", "BTC-USD"])
.benchmark_symbol("^GSPC")
.start_date("2023-01-01")
.end_date("2024-12-31")
.interval(Interval::OneDay)
.confidence_level(0.95)
.risk_free_rate(0.02)
.objective_function(ObjectiveFunction::MaxSharpe)
.constraints(Some(constraints))
.build()
.await?;
portfolio.optimize()?;
portfolio
.report(Some(ReportType::Optimization))
.await?
.show()?;
Ok(())
}
#[tokio::test]
async fn test_portfolio_allocation_rebalancing_dca() -> Result<(), Box<dyn Error>> {
let mut portfolio_alloc = Portfolio::builder()
.ticker_symbols(vec!["AAPL", "MSFT", "NVDA", "BTC-USD"])
.benchmark_symbol("^GSPC")
.start_date("2023-01-01")
.end_date("2024-12-31")
.interval(Interval::OneDay)
.confidence_level(0.95)
.risk_free_rate(0.02)
.weights(vec![25_000.0, 25_000.0, 25_000.0, 25_000.0])
.rebalance_strategy(Some(RebalanceStrategy::Calendar(
ScheduleFrequency::Quarterly,
)))
.scheduled_cash_flows(Some(vec![ScheduledCashFlow {
amount: 2_000.0,
frequency: ScheduleFrequency::Monthly,
start_date: None,
end_date: None,
allocation: CashFlowAllocation::ProRata,
}]))
.build()
.await?;
portfolio_alloc.performance_stats()?;
portfolio_alloc
.report(Some(ReportType::Performance))
.await?
.show()?;
Ok(())
}
#[tokio::test]
async fn test_custom_data() -> Result<(), Box<dyn Error>> {
let aapl = KLINE::from_csv("AAPL", "../examples/datasets/aapl.csv")?;
let msft = KLINE::from_csv("MSFT", "../examples/datasets/msft.csv")?;
let nvda = KLINE::from_csv("NVDA", "../examples/datasets/nvda.csv")?;
let goog = KLINE::from_csv("GOOG", "../examples/datasets/goog.csv")?;
let btcusd = KLINE::from_csv("BTC-USD", "../examples/datasets/btcusd.csv")?;
let gspc = KLINE::from_csv("^GSPC", "../examples/datasets/gspc.csv")?;
let custom_ticker = Ticker::builder()
.ticker("AAPL")
.benchmark_symbol("^GSPC")
.confidence_level(0.95)
.risk_free_rate(0.02)
.ticker_data(Some(aapl.clone()))
.benchmark_data(Some(gspc.clone()))
.build();
custom_ticker
.report(Some(ReportType::Performance))
.await?
.show()?;
let custom_tickers = Tickers::builder()
.tickers(vec!["NVDA", "GOOG", "AAPL", "MSFT", "BTC-USD"])
.benchmark_symbol("^GSPC")
.confidence_level(0.95)
.risk_free_rate(0.02)
.tickers_data(Some(vec![
nvda.clone(),
goog.clone(),
aapl.clone(),
msft.clone(),
btcusd.clone(),
]))
.benchmark_data(Some(gspc.clone()))
.build();
custom_tickers
.report(Some(ReportType::Performance))
.await?
.show()?;
let mut custom_portfolio = Portfolio::builder()
.ticker_symbols(vec!["NVDA", "GOOG", "AAPL", "MSFT", "BTC-USD"])
.benchmark_symbol("^GSPC")
.confidence_level(0.95)
.risk_free_rate(0.02)
.objective_function(ObjectiveFunction::MaxSharpe)
.tickers_data(Some(vec![nvda, goog, aapl, msft, btcusd]))
.benchmark_data(Some(gspc))
.build()
.await?;
custom_portfolio.optimize()?;
custom_portfolio
.report(Some(ReportType::Optimization))
.await?
.show()?;
Ok(())
}
}