finalytics 0.4.1__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 (37) hide show
  1. {finalytics-0.4.1 → finalytics-0.4.2}/PKG-INFO +1 -1
  2. {finalytics-0.4.1 → finalytics-0.4.2}/README.md +5 -5
  3. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/Cargo.toml +3 -3
  4. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/analytics/fundamentals.rs +42 -41
  5. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/analytics/performance.rs +37 -20
  6. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/analytics/statistics.rs +3 -3
  7. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/analytics/stochastics.rs +1 -1
  8. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/charts/portfolio.rs +6 -7
  9. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/charts/ticker.rs +13 -15
  10. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/utils/date_utils.rs +0 -17
  11. {finalytics-0.4.1 → finalytics-0.4.2}/python/Cargo.toml +3 -3
  12. {finalytics-0.4.1 → finalytics-0.4.2}/python/src/ffi.rs +1 -1
  13. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/analytics/mod.rs +0 -0
  14. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/analytics/optimization.rs +0 -0
  15. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/analytics/sentiment.rs +0 -0
  16. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/analytics/technicals.rs +0 -0
  17. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/charts/mod.rs +0 -0
  18. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/data/config.rs +0 -0
  19. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/data/mod.rs +0 -0
  20. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/data/ticker.rs +0 -0
  21. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/lib.rs +0 -0
  22. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/models/mod.rs +0 -0
  23. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/models/portfolio.rs +0 -0
  24. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/models/ticker.rs +0 -0
  25. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/utils/chart_utils.rs +0 -0
  26. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/utils/mod.rs +0 -0
  27. {finalytics-0.4.1 → finalytics-0.4.2}/finalytics/src/utils/web_utils.rs +0 -0
  28. {finalytics-0.4.1 → finalytics-0.4.2}/pyproject.toml +0 -0
  29. {finalytics-0.4.1 → finalytics-0.4.2}/python/Cargo.lock +0 -0
  30. {finalytics-0.4.1 → finalytics-0.4.2}/python/README.md +0 -0
  31. {finalytics-0.4.1 → finalytics-0.4.2}/python/build.sh +0 -0
  32. {finalytics-0.4.1 → finalytics-0.4.2}/python/conf.py +0 -0
  33. {finalytics-0.4.1 → finalytics-0.4.2}/python/index.rst +0 -0
  34. {finalytics-0.4.1 → finalytics-0.4.2}/python/src/lib.rs +0 -0
  35. {finalytics-0.4.1 → finalytics-0.4.2}/python/src/portfolio.rs +0 -0
  36. {finalytics-0.4.1 → finalytics-0.4.2}/python/src/ticker.rs +0 -0
  37. {finalytics-0.4.1 → 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.1
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.5.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.1"
3
+ version = "0.5.2"
4
4
  authors = ["Nnamdi Olisaeloka <nnamdiolisaeloka@gmail.com>"]
5
5
  edition = "2021"
6
6
  homepage = "https://finalytics.rs/"
@@ -24,7 +24,7 @@ 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", "fmt_no_tty"] }
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"
@@ -34,7 +34,7 @@ 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
39
  futures = "0.3.30"
40
40
 
@@ -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,6 @@
1
1
  use polars::prelude::*;
2
2
  use std::error::Error;
3
+ use chrono::{DateTime, NaiveDateTime};
3
4
  use futures::future::join_all;
4
5
 
5
6
  use crate::data::config::Interval;
@@ -15,6 +16,7 @@ pub struct TickerPerformanceStats {
15
16
  pub benchmark_symbol: String,
16
17
  pub start_date: String,
17
18
  pub end_date: String,
19
+ pub dates_array: Vec<String>,
18
20
  pub interval: Interval,
19
21
  pub confidence_level: f64,
20
22
  pub risk_free_rate: f64,
@@ -49,17 +51,22 @@ impl TickerPerformance for Ticker {
49
51
  .risk_free_rate(self.risk_free_rate)
50
52
  .build();
51
53
  let benchmark_returns = benchmark_ticker.roc(1).await?;
52
- let benchmark_returns = benchmark_returns.join(
53
- &security_returns,
54
+ let benchmark_returns = security_returns.join(
55
+ &benchmark_returns,
54
56
  &["timestamp"],
55
57
  &["timestamp"],
56
- JoinArgs::new(JoinType::Inner),
58
+ JoinArgs::new(JoinType::Left),
57
59
  )?;
58
- let benchmark_returns = benchmark_returns.sort(&["timestamp"], false, false)?;
60
+ let benchmark_returns = benchmark_returns.sort(&["timestamp"], SortMultipleOptions::new().with_order_descending(false))?;
59
61
  let benchmark_returns = benchmark_returns.fill_null(FillNullStrategy::Forward(None))?;
60
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();
61
68
  let benchmark_returns = benchmark_returns.column("roc-1")?.clone();
62
- let security_returns = security_returns.column(&*self.ticker)?.clone();
69
+
63
70
  let performance_stats = PerformanceStats::compute_stats(
64
71
  security_returns.clone(), benchmark_returns.clone(),
65
72
  self.risk_free_rate, self.confidence_level, self.interval)?;
@@ -68,6 +75,7 @@ impl TickerPerformance for Ticker {
68
75
  benchmark_symbol: self.benchmark_symbol.clone(),
69
76
  start_date: self.start_date.clone(),
70
77
  end_date: self.end_date.clone(),
78
+ dates_array,
71
79
  interval: self.interval.clone(),
72
80
  confidence_level: self.confidence_level,
73
81
  risk_free_rate: self.risk_free_rate,
@@ -89,6 +97,7 @@ pub struct PortfolioPerformanceStats {
89
97
  pub start_date: String,
90
98
  pub end_date: String,
91
99
  pub interval: Interval,
100
+ pub dates_array: Vec<String>,
92
101
  pub confidence_level: f64,
93
102
  pub risk_free_rate: f64,
94
103
  pub portfolio_returns: DataFrame,
@@ -191,10 +200,11 @@ impl PortfolioPerformanceStats {
191
200
  df,
192
201
  &["timestamp"],
193
202
  &["timestamp"],
194
- JoinArgs::new(JoinType::Outer),
203
+ JoinArgs::new(JoinType::Full).with_coalesce(JoinCoalesce::CoalesceColumns),
195
204
  )?;
196
205
  }
197
- portfolio_returns = portfolio_returns.sort(&["timestamp"], false, false)?;
206
+
207
+ portfolio_returns = portfolio_returns.sort(&["timestamp"], SortMultipleOptions::new().with_order_descending(false))?;
198
208
  portfolio_returns = portfolio_returns.fill_null(FillNullStrategy::Forward(None))?;
199
209
  portfolio_returns = portfolio_returns.fill_null(FillNullStrategy::Backward(None))?;
200
210
 
@@ -206,23 +216,20 @@ impl PortfolioPerformanceStats {
206
216
  .risk_free_rate(risk_free_rate)
207
217
  .build();
208
218
  let benchmark_returns = benchmark_ticker.roc(1).await?;
209
- let benchmark_returns = if portfolio_returns.height() > benchmark_returns.height() {
210
- benchmark_returns.join(
211
- &portfolio_returns,
212
- &["timestamp"],
213
- &["timestamp"],
214
- JoinArgs::new(JoinType::Outer),
215
- )?} else {
216
- portfolio_returns.join(
219
+ let benchmark_returns = portfolio_returns.join(
217
220
  &benchmark_returns,
218
221
  &["timestamp"],
219
222
  &["timestamp"],
220
- JoinArgs::new(JoinType::Inner),
221
- )?};
223
+ JoinArgs::new(JoinType::Left),
224
+ )?;
222
225
 
223
- let benchmark_returns = benchmark_returns.sort(&["timestamp"], false, false)?;
226
+ let benchmark_returns = benchmark_returns.sort(&["timestamp"], SortMultipleOptions::new().with_order_descending(false))?;
224
227
  let benchmark_returns = benchmark_returns.fill_null(FillNullStrategy::Forward(None))?;
225
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>>();
226
233
  let benchmark_returns = benchmark_returns.column("roc-1")?.clone();
227
234
 
228
235
  let _ = portfolio_returns.drop_in_place("timestamp")?;
@@ -233,6 +240,7 @@ impl PortfolioPerformanceStats {
233
240
  start_date: start_date.to_string(),
234
241
  end_date: end_date.to_string(),
235
242
  interval,
243
+ dates_array,
236
244
  confidence_level,
237
245
  risk_free_rate,
238
246
  portfolio_returns: portfolio_returns.clone(),
@@ -253,8 +261,16 @@ impl PortfolioPerformanceStats {
253
261
  ///
254
262
  /// * `PortfolioPerformanceStats` struct
255
263
  pub fn compute_stats(&self) -> Result<PortfolioPerformanceStats, Box<dyn Error>> {
256
- let mean_returns = self.portfolio_returns.mean().iter().map(|x| x.f64().unwrap()
257
- .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>>();
258
274
  let cov_matrix = covariance_matrix(&self.portfolio_returns)?;
259
275
 
260
276
  let opt_result = portfolio_optimization(&mean_returns, &cov_matrix, &self.portfolio_returns, self.risk_free_rate,
@@ -273,6 +289,7 @@ impl PortfolioPerformanceStats {
273
289
  start_date: self.start_date.clone(),
274
290
  end_date: self.end_date.clone(),
275
291
  interval: self.interval.clone(),
292
+ dates_array: self.dates_array.clone(),
276
293
  confidence_level: self.confidence_level,
277
294
  risk_free_rate: self.risk_free_rate,
278
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
 
@@ -120,8 +120,7 @@ impl PortfolioCharts for Portfolio {
120
120
  ///
121
121
  /// * `Plot` Plotly Chart struct
122
122
  fn performance_chart(&self, height: usize, width: usize) -> Result<Plot, Box<dyn Error>> {
123
- let dates = generate_dates(&*self.performance_stats.start_date,
124
- &*self.performance_stats.end_date, 1);
123
+ let dates = self.performance_stats.dates_array.clone();
125
124
 
126
125
  let returns = self.performance_stats.optimal_portfolio_returns.f64().unwrap().to_vec()
127
126
  .iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
@@ -207,7 +206,7 @@ impl PortfolioCharts for Portfolio {
207
206
  fn asset_returns_chart(&self, height: usize, width: usize) -> Result<Plot, Box<dyn Error>> {
208
207
  let symbols = self.performance_stats.ticker_symbols.clone();
209
208
  let asset_returns = self.performance_stats.portfolio_returns.clone();
210
- let dates = generate_dates(&*self.performance_stats.start_date, &*self.performance_stats.end_date, 1);
209
+ let dates = self.performance_stats.dates_array.clone();
211
210
  let mut plot = Plot::new();
212
211
 
213
212
  for symbol in symbols {
@@ -289,11 +288,11 @@ impl PortfolioCharts for Portfolio {
289
288
 
290
289
 
291
290
  let trace = Table::new(
292
- vec![
291
+ Header::new(vec![
293
292
  format!("<span style=\"font-weight:bold; color:darkgreen;\">{}</span>", "Performance Stats"),
294
293
  format!("<span style=\"font-weight:bold; color:darkgreen;\">{}</span>", "Values"),
295
- ],
296
- vec![fields, values],
294
+ ]),
295
+ Cells::new(vec![fields, values]),
297
296
  );
298
297
  let mut plot = Plot::new();
299
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);
@@ -1,6 +1,5 @@
1
1
  use chrono::{DateTime, NaiveDate, NaiveDateTime, Timelike, Utc};
2
2
  use std::error::Error;
3
- use std::str::FromStr;
4
3
 
5
4
  /// Converts a date string in YYYY-MM-DD format to a Unix Timestamp
6
5
  pub fn to_timestamp(date_str: &str) -> Result<i64, Box<dyn Error>> {
@@ -23,22 +22,6 @@ pub fn to_datetime(date_str: &str) -> Result<NaiveDateTime, Box<dyn Error>> {
23
22
  Ok(datetime)
24
23
  }
25
24
 
26
- /// Generates a vector of dates between a start date and an end date
27
- pub fn generate_dates(start_date: &str, end_date: &str, interval_days: i64) -> Vec<String> {
28
- let start_date = NaiveDate::from_str(start_date).expect("Invalid start date");
29
- let end_date = NaiveDate::from_str(end_date).expect("Invalid end date");
30
-
31
- let mut current_date = start_date;
32
- let mut date_vector = Vec::new();
33
-
34
- while current_date <= end_date {
35
- date_vector.push(current_date.to_string());
36
- current_date += chrono::Duration::days(interval_days);
37
- }
38
-
39
- date_vector
40
- }
41
-
42
25
  /// Computes the the time to maturity of an option in months
43
26
  pub fn time_to_maturity(timestamp: i64) -> f64{
44
27
  let current_date = chrono::Local::now().naive_local().date();
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "finalytics-py"
3
- version = "0.4.1"
3
+ version = "0.4.2"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -10,7 +10,7 @@ crate-type = ["cdylib"]
10
10
 
11
11
  [dependencies]
12
12
  finalytics = { path = "../finalytics", features = ["kaleido"] }
13
- polars = "0.33.2"
13
+ polars = "0.41.3"
14
14
  tokio = { version = "1.32.0", features = ["full"] }
15
15
  pyo3 = { version = "0.20.0", features = ["extension-module", "generate-import-lib"] }
16
- plotly = { version = "0.8.4", package = "plotly-fork"}
16
+ plotly = "0.9.0"
@@ -53,7 +53,7 @@ pub fn to_py_array(py: Python, pyarrow: &PyModule, array: ArrayRef) -> PyResult<
53
53
  pub fn rust_series_to_py_series(series: &Series) -> PyResult<PyObject> {
54
54
  // ensure we have a single chunk
55
55
  let series = series.rechunk();
56
- let array = series.to_arrow(0);
56
+ let array = series.to_arrow(0, false);
57
57
 
58
58
  Python::with_gil(|py| {
59
59
  // import pyarrow
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes