finalytics 0.4.0__tar.gz → 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {finalytics-0.4.0 → finalytics-0.4.2}/PKG-INFO +1 -1
  2. {finalytics-0.4.0 → finalytics-0.4.2}/README.md +5 -5
  3. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/Cargo.toml +5 -4
  4. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/analytics/fundamentals.rs +42 -41
  5. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/analytics/performance.rs +82 -30
  6. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/analytics/statistics.rs +3 -3
  7. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/analytics/stochastics.rs +1 -1
  8. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/charts/portfolio.rs +31 -16
  9. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/charts/ticker.rs +13 -15
  10. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/data/ticker.rs +99 -63
  11. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/lib.rs +25 -4
  12. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/models/portfolio.rs +3 -3
  13. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/models/ticker.rs +1 -1
  14. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/utils/date_utils.rs +0 -17
  15. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/utils/web_utils.rs +1 -2
  16. {finalytics-0.4.0 → finalytics-0.4.2}/python/Cargo.toml +3 -3
  17. {finalytics-0.4.0 → finalytics-0.4.2}/python/conf.py +9 -18
  18. finalytics-0.4.2/python/index.rst +198 -0
  19. {finalytics-0.4.0 → finalytics-0.4.2}/python/src/ffi.rs +1 -1
  20. finalytics-0.4.0/python/index.rst +0 -6
  21. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/analytics/mod.rs +0 -0
  22. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/analytics/optimization.rs +0 -0
  23. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/analytics/sentiment.rs +0 -0
  24. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/analytics/technicals.rs +0 -0
  25. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/charts/mod.rs +0 -0
  26. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/data/config.rs +0 -0
  27. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/data/mod.rs +0 -0
  28. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/models/mod.rs +0 -0
  29. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/utils/chart_utils.rs +0 -0
  30. {finalytics-0.4.0 → finalytics-0.4.2}/finalytics/src/utils/mod.rs +0 -0
  31. {finalytics-0.4.0 → finalytics-0.4.2}/pyproject.toml +0 -0
  32. {finalytics-0.4.0 → finalytics-0.4.2}/python/Cargo.lock +0 -0
  33. {finalytics-0.4.0 → finalytics-0.4.2}/python/README.md +0 -0
  34. {finalytics-0.4.0 → finalytics-0.4.2}/python/build.sh +0 -0
  35. {finalytics-0.4.0 → finalytics-0.4.2}/python/src/lib.rs +0 -0
  36. {finalytics-0.4.0 → finalytics-0.4.2}/python/src/portfolio.rs +0 -0
  37. {finalytics-0.4.0 → finalytics-0.4.2}/python/src/ticker.rs +0 -0
  38. {finalytics-0.4.0 → finalytics-0.4.2}/python/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: finalytics
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -14,7 +14,7 @@ Add the following to your `Cargo.toml` file:
14
14
 
15
15
  ```toml
16
16
  [dependencies]
17
- finalytics = {version = "0.4.0", features = ["kaleido"]}
17
+ finalytics = {version = "0.5.2", features = ["kaleido"]}
18
18
  ```
19
19
 
20
20
  Or run the following command:
@@ -41,7 +41,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
41
41
  let ticker = TickerBuilder::new()
42
42
  .ticker("AAPL")
43
43
  .start_date("2023-01-01")
44
- .end_date("2023-02-01")
44
+ .end_date("2023-12-31")
45
45
  .interval(Interval::OneDay)
46
46
  .benchmark_symbol("^GSPC")
47
47
  .confidence_level(0.95)
@@ -73,7 +73,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
73
73
  let ticker_symbols = Vec::from(["NVDA", "BRK-A", "AAPL", "ZN=F"]);
74
74
  let portfolio = PortfolioBuilder::new().ticker_symbols(ticker_symbols)
75
75
  .benchmark_symbol("^GSPC")
76
- .start_date("2017-01-01")
76
+ .start_date("2020-01-01")
77
77
  .end_date("2023-01-01")
78
78
  .interval(Interval::OneDay)
79
79
  .confidence_level(0.95)
@@ -133,7 +133,7 @@ from finalytics import Ticker
133
133
 
134
134
  ticker = Ticker(symbol="AAPL",
135
135
  start="2023-01-01",
136
- end="2023-10-31",
136
+ end="2023-12-31",
137
137
  interval="1d",
138
138
  confidence_level=0.95,
139
139
  risk_free_rate=0.02)
@@ -148,7 +148,7 @@ ticker.performance_chart().show()
148
148
  from finalytics import Portfolio
149
149
 
150
150
  portfolio = Portfolio(ticker_symbols=["AAPL", "GOOG", "MSFT", "BTC-USD"],
151
- benchmark_symbol="^GSPC", start_date="2020-01-01", end_date="2022-01-01", interval="1d",
151
+ benchmark_symbol="^GSPC", start_date="2020-01-01", end_date="2023-01-01", interval="1d",
152
152
  confidence_level=0.95, risk_free_rate=0.02, max_iterations=1000,
153
153
  objective_function="max_sharpe")
154
154
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "finalytics"
3
- version = "0.5.0"
3
+ version = "0.5.2"
4
4
  authors = ["Nnamdi Olisaeloka <nnamdiolisaeloka@gmail.com>"]
5
5
  edition = "2021"
6
6
  homepage = "https://finalytics.rs/"
@@ -24,18 +24,19 @@ chrono = "0.4.37"
24
24
  select = "0.6.0"
25
25
  scraper = "0.19.0"
26
26
  sentiment = "0.1.1"
27
- polars = { version = "0.33.2", default-features = false, features = ["lazy", "dtype-datetime", "rows"] }
27
+ polars = { version = "0.41.3", default-features = false, features = ["lazy", "dtype-datetime", "rows", "fmt_no_tty"] }
28
28
  ta = "0.5.0"
29
29
  num-format = "0.4.4"
30
30
  smartcore = "0.3.2"
31
- statrs = "0.16.0"
31
+ statrs = "0.17.1"
32
32
  rand = "0.8.5"
33
33
  ndarray = "0.15.6"
34
34
  rand_distr = "0.4.3"
35
35
  optimization = "0.2.0"
36
36
  html-escape = "0.2.13"
37
- plotly = { version = "0.8.4", package = "plotly-fork"}
37
+ plotly = { version = "0.9.0", features = ["kaleido"] }
38
38
  once_cell = "1.19.0"
39
+ futures = "0.3.30"
39
40
 
40
41
  [features]
41
42
  kaleido = ["plotly/kaleido"]
@@ -21,40 +21,41 @@ impl Financials for Ticker {
21
21
  let income_statement = self.get_fundamentals("income-statement", "quarterly").await?;
22
22
  let balance_sheet = self.get_fundamentals("balance-sheet", "quarterly").await?;
23
23
  let cash_flow = self.get_fundamentals("cash-flow", "quarterly").await?;
24
+ let ratios = vec![
25
+ Series::new("date", income_statement.column("asOfDate")?),
26
+ Series::new("Gross Profit Margin", (income_statement.column("GrossProfit")? / income_statement.column("TotalRevenue")?)?),
27
+ Series::new("Operating Profit Margin", (income_statement.column("EBIT")? / income_statement.column("TotalRevenue")?)?),
28
+ Series::new("Net Profit Margin", (income_statement.column("NetIncome")? / income_statement.column("TotalRevenue")?)?),
29
+ Series::new("Return on Assets", (income_statement.column("NetIncome")? / balance_sheet.column("TotalAssets")?)?),
30
+ Series::new("Return on Equity", (income_statement.column("NetIncome")? / balance_sheet.column("TotalEquityGrossMinorityInterest")?)?),
31
+ Series::new("Quick Ratio", (balance_sheet.column("CurrentAssets")? / balance_sheet.column("CurrentLiabilities")?)?),
32
+ Series::new("Current Ratio", (balance_sheet.column("CurrentAssets")? / balance_sheet.column("CurrentLiabilities")?)?),
33
+ Series::new("Debt to Equity", (balance_sheet.column("TotalLiabilitiesNetMinorityInterest")? / balance_sheet.column("TotalEquityGrossMinorityInterest")?)?),
34
+ Series::new("Debt to Assets", (balance_sheet.column("TotalLiabilitiesNetMinorityInterest")? / balance_sheet.column("TotalAssets")?)?),
35
+ Series::new("Interest Coverage", (income_statement.column("EBIT")? / income_statement.column("InterestExpense")?)?),
36
+ Series::new("Asset Turnover", (income_statement.column("TotalRevenue")? / balance_sheet.column("TotalAssets")?)?),
37
+ Series::new("Inventory Turnover", (income_statement.column("CostOfRevenue")? / balance_sheet.column("Inventory")?)?),
38
+ Series::new("Days Receivable", (balance_sheet.column("AccountsReceivable")? / income_statement.column("TotalRevenue")?)? * 365.0),
39
+ Series::new("Days Inventory", (balance_sheet.column("Inventory")? / income_statement.column("CostOfRevenue")?)? * 365.0),
40
+ Series::new("Days Payable", (balance_sheet.column("AccountsPayable")? / income_statement.column("CostOfRevenue")?)? * 365.0),
41
+ Series::new("Earnings per Share", income_statement.column("DilutedEPS")?),
42
+ Series::new("Price to Earnings", (balance_sheet.column("TotalCapitalization")? / income_statement.column("NetIncome")?)?),
43
+ Series::new("Price to Book", (balance_sheet.column("TotalCapitalization")? / balance_sheet.column("TotalEquityGrossMinorityInterest")?)?),
44
+ Series::new("Price to Sales", (balance_sheet.column("TotalCapitalization")? / income_statement.column("TotalRevenue")?)?),
45
+ Series::new("Price to Cashflow", (balance_sheet.column("TotalCapitalization")? / cash_flow.column("OperatingCashFlow")?)?),
46
+ Series::new("Price to Free Cashflow", (balance_sheet.column("TotalCapitalization")? / cash_flow.column("FreeCashFlow")?)?),
47
+ ];
24
48
 
25
- let df = df!(
26
- "date" => *&income_statement.column("asOfDate")?,
27
- "Gross Profit Margin" => *&income_statement.column("GrossProfit")? / *&income_statement.column("TotalRevenue")?,
28
- "Operating Profit Margin" => *&income_statement.column("EBIT")? / *&income_statement.column("TotalRevenue")?,
29
- "Net Profit Margin" => *&income_statement.column("NetIncome")? / *&income_statement.column("TotalRevenue")?,
30
- "Return on Assets" => *&income_statement.column("NetIncome")? / *&balance_sheet.column("TotalAssets")?,
31
- "Return on Equity" => *&income_statement.column("NetIncome")? / *&balance_sheet.column("TotalEquityGrossMinorityInterest")?,
32
- "Quick Ratio" => *&balance_sheet.column("CurrentAssets")? / *&balance_sheet.column("CurrentLiabilities")?,
33
- "Current Ratio" => *&balance_sheet.column("CurrentAssets")? / *&balance_sheet.column("CurrentLiabilities")?,
34
- "Debt to Equity" => *&balance_sheet.column("TotalLiabilitiesNetMinorityInterest")? / *&balance_sheet.column("TotalEquityGrossMinorityInterest")?,
35
- "Debt to Assets" => *&balance_sheet.column("TotalLiabilitiesNetMinorityInterest")? / *&balance_sheet.column("TotalAssets")?,
36
- "Interest Coverage" => *&income_statement.column("EBIT")? / *&income_statement.column("InterestExpense")?,
37
- "Asset Turnover" => *&income_statement.column("TotalRevenue")? / *&balance_sheet.column("TotalAssets")?,
38
- "Inventory Turnover" => *&income_statement.column("CostOfRevenue")? / *&balance_sheet.column("Inventory")?,
39
- "Days Receivable" => *&balance_sheet.column("AccountsReceivable")? / *&income_statement.column("TotalRevenue")? * 365.0,
40
- "Days Inventory" => *&balance_sheet.column("Inventory")? / *&income_statement.column("CostOfRevenue")? * 365.0,
41
- "Days Payable" => *&balance_sheet.column("AccountsPayable")? / *&income_statement.column("CostOfRevenue")? * 365.0,
42
- "Earnings per Share" => *&income_statement.column("DilutedEPS")?,
43
- "Price to Earnings" => *&balance_sheet.column("TotalCapitalization")? / *&income_statement.column("NetIncome")?,
44
- "Price to Book" => *&balance_sheet.column("TotalCapitalization")? / *&balance_sheet.column("TotalEquityGrossMinorityInterest")?,
45
- "Price to Sales" => *&balance_sheet.column("TotalCapitalization")? / *&income_statement.column("TotalRevenue")?,
46
- "Price to Cashflow" => *&balance_sheet.column("TotalCapitalization")? / *&cash_flow.column("OperatingCashFlow")?,
47
- "Price to Free Cashflow" => *&balance_sheet.column("TotalCapitalization")? / *&cash_flow.column("FreeCashFlow")?,
48
- )?;
49
+ let df = DataFrame::new(ratios)?;
49
50
 
50
51
  // Transpose the DataFrame
51
- let dates = df.column("date")?.utf8()?.into_iter()
52
- .map(|x| x.unwrap()).collect::<Vec<&str>>();
53
- let df = df.drop("date").unwrap();
52
+ let dates = df.column("date")?.str()?.into_no_null_iter()
53
+ .collect::<Vec<&str>>();
54
+ let mut df = df.drop("date").unwrap();
54
55
  let items = Series::new("Items", df.get_column_names());
55
56
  let mut transposed_df = df.transpose(None, None)?;
56
57
  let _ = transposed_df.set_column_names(&dates)?;
57
- let _ = transposed_df.insert_at_idx(0, items)?;
58
+ let _ = transposed_df.insert_column(0, items)?;
58
59
  Ok(transposed_df)
59
60
  }
60
61
 
@@ -102,15 +103,15 @@ impl Financials for Ticker {
102
103
  .collect();
103
104
 
104
105
  // Select and alias columns using the expressions
105
- let renamed_df = df.lazy().select(expressions).collect()?;
106
+ let mut renamed_df = df.lazy().select(expressions).collect()?;
106
107
 
107
108
  // Transpose the DataFrame
108
- let dates = income_statement.column("asOfDate")?.utf8()?.into_iter()
109
- .map(|x| x.unwrap()).collect::<Vec<&str>>();
109
+ let dates = income_statement.column("asOfDate")?.str()?.into_no_null_iter()
110
+ .collect::<Vec<&str>>();
110
111
  let items = Series::new("Items", renamed_df.get_column_names());
111
112
  let mut transposed_df = renamed_df.transpose(None, None)?;
112
113
  let _ = transposed_df.set_column_names(&dates)?;
113
- let _ = transposed_df.insert_at_idx(0, items)?;
114
+ let _ = transposed_df.insert_column(0, items)?;
114
115
  Ok(transposed_df)
115
116
  }
116
117
 
@@ -161,7 +162,7 @@ impl Financials for Ticker {
161
162
  "CommonStock", "RetainedEarnings", "CommonStockEquity", "TotalEquityGrossMinorityInterest",
162
163
  ];
163
164
 
164
- // remove item from cols if it doesnt exist in the balance sheet dataframe
165
+ // remove item from cols if it doesn't exist in the balance sheet dataframe
165
166
  cols.retain(|x| balance_sheet.column(*x).is_ok());
166
167
 
167
168
 
@@ -177,15 +178,15 @@ impl Financials for Ticker {
177
178
  .collect();
178
179
 
179
180
  // Select and alias columns using the expressions
180
- let renamed_df = df.lazy().select(expressions).collect()?;
181
+ let mut renamed_df = df.lazy().select(expressions).collect()?;
181
182
 
182
183
  // Transpose the DataFrame
183
- let dates = balance_sheet.column("asOfDate")?.utf8()?.into_iter()
184
- .map(|x| x.unwrap()).collect::<Vec<&str>>();
184
+ let dates = balance_sheet.column("asOfDate")?.str()?.into_no_null_iter()
185
+ .collect::<Vec<&str>>();
185
186
  let items = Series::new("Items", renamed_df.get_column_names());
186
187
  let mut transposed_df = renamed_df.transpose(None, None)?;
187
188
  let _ = transposed_df.set_column_names(&dates)?;
188
- let _ = transposed_df.insert_at_idx(0, items)?;
189
+ let _ = transposed_df.insert_column(0, items)?;
189
190
 
190
191
  Ok(transposed_df)
191
192
 
@@ -267,15 +268,15 @@ impl Financials for Ticker {
267
268
  .collect();
268
269
 
269
270
  // Select and alias columns using the expressions
270
- let renamed_df = df.lazy().select(expressions).collect()?;
271
+ let mut renamed_df = df.lazy().select(expressions).collect()?;
271
272
 
272
273
  // Transpose the DataFrame
273
- let dates = cash_flow.column("asOfDate")?.utf8()?.into_iter()
274
- .map(|x| x.unwrap()).collect::<Vec<&str>>();
274
+ let dates = cash_flow.column("asOfDate")?.str()?.into_no_null_iter()
275
+ .collect::<Vec<&str>>();
275
276
  let items = Series::new("Items", renamed_df.get_column_names());
276
277
  let mut transposed_df = renamed_df.transpose(None, None)?;
277
278
  let _ = transposed_df.set_column_names(&dates)?;
278
- let _ = transposed_df.insert_at_idx(0, items)?;
279
+ let _ = transposed_df.insert_column(0, items)?;
279
280
 
280
281
  Ok(transposed_df)
281
282
  }
@@ -1,5 +1,7 @@
1
1
  use polars::prelude::*;
2
2
  use std::error::Error;
3
+ use chrono::{DateTime, NaiveDateTime};
4
+ use futures::future::join_all;
3
5
 
4
6
  use crate::data::config::Interval;
5
7
  use crate::models::ticker::{Ticker, TickerBuilder};
@@ -14,6 +16,7 @@ pub struct TickerPerformanceStats {
14
16
  pub benchmark_symbol: String,
15
17
  pub start_date: String,
16
18
  pub end_date: String,
19
+ pub dates_array: Vec<String>,
17
20
  pub interval: Interval,
18
21
  pub confidence_level: f64,
19
22
  pub risk_free_rate: f64,
@@ -48,17 +51,22 @@ impl TickerPerformance for Ticker {
48
51
  .risk_free_rate(self.risk_free_rate)
49
52
  .build();
50
53
  let benchmark_returns = benchmark_ticker.roc(1).await?;
51
- let benchmark_returns = benchmark_returns.join(
52
- &security_returns,
54
+ let benchmark_returns = security_returns.join(
55
+ &benchmark_returns,
53
56
  &["timestamp"],
54
57
  &["timestamp"],
55
- JoinArgs::new(JoinType::Inner),
58
+ JoinArgs::new(JoinType::Left),
56
59
  )?;
57
- let benchmark_returns = benchmark_returns.sort(&["timestamp"], false, false)?;
60
+ let benchmark_returns = benchmark_returns.sort(&["timestamp"], SortMultipleOptions::new().with_order_descending(false))?;
58
61
  let benchmark_returns = benchmark_returns.fill_null(FillNullStrategy::Forward(None))?;
59
62
  let benchmark_returns = benchmark_returns.fill_null(FillNullStrategy::Backward(None))?;
63
+ let dates_array = benchmark_returns.column("timestamp")?.datetime()?
64
+ .into_no_null_iter().map(|x| DateTime::from_timestamp_millis(x).unwrap()
65
+ .naive_local()).collect::<Vec<NaiveDateTime>>();
66
+ let dates_array = dates_array.iter().map(|x| x.to_string()).collect::<Vec<String>>();
67
+ let security_returns = benchmark_returns.column(&*self.ticker)?.clone();
60
68
  let benchmark_returns = benchmark_returns.column("roc-1")?.clone();
61
- let security_returns = security_returns.column(&*self.ticker)?.clone();
69
+
62
70
  let performance_stats = PerformanceStats::compute_stats(
63
71
  security_returns.clone(), benchmark_returns.clone(),
64
72
  self.risk_free_rate, self.confidence_level, self.interval)?;
@@ -67,6 +75,7 @@ impl TickerPerformance for Ticker {
67
75
  benchmark_symbol: self.benchmark_symbol.clone(),
68
76
  start_date: self.start_date.clone(),
69
77
  end_date: self.end_date.clone(),
78
+ dates_array,
70
79
  interval: self.interval.clone(),
71
80
  confidence_level: self.confidence_level,
72
81
  risk_free_rate: self.risk_free_rate,
@@ -88,6 +97,7 @@ pub struct PortfolioPerformanceStats {
88
97
  pub start_date: String,
89
98
  pub end_date: String,
90
99
  pub interval: Interval,
100
+ pub dates_array: Vec<String>,
91
101
  pub confidence_level: f64,
92
102
  pub risk_free_rate: f64,
93
103
  pub portfolio_returns: DataFrame,
@@ -131,21 +141,55 @@ impl PortfolioPerformanceStats {
131
141
  max_iterations: u64,
132
142
  objective_function: ObjectiveFunction
133
143
  ) -> Result<PortfolioPerformanceStats, Box<dyn Error>> {
134
- let mut dfs: Vec<DataFrame> = Vec::new();
135
- for ticker_symbol in ticker_symbols.iter() {
136
- let ticker = TickerBuilder::new().ticker(ticker_symbol)
144
+ let mut futures = Vec::new();
145
+
146
+ for ticker_symbol in ticker_symbols.clone().into_iter() {
147
+ let ticker = TickerBuilder::new().ticker(&ticker_symbol)
137
148
  .start_date(start_date)
138
149
  .end_date(end_date)
139
150
  .interval(interval)
140
151
  .confidence_level(confidence_level)
141
152
  .risk_free_rate(risk_free_rate)
142
153
  .build();
143
- let security_df = ticker.roc(1).await?;
144
- let security_returns_df = DataFrame::new(vec![
145
- security_df.column("timestamp")?.clone(),
146
- security_df.column("roc-1")?.clone().with_name(ticker_symbol)
147
- ])?;
148
- dfs.push(security_returns_df);
154
+
155
+ let fut = tokio::task::spawn(async move {
156
+ match ticker.roc(1).await {
157
+ Ok(security_df) => {
158
+ match DataFrame::new(vec![
159
+ security_df.column("timestamp").map_err(|e| e.to_string())?.clone(),
160
+ security_df.column("roc-1").map_err(|e| e.to_string())?.clone().with_name(&ticker_symbol)
161
+ ]) {
162
+ Ok(security_returns_df) => Ok(security_returns_df),
163
+ Err(e) => {
164
+ eprintln!("Error creating DataFrame for {}: {}", ticker_symbol, e);
165
+ Err(format!("Error creating DataFrame for {}: {}", ticker_symbol, e))
166
+ }
167
+ }
168
+ }
169
+ Err(e) => {
170
+ eprintln!("Error Fetching Price Data for {}: {}", ticker_symbol, e);
171
+ Err(format!("Error Fetching Price Data for {}: {}", ticker_symbol, e))
172
+ }
173
+ }
174
+ });
175
+
176
+ futures.push(fut);
177
+ }
178
+
179
+ let results = join_all(futures).await;
180
+ let mut fetched_symbols: Vec<String> = Vec::new();
181
+ let mut dfs: Vec<DataFrame> = Vec::new();
182
+
183
+ for result in results {
184
+ match result {
185
+ Ok(Ok(df)) => {
186
+ let symbol = df.get_column_names()[1].to_string();
187
+ fetched_symbols.push(symbol);
188
+ dfs.push(df);
189
+ }
190
+ Ok(Err(_)) => continue,
191
+ Err(e) => eprintln!("Error in task: {}", e),
192
+ }
149
193
  }
150
194
 
151
195
  let mut portfolio_returns = dfs[0].clone();
@@ -156,10 +200,11 @@ impl PortfolioPerformanceStats {
156
200
  df,
157
201
  &["timestamp"],
158
202
  &["timestamp"],
159
- JoinArgs::new(JoinType::Outer),
203
+ JoinArgs::new(JoinType::Full).with_coalesce(JoinCoalesce::CoalesceColumns),
160
204
  )?;
161
205
  }
162
- portfolio_returns = portfolio_returns.sort(&["timestamp"], false, false)?;
206
+
207
+ portfolio_returns = portfolio_returns.sort(&["timestamp"], SortMultipleOptions::new().with_order_descending(false))?;
163
208
  portfolio_returns = portfolio_returns.fill_null(FillNullStrategy::Forward(None))?;
164
209
  portfolio_returns = portfolio_returns.fill_null(FillNullStrategy::Backward(None))?;
165
210
 
@@ -171,33 +216,31 @@ impl PortfolioPerformanceStats {
171
216
  .risk_free_rate(risk_free_rate)
172
217
  .build();
173
218
  let benchmark_returns = benchmark_ticker.roc(1).await?;
174
- let benchmark_returns = if portfolio_returns.height() > benchmark_returns.height() {
175
- benchmark_returns.join(
176
- &portfolio_returns,
177
- &["timestamp"],
178
- &["timestamp"],
179
- JoinArgs::new(JoinType::Outer),
180
- )?} else {
181
- portfolio_returns.join(
219
+ let benchmark_returns = portfolio_returns.join(
182
220
  &benchmark_returns,
183
221
  &["timestamp"],
184
222
  &["timestamp"],
185
- JoinArgs::new(JoinType::Inner),
186
- )?};
223
+ JoinArgs::new(JoinType::Left),
224
+ )?;
187
225
 
188
- let benchmark_returns = benchmark_returns.sort(&["timestamp"], false, false)?;
226
+ let benchmark_returns = benchmark_returns.sort(&["timestamp"], SortMultipleOptions::new().with_order_descending(false))?;
189
227
  let benchmark_returns = benchmark_returns.fill_null(FillNullStrategy::Forward(None))?;
190
228
  let benchmark_returns = benchmark_returns.fill_null(FillNullStrategy::Backward(None))?;
229
+ let dates_array = benchmark_returns.column("timestamp")?.datetime()?
230
+ .into_no_null_iter().map(|x| DateTime::from_timestamp_millis(x).unwrap()
231
+ .naive_local()).collect::<Vec<NaiveDateTime>>();
232
+ let dates_array = dates_array.iter().map(|x| x.to_string()).collect::<Vec<String>>();
191
233
  let benchmark_returns = benchmark_returns.column("roc-1")?.clone();
192
234
 
193
235
  let _ = portfolio_returns.drop_in_place("timestamp")?;
194
236
 
195
237
  Ok(PortfolioPerformanceStats {
196
- ticker_symbols: ticker_symbols.clone(),
238
+ ticker_symbols: fetched_symbols.clone(),
197
239
  benchmark_symbol: benchmark_symbol.to_string(),
198
240
  start_date: start_date.to_string(),
199
241
  end_date: end_date.to_string(),
200
242
  interval,
243
+ dates_array,
201
244
  confidence_level,
202
245
  risk_free_rate,
203
246
  portfolio_returns: portfolio_returns.clone(),
@@ -218,8 +261,16 @@ impl PortfolioPerformanceStats {
218
261
  ///
219
262
  /// * `PortfolioPerformanceStats` struct
220
263
  pub fn compute_stats(&self) -> Result<PortfolioPerformanceStats, Box<dyn Error>> {
221
- let mean_returns = self.portfolio_returns.mean().iter().map(|x| x.f64().unwrap()
222
- .get(0).unwrap()).collect::<Vec<f64>>();
264
+ let mean_returns = self.portfolio_returns
265
+ .get_columns()
266
+ .iter()
267
+ .map(|col| {
268
+ col.f64()
269
+ .unwrap()
270
+ .mean()
271
+ .unwrap()
272
+ })
273
+ .collect::<Vec<f64>>();
223
274
  let cov_matrix = covariance_matrix(&self.portfolio_returns)?;
224
275
 
225
276
  let opt_result = portfolio_optimization(&mean_returns, &cov_matrix, &self.portfolio_returns, self.risk_free_rate,
@@ -238,6 +289,7 @@ impl PortfolioPerformanceStats {
238
289
  start_date: self.start_date.clone(),
239
290
  end_date: self.end_date.clone(),
240
291
  interval: self.interval.clone(),
292
+ dates_array: self.dates_array.clone(),
241
293
  confidence_level: self.confidence_level,
242
294
  risk_free_rate: self.risk_free_rate,
243
295
  portfolio_returns: self.portfolio_returns.clone(),
@@ -85,7 +85,7 @@ impl PerformanceStats {
85
85
  let downside_mask = &returns.lt_eq(0.0).unwrap();
86
86
  let downside_returns = returns.filter(downside_mask).unwrap();
87
87
  let sortino_ratio = (annualized_return - risk_free_rate) / (std_dev( &downside_returns) * 252.0_f64.sqrt());
88
- let excess_returns = returns.clone() - benchmark_returns.clone();
88
+ let excess_returns = (returns.clone() - benchmark_returns.clone())?;
89
89
  let active_return = excess_returns.mean().ok_or("Error calculating active return")?;
90
90
  let active_return = ((1.0 + active_return/100.0).powf(252.0) - 1.0) * 100.0;
91
91
  let active_risk = std_dev(&excess_returns) * 252.0_f64.sqrt();
@@ -309,7 +309,7 @@ pub fn rand_weights(num_assets: usize) -> Vec<f64> {
309
309
  pub fn mean_portfolio_return(weights: &Vec<f64>, mean_returns: &Vec<f64>) -> f64 {
310
310
  let weights = Series::new("Weights", weights);
311
311
  let mean_returns = Series::new("Mean Returns", mean_returns);
312
- let weighted_returns = mean_returns.mul(weights);
312
+ let weighted_returns = mean_returns.mul(weights).unwrap();
313
313
  let mean_portfolio_return = weighted_returns.sum().unwrap();
314
314
  mean_portfolio_return
315
315
  }
@@ -348,7 +348,7 @@ pub fn daily_portfolio_returns(weights: &Vec<f64>, returns: &DataFrame) -> Serie
348
348
  let col_str = returns.get_column_names()[i];
349
349
  let security_returns = returns.column(col_str).unwrap().f64().unwrap().to_vec();
350
350
  let weighted_returns = security_returns.iter().map(|x| x.unwrap() * weight).collect::<Vec<f64>>();
351
- portfolio_returns = portfolio_returns + Series::new("Portfolio Returns", weighted_returns);
351
+ portfolio_returns = (portfolio_returns + Series::new("Portfolio Returns", weighted_returns)).unwrap();
352
352
  }
353
353
  portfolio_returns
354
354
  }
@@ -253,7 +253,7 @@ impl VolatilitySurface for Ticker {
253
253
  let vol_df = df.filter(&mask)?;
254
254
  if vol_df.height() > 0 {
255
255
  let option_price = vol_df.column("lastPrice")?.f64()?.get(0).unwrap();
256
- let option_type = vol_df.column("type")?.utf8()?.get(0).unwrap();
256
+ let option_type = vol_df.column("type")?.str()?.get(0).unwrap();
257
257
  let option_type = match option_type {
258
258
  "call" => OptionType::Call,
259
259
  "put" => OptionType::Put,
@@ -3,8 +3,8 @@ use plotly::{Bar, Histogram, Layout, Plot, Scatter, Table};
3
3
  use plotly::color::NamedColor;
4
4
  use plotly::common::{Fill, Marker, MarkerSymbol, Mode, Title};
5
5
  use plotly::layout::{Axis, GridPattern, LayoutGrid, RowOrder};
6
+ use plotly::traces::table::{Cells, Header};
6
7
  use crate::analytics::statistics::cumulative_returns_list;
7
- use crate::utils::date_utils::generate_dates;
8
8
  use crate::models::portfolio::Portfolio;
9
9
 
10
10
 
@@ -52,11 +52,20 @@ impl PortfolioCharts for Portfolio {
52
52
  let ticker_symbols = self.performance_stats.ticker_symbols.clone();
53
53
  let weights = self.performance_stats.optimal_weights.clone().iter()
54
54
  .map(|x| x * 100.0).collect::<Vec<f64>>();
55
- let allocation_trace = Bar::new(ticker_symbols.clone(), weights.clone())
55
+
56
+ let filtered: Vec<_> = ticker_symbols.iter()
57
+ .zip(weights.iter())
58
+ .filter(|&(_, &weight)| weight > 0.0)
59
+ .collect();
60
+
61
+ let filtered_ticker_symbols: Vec<String> = filtered.iter().map(|&(ticker, _)| ticker.clone()).collect();
62
+ let filtered_weights: Vec<f64> = filtered.iter().map(|&(_, &weight)| weight).collect();
63
+
64
+ let allocation_trace = Bar::new(filtered_ticker_symbols.clone(), filtered_weights.clone())
56
65
  .name("Asset Allocation")
57
66
  .x_axis("x2")
58
67
  .y_axis("y2")
59
- .text_array(weights.clone().iter().map(|w| format!("{:.2}%", w).to_string()).collect::<Vec<_>>());
68
+ .text_array(filtered_weights.clone().iter().map(|w| format!("{:.2}%", w).to_string()).collect::<Vec<_>>());
60
69
 
61
70
 
62
71
  let mut plot = Plot::new();
@@ -111,8 +120,7 @@ impl PortfolioCharts for Portfolio {
111
120
  ///
112
121
  /// * `Plot` Plotly Chart struct
113
122
  fn performance_chart(&self, height: usize, width: usize) -> Result<Plot, Box<dyn Error>> {
114
- let dates = generate_dates(&*self.performance_stats.start_date,
115
- &*self.performance_stats.end_date, 1);
123
+ let dates = self.performance_stats.dates_array.clone();
116
124
 
117
125
  let returns = self.performance_stats.optimal_portfolio_returns.f64().unwrap().to_vec()
118
126
  .iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
@@ -198,17 +206,24 @@ impl PortfolioCharts for Portfolio {
198
206
  fn asset_returns_chart(&self, height: usize, width: usize) -> Result<Plot, Box<dyn Error>> {
199
207
  let symbols = self.performance_stats.ticker_symbols.clone();
200
208
  let asset_returns = self.performance_stats.portfolio_returns.clone();
201
- let dates = generate_dates(&*self.performance_stats.start_date, &*self.performance_stats.end_date, 1);
209
+ let dates = self.performance_stats.dates_array.clone();
202
210
  let mut plot = Plot::new();
203
211
 
204
212
  for symbol in symbols {
205
- let returns = asset_returns.column(&symbol).unwrap().f64().unwrap().to_vec()
206
- .iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
207
- let cum_returns = cumulative_returns_list(returns.clone());
208
- let cum_returns_trace = Scatter::new(dates.clone(), cum_returns.clone())
209
- .name(format!("{}", symbol))
210
- .mode(Mode::Lines);
211
- plot.add_trace(cum_returns_trace);
213
+ match asset_returns.column(&symbol) {
214
+ Ok(returns_series) => {
215
+ let returns = returns_series.f64().unwrap().to_vec()
216
+ .iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
217
+ let cum_returns = cumulative_returns_list(returns.clone());
218
+ let cum_returns_trace = Scatter::new(dates.clone(), cum_returns.clone())
219
+ .name(format!("{}", symbol))
220
+ .mode(Mode::Lines);
221
+ plot.add_trace(cum_returns_trace);
222
+ }
223
+ Err(e) => {
224
+ eprintln!("Unable to fetch returns for {}: {}", symbol, e);
225
+ }
226
+ }
212
227
  }
213
228
 
214
229
  let layout = Layout::new()
@@ -273,11 +288,11 @@ impl PortfolioCharts for Portfolio {
273
288
 
274
289
 
275
290
  let trace = Table::new(
276
- vec![
291
+ Header::new(vec![
277
292
  format!("<span style=\"font-weight:bold; color:darkgreen;\">{}</span>", "Performance Stats"),
278
293
  format!("<span style=\"font-weight:bold; color:darkgreen;\">{}</span>", "Values"),
279
- ],
280
- vec![fields, values],
294
+ ]),
295
+ Cells::new(vec![fields, values]),
281
296
  );
282
297
  let mut plot = Plot::new();
283
298
  plot.add_trace(trace);
@@ -6,6 +6,7 @@ use num_format::{Locale, ToFormattedString};
6
6
  use plotly::common::{Fill, Line, LineShape, Mode, Title};
7
7
  use plotly::{Bar, Candlestick, Histogram, Layout, Plot, Scatter, Surface, Table};
8
8
  use plotly::layout::{Axis, GridPattern, LayoutGrid, LayoutScene, RangeSelector, RangeSlider, RowOrder, SelectorButton, SelectorStep, StepMode};
9
+ use plotly::traces::table::{Cells, Header};
9
10
 
10
11
  use crate::models::ticker::Ticker;
11
12
  use crate::data::ticker::TickerData;
@@ -13,7 +14,7 @@ use crate::prelude::TechnicalIndicators;
13
14
  use crate::analytics::fundamentals::Financials;
14
15
  use crate::analytics::performance::TickerPerformance;
15
16
  use crate::analytics::stochastics::VolatilitySurface;
16
- use crate::utils::date_utils::{generate_dates, to_date};
17
+ use crate::utils::date_utils::to_date;
17
18
  use crate::analytics::statistics::cumulative_returns_list;
18
19
 
19
20
  pub trait TickerCharts {
@@ -86,8 +87,7 @@ impl TickerCharts for Ticker {
86
87
  let layout = Layout::new()
87
88
  .height(height)
88
89
  .width(width)
89
- .title(Title::new(&*format!("<span style=\"font-weight:bold; color:darkgreen;\">{} Candlestick Chart</span>",
90
- self.ticker)))
90
+ .title(&*format!("<span style=\"font-weight:bold; color:darkgreen;\">{} Candlestick Chart</span>", self.ticker))
91
91
  .grid(
92
92
  LayoutGrid::new()
93
93
  .rows(3)
@@ -171,9 +171,7 @@ impl TickerCharts for Ticker {
171
171
  /// * `Plot` Plotly Chart struct
172
172
  async fn performance_chart(&self, height: usize, width: usize) -> Result<Plot, Box<dyn Error>> {
173
173
  let performance_stats = self.performance_stats().await?;
174
- let dates = generate_dates(&*performance_stats.start_date,
175
- &*performance_stats.end_date, 1);
176
-
174
+ let dates = performance_stats.dates_array;
177
175
  let returns = performance_stats.security_returns.f64().unwrap().to_vec()
178
176
  .iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
179
177
 
@@ -331,11 +329,11 @@ impl TickerCharts for Ticker {
331
329
  ];
332
330
 
333
331
  let trace = Table::new(
334
- vec![
332
+ Header::new(vec![
335
333
  format!("<span style=\"font-weight:bold; color:darkgreen;\">{}</span>", "Summary Stats"),
336
334
  format!("<span style=\"font-weight:bold; color:darkgreen;\">{}</span>", "Values"),
337
- ],
338
- vec![fields, values],
335
+ ]),
336
+ Cells::new(vec![fields, values]),
339
337
  );
340
338
  let mut plot = Plot::new();
341
339
  plot.add_trace(trace);
@@ -404,11 +402,11 @@ impl TickerCharts for Ticker {
404
402
 
405
403
 
406
404
  let trace = Table::new(
407
- vec![
405
+ Header::new(vec![
408
406
  format!("<span style=\"font-weight:bold; color:darkgreen;\">{}</span>", "Performance Stats"),
409
407
  format!("<span style=\"font-weight:bold; color:darkgreen;\">{}</span>", "Values"),
410
- ],
411
- vec![fields, values],
408
+ ]),
409
+ Cells::new(vec![fields, values]),
412
410
  );
413
411
  let mut plot = Plot::new();
414
412
  plot.add_trace(trace);
@@ -453,7 +451,7 @@ impl TickerCharts for Ticker {
453
451
  x.iter()
454
452
  .map(|y| match y {
455
453
  AnyValue::Null => "".to_string(),
456
- AnyValue::Utf8(str_val) => str_val.to_string(),
454
+ AnyValue::String(str_val) => str_val.to_string(),
457
455
  AnyValue::Float64(val) => if name == &"Financial Ratios" {format!("{:.2}", val)}
458
456
  else if val > -999.0 && val < 999.0 {format!("${:.2}", val)}
459
457
  else{format!("${}", (val as i64).to_formatted_string(&Locale::en))
@@ -464,8 +462,8 @@ impl TickerCharts for Ticker {
464
462
  })
465
463
  .collect::<Vec<Vec<String>>>();
466
464
  let trace = Table::new(
467
- fields,
468
- values,
465
+ Header::new(fields),
466
+ Cells::new(values),
469
467
  );
470
468
  let mut plot = Plot::new();
471
469
  plot.add_trace(trace);