finalytics 0.4.0__tar.gz → 0.4.1__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.1}/PKG-INFO +1 -1
  2. {finalytics-0.4.0 → finalytics-0.4.1}/README.md +1 -1
  3. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/Cargo.toml +4 -3
  4. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/analytics/performance.rs +45 -10
  5. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/charts/portfolio.rs +25 -9
  6. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/data/ticker.rs +99 -63
  7. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/lib.rs +25 -4
  8. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/models/portfolio.rs +3 -3
  9. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/models/ticker.rs +1 -1
  10. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/utils/web_utils.rs +1 -2
  11. {finalytics-0.4.0 → finalytics-0.4.1}/python/Cargo.toml +1 -1
  12. {finalytics-0.4.0 → finalytics-0.4.1}/python/conf.py +9 -18
  13. finalytics-0.4.1/python/index.rst +198 -0
  14. finalytics-0.4.0/python/index.rst +0 -6
  15. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/analytics/fundamentals.rs +0 -0
  16. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/analytics/mod.rs +0 -0
  17. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/analytics/optimization.rs +0 -0
  18. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/analytics/sentiment.rs +0 -0
  19. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/analytics/statistics.rs +0 -0
  20. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/analytics/stochastics.rs +0 -0
  21. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/analytics/technicals.rs +0 -0
  22. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/charts/mod.rs +0 -0
  23. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/charts/ticker.rs +0 -0
  24. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/data/config.rs +0 -0
  25. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/data/mod.rs +0 -0
  26. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/models/mod.rs +0 -0
  27. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/utils/chart_utils.rs +0 -0
  28. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/utils/date_utils.rs +0 -0
  29. {finalytics-0.4.0 → finalytics-0.4.1}/finalytics/src/utils/mod.rs +0 -0
  30. {finalytics-0.4.0 → finalytics-0.4.1}/pyproject.toml +0 -0
  31. {finalytics-0.4.0 → finalytics-0.4.1}/python/Cargo.lock +0 -0
  32. {finalytics-0.4.0 → finalytics-0.4.1}/python/README.md +0 -0
  33. {finalytics-0.4.0 → finalytics-0.4.1}/python/build.sh +0 -0
  34. {finalytics-0.4.0 → finalytics-0.4.1}/python/src/ffi.rs +0 -0
  35. {finalytics-0.4.0 → finalytics-0.4.1}/python/src/lib.rs +0 -0
  36. {finalytics-0.4.0 → finalytics-0.4.1}/python/src/portfolio.rs +0 -0
  37. {finalytics-0.4.0 → finalytics-0.4.1}/python/src/ticker.rs +0 -0
  38. {finalytics-0.4.0 → finalytics-0.4.1}/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.1
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.0", features = ["kaleido"]}
18
18
  ```
19
19
 
20
20
  Or run the following command:
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "finalytics"
3
- version = "0.5.0"
3
+ version = "0.5.1"
4
4
  authors = ["Nnamdi Olisaeloka <nnamdiolisaeloka@gmail.com>"]
5
5
  edition = "2021"
6
6
  homepage = "https://finalytics.rs/"
@@ -24,11 +24,11 @@ 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.33.2", 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"
@@ -36,6 +36,7 @@ optimization = "0.2.0"
36
36
  html-escape = "0.2.13"
37
37
  plotly = { version = "0.8.4", package = "plotly-fork"}
38
38
  once_cell = "1.19.0"
39
+ futures = "0.3.30"
39
40
 
40
41
  [features]
41
42
  kaleido = ["plotly/kaleido"]
@@ -1,5 +1,6 @@
1
1
  use polars::prelude::*;
2
2
  use std::error::Error;
3
+ use futures::future::join_all;
3
4
 
4
5
  use crate::data::config::Interval;
5
6
  use crate::models::ticker::{Ticker, TickerBuilder};
@@ -131,21 +132,55 @@ impl PortfolioPerformanceStats {
131
132
  max_iterations: u64,
132
133
  objective_function: ObjectiveFunction
133
134
  ) -> 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)
135
+ let mut futures = Vec::new();
136
+
137
+ for ticker_symbol in ticker_symbols.clone().into_iter() {
138
+ let ticker = TickerBuilder::new().ticker(&ticker_symbol)
137
139
  .start_date(start_date)
138
140
  .end_date(end_date)
139
141
  .interval(interval)
140
142
  .confidence_level(confidence_level)
141
143
  .risk_free_rate(risk_free_rate)
142
144
  .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);
145
+
146
+ let fut = tokio::task::spawn(async move {
147
+ match ticker.roc(1).await {
148
+ Ok(security_df) => {
149
+ match DataFrame::new(vec![
150
+ security_df.column("timestamp").map_err(|e| e.to_string())?.clone(),
151
+ security_df.column("roc-1").map_err(|e| e.to_string())?.clone().with_name(&ticker_symbol)
152
+ ]) {
153
+ Ok(security_returns_df) => Ok(security_returns_df),
154
+ Err(e) => {
155
+ eprintln!("Error creating DataFrame for {}: {}", ticker_symbol, e);
156
+ Err(format!("Error creating DataFrame for {}: {}", ticker_symbol, e))
157
+ }
158
+ }
159
+ }
160
+ Err(e) => {
161
+ eprintln!("Error Fetching Price Data for {}: {}", ticker_symbol, e);
162
+ Err(format!("Error Fetching Price Data for {}: {}", ticker_symbol, e))
163
+ }
164
+ }
165
+ });
166
+
167
+ futures.push(fut);
168
+ }
169
+
170
+ let results = join_all(futures).await;
171
+ let mut fetched_symbols: Vec<String> = Vec::new();
172
+ let mut dfs: Vec<DataFrame> = Vec::new();
173
+
174
+ for result in results {
175
+ match result {
176
+ Ok(Ok(df)) => {
177
+ let symbol = df.get_column_names()[1].to_string();
178
+ fetched_symbols.push(symbol);
179
+ dfs.push(df);
180
+ }
181
+ Ok(Err(_)) => continue,
182
+ Err(e) => eprintln!("Error in task: {}", e),
183
+ }
149
184
  }
150
185
 
151
186
  let mut portfolio_returns = dfs[0].clone();
@@ -193,7 +228,7 @@ impl PortfolioPerformanceStats {
193
228
  let _ = portfolio_returns.drop_in_place("timestamp")?;
194
229
 
195
230
  Ok(PortfolioPerformanceStats {
196
- ticker_symbols: ticker_symbols.clone(),
231
+ ticker_symbols: fetched_symbols.clone(),
197
232
  benchmark_symbol: benchmark_symbol.to_string(),
198
233
  start_date: start_date.to_string(),
199
234
  end_date: end_date.to_string(),
@@ -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();
@@ -202,13 +211,20 @@ impl PortfolioCharts for Portfolio {
202
211
  let mut plot = Plot::new();
203
212
 
204
213
  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);
214
+ match asset_returns.column(&symbol) {
215
+ Ok(returns_series) => {
216
+ let returns = returns_series.f64().unwrap().to_vec()
217
+ .iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
218
+ let cum_returns = cumulative_returns_list(returns.clone());
219
+ let cum_returns_trace = Scatter::new(dates.clone(), cum_returns.clone())
220
+ .name(format!("{}", symbol))
221
+ .mode(Mode::Lines);
222
+ plot.add_trace(cum_returns_trace);
223
+ }
224
+ Err(e) => {
225
+ eprintln!("Unable to fetch returns for {}: {}", symbol, e);
226
+ }
227
+ }
212
228
  }
213
229
 
214
230
  let layout = Layout::new()
@@ -24,7 +24,8 @@ impl TickerData for Ticker {
24
24
  let response = REQUEST_CLIENT.get(&url).send().await?;
25
25
  let result= response.json::<Value>().await?;
26
26
  let value = &result["optionChain"]["result"][0]["quote"].to_string();
27
- let quote: Quote = serde_json::from_value(value.parse()?).expect("Failed to deserialize into Quote");
27
+ let quote: Quote = serde_json::from_value(value.parse()?)
28
+ .map_err(|e| format!("Failed to deserialize into Quote: {}", e))?;
28
29
  Ok(quote)
29
30
  }
30
31
 
@@ -34,7 +35,8 @@ impl TickerData for Ticker {
34
35
  let response = REQUEST_CLIENT.get(&url).send().await?;
35
36
  let result = response.json::<Value>().await?;
36
37
  let value = &result["optionChain"]["result"][0]["quote"].to_string();
37
- let stats: TickerSummaryStats = serde_json::from_value(value.parse()?).expect("Failed to deserialize into TickerSummaryStats");
38
+ let stats: TickerSummaryStats = serde_json::from_value(value.parse()?)
39
+ .map_err(|e| format!("Failed to deserialize into TickerSummaryStats: {}", e))?;
38
40
  Ok(stats)
39
41
  }
40
42
 
@@ -53,7 +55,7 @@ impl TickerData for Ticker {
53
55
  let value = &result["chart"]["result"][0];
54
56
  let timestamp = &value["timestamp"]
55
57
  .as_array()
56
- .ok_or(format!("timestamp array not found: {result}"))?
58
+ .ok_or(format!("timestamp array not found for {}: {}", self.ticker, result))?
57
59
  .iter()
58
60
  .map(|ts| {
59
61
  let timestamp = ts.as_i64().unwrap();
@@ -76,35 +78,35 @@ impl TickerData for Ticker {
76
78
 
77
79
  let open = indicators["open"]
78
80
  .as_array()
79
- .ok_or("open array not found")?
81
+ .ok_or(format!("open array not found for {}: {}", self.ticker, result))?
80
82
  .iter()
81
83
  .map(|o| o.as_f64().unwrap_or(0.0))
82
84
  .collect::<Vec<f64>>();
83
85
 
84
86
  let high = indicators["high"]
85
87
  .as_array()
86
- .ok_or("high array not found")?
88
+ .ok_or(format!("high array not found for {}: {}", self.ticker, result))?
87
89
  .iter()
88
90
  .map(|h| h.as_f64().unwrap_or(0.0))
89
91
  .collect::<Vec<f64>>();
90
92
 
91
93
  let low = indicators["low"]
92
94
  .as_array()
93
- .ok_or("low array not found")?
95
+ .ok_or(format!("low array not found for {}: {}", self.ticker, result))?
94
96
  .iter()
95
97
  .map(|l| l.as_f64().unwrap_or(0.0))
96
98
  .collect::<Vec<f64>>();
97
99
 
98
100
  let close = indicators["close"]
99
101
  .as_array()
100
- .ok_or("close array not found")?
102
+ .ok_or(format!("close array not found for {}: {}", self.ticker, result))?
101
103
  .iter()
102
104
  .map(|c| c.as_f64().unwrap_or(0.0))
103
105
  .collect::<Vec<f64>>();
104
106
 
105
107
  let volume = indicators["volume"]
106
108
  .as_array()
107
- .ok_or("volume array not found")?
109
+ .ok_or(format!("volume array not found for {}: {}", self.ticker, result))?
108
110
  .iter()
109
111
  .map(|v| v.as_f64().unwrap_or(0.0))
110
112
  .collect::<Vec<f64>>();
@@ -114,7 +116,7 @@ impl TickerData for Ticker {
114
116
  .unwrap_or_else(|| {
115
117
  indicators["close"]
116
118
  .as_array()
117
- .ok_or("close array not found")
119
+ .ok_or(format!("close array not found for {}: {}", self.ticker, result))
118
120
  .unwrap_or_else(|_| {
119
121
  indicators["close"]
120
122
  .as_array()
@@ -150,71 +152,103 @@ impl TickerData for Ticker {
150
152
  Ok(df)
151
153
  }
152
154
 
155
+
153
156
  /// Returns Ticker Option Chain Data from Yahoo Finance for all available expirations
154
157
  async fn get_options(&self) -> Result<Options, Box<dyn Error>> {
155
158
  let url = format!("https://query2.finance.yahoo.com/v6/finance/options/{}", self.ticker);
156
159
  let response = REQUEST_CLIENT.get(&url).send().await?;
157
160
  let result = response.json::<Value>().await?;
158
- let ticker_price = result["optionChain"]["result"][0]["quote"]["regularMarketPrice"].as_f64().unwrap();
159
- let expiration_dates = &result["optionChain"]["result"][0]["expirationDates"];
160
- let strike_values = &result["optionChain"]["result"][0]["strikes"];
161
- let strikes = strike_values.as_array().unwrap()
162
- .iter().map(|x| x.as_f64().unwrap()).collect::<Vec<f64>>();
163
- let timestamps = expiration_dates.as_array().unwrap().iter()
164
- .map(|x| x.as_i64().unwrap()).collect::<Vec<i64>>();
165
- let ttms = timestamps.clone().iter().map(|x| time_to_maturity(*x))
161
+
162
+ let ticker_price = result["optionChain"]["result"][0]["quote"]["regularMarketPrice"]
163
+ .as_f64()
164
+ .ok_or("Failed to parse regularMarketPrice as f64")?;
165
+
166
+ let expiration_dates = result["optionChain"]["result"][0]["expirationDates"]
167
+ .as_array()
168
+ .ok_or("Failed to parse expirationDates as array")?;
169
+
170
+ let strike_values = result["optionChain"]["result"][0]["strikes"]
171
+ .as_array()
172
+ .ok_or("Failed to parse strikes as array")?;
173
+
174
+ let strikes = strike_values
175
+ .iter()
176
+ .map(|x| x.as_f64().ok_or("Failed to parse strike as f64"))
177
+ .collect::<Result<Vec<f64>, _>>()?;
178
+
179
+ let timestamps = expiration_dates
180
+ .iter()
181
+ .map(|x| x.as_i64().ok_or("Failed to parse expiration date as i64"))
182
+ .collect::<Result<Vec<i64>, _>>()?;
183
+
184
+ let ttms = timestamps
185
+ .iter()
186
+ .map(|x| time_to_maturity(*x))
166
187
  .collect::<Vec<f64>>();
167
- let expiration_dates = timestamps.iter().map(|x| to_date(*x))
188
+
189
+ let expiration_dates = timestamps
190
+ .iter()
191
+ .map(|x| to_date(*x))
168
192
  .collect::<Vec<String>>();
193
+
169
194
  let mut options_chain = DataFrame::default();
170
- for t in timestamps.iter() {
171
- let url = format!("https://query2.finance.yahoo.com/v6/finance/options/{}?date={}", self.ticker, t);
195
+
196
+ for t in &timestamps {
197
+ let url = format!(
198
+ "https://query2.finance.yahoo.com/v6/finance/options/{}?date={}",
199
+ self.ticker, t
200
+ );
172
201
  let response = REQUEST_CLIENT.get(&url).send().await?;
173
202
  let result = response.json::<Value>().await?;
174
203
  let expiration = to_date(*t);
175
204
  let ttm = time_to_maturity(*t);
205
+
176
206
  let calls = &result["optionChain"]["result"][0]["options"][0]["calls"];
177
- let calls_vec: Vec<OptionContract> = serde_json::from_value(calls.to_string().parse()?)
178
- .expect("Failed to deserialize into Option Chain");
207
+ let calls_vec: Vec<OptionContract> = serde_json::from_value(calls.clone())
208
+ .map_err(|e| format!("Failed to deserialize calls into OptionContract: {}", e))?;
209
+
179
210
  let calls_df = df!(
180
- "expiration" => calls_vec.iter().map(|_| &*expiration).collect::<Vec<&str>>(),
181
- "ttm" => calls_vec.iter().map(|_| ttm).collect::<Vec<f64>>(),
182
- "type" => calls_vec.iter().map(|_| "call").collect::<Vec<&str>>(),
183
- "contractSymbol" => calls_vec.iter().map(|x| x.contractSymbol.as_str()).collect::<Vec<&str>>(),
184
- "strike" => calls_vec.iter().map(|x| x.strike).collect::<Vec<f64>>(),
185
- "currency" => calls_vec.iter().map(|x| x.currency.as_str()).collect::<Vec<&str>>(),
186
- "lastPrice" => calls_vec.iter().map(|x| x.lastPrice).collect::<Vec<f64>>(),
187
- "change" => calls_vec.iter().map(|x| x.change).collect::<Vec<f64>>(),
188
- "percentChange" => calls_vec.iter().map(|x| x.percentChange).collect::<Vec<f64>>(),
189
- "openInterest" => calls_vec.iter().map(|x| x.openInterest).collect::<Vec<f64>>(),
190
- "bid" => calls_vec.iter().map(|x| x.bid).collect::<Vec<f64>>(),
191
- "ask" => calls_vec.iter().map(|x| x.ask).collect::<Vec<f64>>(),
192
- "contractSize" => calls_vec.iter().map(|x| x.contractSize.as_str()).collect::<Vec<&str>>(),
193
- "lastTradeDate" => calls_vec.iter().map(|x| DateTime::from_timestamp(x.lastTradeDate, 0).unwrap().naive_local()).collect::<Vec<NaiveDateTime>>(),
194
- "impliedVolatility" => calls_vec.iter().map(|x| x.impliedVolatility).collect::<Vec<f64>>(),
195
- "inTheMoney" => calls_vec.iter().map(|x| x.inTheMoney).collect::<Vec<bool>>(),
196
- )?;
211
+ "expiration" => calls_vec.iter().map(|_| &*expiration).collect::<Vec<&str>>(),
212
+ "ttm" => calls_vec.iter().map(|_| ttm).collect::<Vec<f64>>(),
213
+ "type" => calls_vec.iter().map(|_| "call").collect::<Vec<&str>>(),
214
+ "contractSymbol" => calls_vec.iter().map(|x| x.contractSymbol.as_str()).collect::<Vec<&str>>(),
215
+ "strike" => calls_vec.iter().map(|x| x.strike).collect::<Vec<f64>>(),
216
+ "currency" => calls_vec.iter().map(|x| x.currency.as_str()).collect::<Vec<&str>>(),
217
+ "lastPrice" => calls_vec.iter().map(|x| x.lastPrice).collect::<Vec<f64>>(),
218
+ "change" => calls_vec.iter().map(|x| x.change).collect::<Vec<f64>>(),
219
+ "percentChange" => calls_vec.iter().map(|x| x.percentChange).collect::<Vec<f64>>(),
220
+ "openInterest" => calls_vec.iter().map(|x| x.openInterest).collect::<Vec<f64>>(),
221
+ "bid" => calls_vec.iter().map(|x| x.bid).collect::<Vec<f64>>(),
222
+ "ask" => calls_vec.iter().map(|x| x.ask).collect::<Vec<f64>>(),
223
+ "contractSize" => calls_vec.iter().map(|x| x.contractSize.as_str()).collect::<Vec<&str>>(),
224
+ "lastTradeDate" => calls_vec.iter().map(|x| DateTime::from_timestamp(x.lastTradeDate, 0).unwrap().naive_local()).collect::<Vec<NaiveDateTime>>(),
225
+ "impliedVolatility" => calls_vec.iter().map(|x| x.impliedVolatility).collect::<Vec<f64>>(),
226
+ "inTheMoney" => calls_vec.iter().map(|x| x.inTheMoney).collect::<Vec<bool>>(),
227
+ )?;
228
+
197
229
  let puts = &result["optionChain"]["result"][0]["options"][0]["puts"];
198
- let puts_vec: Vec<OptionContract> = serde_json::from_value(puts.to_string().parse()?)
199
- .expect("Failed to deserialize into Option Chain");
230
+ let puts_vec: Vec<OptionContract> = serde_json::from_value(puts.clone())
231
+ .map_err(|e| format!("Failed to deserialize puts into OptionContract: {}", e))?;
232
+
200
233
  let puts_df = df!(
201
- "expiration" => puts_vec.iter().map(|_| &*expiration).collect::<Vec<&str>>(),
202
- "ttm" => puts_vec.iter().map(|_| ttm).collect::<Vec<f64>>(),
203
- "type" => puts_vec.iter().map(|_| "put").collect::<Vec<&str>>(),
204
- "contractSymbol" => puts_vec.iter().map(|x| x.contractSymbol.as_str()).collect::<Vec<&str>>(),
205
- "strike" => puts_vec.iter().map(|x| x.strike).collect::<Vec<f64>>(),
206
- "currency" => puts_vec.iter().map(|x| x.currency.as_str()).collect::<Vec<&str>>(),
207
- "lastPrice" => puts_vec.iter().map(|x| x.lastPrice).collect::<Vec<f64>>(),
208
- "change" => puts_vec.iter().map(|x| x.change).collect::<Vec<f64>>(),
209
- "percentChange" => puts_vec.iter().map(|x| x.percentChange).collect::<Vec<f64>>(),
210
- "openInterest" => puts_vec.iter().map(|x| x.openInterest).collect::<Vec<f64>>(),
211
- "bid" => puts_vec.iter().map(|x| x.bid).collect::<Vec<f64>>(),
212
- "ask" => puts_vec.iter().map(|x| x.ask).collect::<Vec<f64>>(),
213
- "contractSize" => puts_vec.iter().map(|x| x.contractSize.as_str()).collect::<Vec<&str>>(),
214
- "lastTradeDate" => puts_vec.iter().map(|x| DateTime::from_timestamp(x.lastTradeDate, 0).unwrap().naive_local()).collect::<Vec<NaiveDateTime>>(),
215
- "impliedVolatility" => puts_vec.iter().map(|x| x.impliedVolatility).collect::<Vec<f64>>(),
216
- "inTheMoney" => puts_vec.iter().map(|x| x.inTheMoney).collect::<Vec<bool>>(),
217
- )?;
234
+ "expiration" => puts_vec.iter().map(|_| &*expiration).collect::<Vec<&str>>(),
235
+ "ttm" => puts_vec.iter().map(|_| ttm).collect::<Vec<f64>>(),
236
+ "type" => puts_vec.iter().map(|_| "put").collect::<Vec<&str>>(),
237
+ "contractSymbol" => puts_vec.iter().map(|x| x.contractSymbol.as_str()).collect::<Vec<&str>>(),
238
+ "strike" => puts_vec.iter().map(|x| x.strike).collect::<Vec<f64>>(),
239
+ "currency" => puts_vec.iter().map(|x| x.currency.as_str()).collect::<Vec<&str>>(),
240
+ "lastPrice" => puts_vec.iter().map(|x| x.lastPrice).collect::<Vec<f64>>(),
241
+ "change" => puts_vec.iter().map(|x| x.change).collect::<Vec<f64>>(),
242
+ "percentChange" => puts_vec.iter().map(|x| x.percentChange).collect::<Vec<f64>>(),
243
+ "openInterest" => puts_vec.iter().map(|x| x.openInterest).collect::<Vec<f64>>(),
244
+ "bid" => puts_vec.iter().map(|x| x.bid).collect::<Vec<f64>>(),
245
+ "ask" => puts_vec.iter().map(|x| x.ask).collect::<Vec<f64>>(),
246
+ "contractSize" => puts_vec.iter().map(|x| x.contractSize.as_str()).collect::<Vec<&str>>(),
247
+ "lastTradeDate" => puts_vec.iter().map(|x| DateTime::from_timestamp(x.lastTradeDate, 0).unwrap().naive_local()).collect::<Vec<NaiveDateTime>>(),
248
+ "impliedVolatility" => puts_vec.iter().map(|x| x.impliedVolatility).collect::<Vec<f64>>(),
249
+ "inTheMoney" => puts_vec.iter().map(|x| x.inTheMoney).collect::<Vec<bool>>(),
250
+ )?;
251
+
218
252
  let df = calls_df.vstack(&puts_df)?;
219
253
  options_chain.vstack_mut(&df)?;
220
254
  }
@@ -224,10 +258,11 @@ impl TickerData for Ticker {
224
258
  expiration_dates,
225
259
  ttms,
226
260
  strikes,
227
- chain: options_chain
261
+ chain: options_chain,
228
262
  })
229
263
  }
230
264
 
265
+
231
266
  /// Returns Ticker Fundamental Data from Yahoo Finance for a given statement type and frequency
232
267
  ///
233
268
  /// # Arguments
@@ -257,7 +292,8 @@ impl TickerData for Ticker {
257
292
  timeseries/{symbol}?symbol={symbol}&type={_type}&period1={period1}&period2={period2}");
258
293
  let response = REQUEST_CLIENT.get(&url).send().await?;
259
294
  let result = response.json::<Value>().await?;
260
- let data: FundamentalsResponse = serde_json::from_value(result).expect("Failed to parse JSON");
295
+ let data: FundamentalsResponse = serde_json::from_value(result)
296
+ .map_err(|e| format!("Failed to deserialize into FundamentalsResponse: {}", e))?;
261
297
  let mut columns: Vec<Series> = vec![];
262
298
  let mut temp_items: HashMap<String, Value> = HashMap::new();
263
299
  let mut init = 0;
@@ -266,7 +302,7 @@ impl TickerData for Ticker {
266
302
  for (key, value) in item {
267
303
  if _type_clone.contains(&key.as_str()){
268
304
  let items: Vec<Object> = serde_json::from_value(value.to_string().parse()?)
269
- .expect("Failed to deserialize into Object");
305
+ .map_err(|e| format!("Failed to deserialize into Object: {}", e))?;
270
306
  let date_vec = items.iter().map(|x| x.asOfDate.clone()).collect::<Vec<String>>();
271
307
  if date_vec.len() < 4 {
272
308
  temp_items.insert(key.clone(), value.clone());
@@ -309,7 +345,7 @@ impl TickerData for Ticker {
309
345
  if temp_items.len() > 0 {
310
346
  for (key, value) in temp_items {
311
347
  let items: Vec<Object> = serde_json::from_value(value.to_string().parse()?)
312
- .expect("Failed to deserialize into Object");
348
+ .map_err(|e| format!("Failed to deserialize into Object: {}", e))?;
313
349
  let mut vars_vec: Vec<f64> = vec![];
314
350
  for d in columns[0].iter(){
315
351
  let mut found = false;
@@ -328,7 +364,7 @@ impl TickerData for Ticker {
328
364
  columns.push(vars_series);
329
365
  }
330
366
  }
331
- let df = DataFrame::new(columns).unwrap();
367
+ let df = DataFrame::new(columns)?;
332
368
  Ok(df)
333
369
  }
334
370
 
@@ -42,17 +42,38 @@ pub mod prelude {
42
42
  mod tests {
43
43
  use crate::prelude::*;
44
44
  #[tokio::test]
45
- async fn get_quote() {
45
+ async fn ticker_performance() {
46
46
  let ticker = TickerBuilder::new().ticker("AAPL")
47
47
  .start_date("2023-01-01")
48
- .end_date("2023-02-01")
48
+ .end_date("2023-12-31")
49
49
  .interval(Interval::OneDay)
50
50
  .benchmark_symbol("^GSPC")
51
51
  .confidence_level(0.95)
52
52
  .risk_free_rate(0.02)
53
53
  .build();
54
54
 
55
- let quote = ticker.get_quote().await.unwrap();
56
- dbg!(quote);
55
+ let performance = ticker.performance_stats().await.unwrap();
56
+
57
+ dbg!(performance);
58
+ }
59
+
60
+ #[tokio::test]
61
+ async fn portfolio_performance() {
62
+ let ticker_symbols = Vec::from(["AAPL", "BRK-A", "NVDA", "MSFT", "BTC-USD"]);
63
+ let portfolio = PortfolioBuilder::new().ticker_symbols(ticker_symbols)
64
+ .benchmark_symbol("^GSPC")
65
+ .start_date("2023-01-01")
66
+ .end_date("2023-12-31")
67
+ .interval(Interval::OneDay)
68
+ .confidence_level(0.95)
69
+ .risk_free_rate(0.02)
70
+ .max_iterations(1000)
71
+ .objective_function(ObjectiveFunction::MaxSharpe)
72
+ .build().await.unwrap();
73
+
74
+ let performance = portfolio.performance_stats;
75
+
76
+ dbg!(performance);
57
77
  }
78
+
58
79
  }
@@ -105,11 +105,11 @@ impl PortfolioBuilder {
105
105
  /// #[tokio::main]
106
106
  /// async fn main() -> Result<(), Box<dyn Error>> {
107
107
  /// // Construct the Portfolio Object
108
- /// let ticker_symbols = Vec::from(["NVDA", "BRK-A", "AAPL", "ZN=F"]);
108
+ /// let ticker_symbols = Vec::from(["NVDA", "BRK-A", "AAPL", "MSFT", "BTC-USD"]);
109
109
  /// let portfolio = PortfolioBuilder::new().ticker_symbols(ticker_symbols)
110
110
  /// .benchmark_symbol("^GSPC")
111
- /// .start_date("2017-01-01")
112
- /// .end_date("2023-01-01")
111
+ /// .start_date("2023-01-01")
112
+ /// .end_date("2023-12-31")
113
113
  /// .interval(Interval::OneDay)
114
114
  /// .confidence_level(0.95)
115
115
  /// .risk_free_rate(0.02)
@@ -144,7 +144,7 @@ impl TickerBuilder {
144
144
  /// // Instantiate the Ticker Object
145
145
  /// let ticker = TickerBuilder::new().ticker("AAPL")
146
146
  /// .start_date("2023-01-01")
147
- /// .end_date("2023-02-01")
147
+ /// .end_date("2023-12-31")
148
148
  /// .interval(Interval::OneDay)
149
149
  /// .benchmark_symbol("^GSPC")
150
150
  /// .confidence_level(0.95)
@@ -30,7 +30,6 @@ pub struct Article {
30
30
  }
31
31
 
32
32
  pub async fn scrape_text(url: &str, title: &str) -> Result<Article, Box<dyn Error>> {
33
- let client = Client::new();
34
33
  let response = REQUEST_CLIENT.get(url).send().await?;
35
34
  let body = response.text().await?;
36
35
 
@@ -43,7 +42,7 @@ pub async fn scrape_text(url: &str, title: &str) -> Result<Article, Box<dyn Erro
43
42
 
44
43
  for node in document.find(Name("a")) {
45
44
  link.push_str(&node.text());
46
- let response = client.get(&node.text()).send().await?;
45
+ let response = REQUEST_CLIENT.get(&node.text()).send().await?;
47
46
  let body = response.text().await?;
48
47
  let document = Document::from_read(body.as_bytes()).unwrap();
49
48
  for node in document.find(Name("p")) {
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "finalytics-py"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -6,32 +6,23 @@
6
6
  # -- Project information -----------------------------------------------------
7
7
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8
8
 
9
-
10
9
  project = 'Finalytics'
11
10
  copyright = '2023, Nnamdi Olisaeloka'
12
11
  author = 'Nnamdi Olisaeloka'
13
- release = '0.3.1'
14
-
12
+ release = '0.4.0'
15
13
 
16
- extensions = [
17
- 'sphinx.ext.autodoc',
18
- 'sphinx.ext.autosummary',
19
- 'sphinx.ext.intersphinx',
20
- 'sphinx.ext.todo',
21
- 'sphinx.ext.inheritance_diagram',
22
- 'sphinx.ext.autosectionlabel',
23
- 'sphinx.ext.napoleon',
24
- 'sphinx_rtd_theme',
25
- ]
14
+ # -- General configuration ---------------------------------------------------
15
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
26
16
 
27
- autosummary_generate = True
28
- autosummary_imported_members = True
17
+ extensions = []
29
18
 
30
19
  templates_path = ['_templates']
31
- exclude_patterns = []
20
+ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
21
+
22
+
32
23
 
33
24
  # -- Options for HTML output -------------------------------------------------
34
25
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
35
26
 
36
- html_theme = 'sphinx_rtd_theme'
37
- html_static_path = ['_static']
27
+ html_theme = 'alabaster'
28
+ html_static_path = ['_static']
@@ -0,0 +1,198 @@
1
+ Welcome to Finalytics Documentation
2
+ ====================================
3
+
4
+ Ticker Module
5
+ -------------
6
+
7
+ .. py:class:: Ticker
8
+
9
+ A class representing a Ticker object.
10
+
11
+ .. py:method:: __init__(symbol: str, start_date: Optional[str], end_date: Optional[str], interval: Optional[str], benchmark_symbol: Optional[str], confidence_level: Optional[float], risk_free_rate: Optional[float]) -> Ticker
12
+
13
+ Create a new Ticker object.
14
+
15
+ :param symbol: The ticker symbol of the asset.
16
+ :type symbol: str
17
+ :param start_date: Optional start date for historical data, defaults to None.
18
+ :type start_date: Optional[str]
19
+ :param end_date: Optional end date for historical data, defaults to None.
20
+ :type end_date: Optional[str]
21
+ :param interval: Optional data interval (2m, 5m, 15m, 30m, 1h, 1d, 1wk, 1mo, 3mo), defaults to None.
22
+ :type interval: Optional[str]
23
+ :param benchmark_symbol: Optional benchmark symbol, defaults to None.
24
+ :type benchmark_symbol: Optional[str]
25
+ :param confidence_level: Optional confidence level for statistics, defaults to None.
26
+ :type confidence_level: Optional[float]
27
+ :param risk_free_rate: Optional risk-free rate for calculations, defaults to None.
28
+ :type risk_free_rate: Optional[float]
29
+ :return: A Ticker object.
30
+ :rtype: Ticker
31
+
32
+ .. py:method:: get_quote() -> dict
33
+
34
+ Get the current ticker quote stats.
35
+
36
+ :return: Dictionary containing current ticker quote stats.
37
+ :rtype: dict
38
+
39
+ .. py:method:: get_summary_stats() -> dict
40
+
41
+ Get summary technical and fundamental statistics for the ticker.
42
+
43
+ :return: Dictionary containing summary statistics.
44
+ :rtype: dict
45
+
46
+ .. py:method:: get_price_history() -> DataFrame
47
+
48
+ Get the OHLCV data for the ticker for a given time period.
49
+
50
+ :return: Polars DataFrame containing OHLCV data.
51
+ :rtype: DataFrame
52
+
53
+ .. py:method:: get_options_chain() -> DataFrame
54
+
55
+ Get the options chain for the ticker.
56
+
57
+ :return: Polars DataFrame containing the options chain.
58
+ :rtype: DataFrame
59
+
60
+ .. py:method:: get_news(compute_sentiment: bool) -> dict
61
+
62
+ Get the latest news for the given ticker.
63
+
64
+ :param compute_sentiment: Whether to compute sentiment of news articles.
65
+ :type compute_sentiment: bool
66
+ :return: Dictionary containing news articles and sentiment results if requested.
67
+ :rtype: dict
68
+
69
+ .. py:method:: get_income_statement() -> DataFrame
70
+
71
+ Get the Income Statement for the ticker.
72
+
73
+ :return: Polars DataFrame containing the Income Statement.
74
+ :rtype: DataFrame
75
+
76
+ .. py:method:: get_balance_sheet() -> DataFrame
77
+
78
+ Get the Balance Sheet for the ticker.
79
+
80
+ :return: Polars DataFrame containing the Balance Sheet.
81
+ :rtype: DataFrame
82
+
83
+ .. py:method:: get_cashflow_statement() -> DataFrame
84
+
85
+ Get the Cashflow Statement for the ticker.
86
+
87
+ :return: Polars DataFrame containing the Cashflow Statement.
88
+ :rtype: DataFrame
89
+
90
+ .. py:method:: get_financial_ratios() -> DataFrame
91
+
92
+ Get the Financial Ratios for the ticker.
93
+
94
+ :return: Polars DataFrame containing the Financial Ratios.
95
+ :rtype: DataFrame
96
+
97
+ .. py:method:: volatility_surface() -> DataFrame
98
+
99
+ Get the implied volatility surface for the ticker options chain.
100
+
101
+ :return: Polars DataFrame containing the implied volatility surface.
102
+ :rtype: DataFrame
103
+
104
+ .. py:method:: performance_stats() -> dict
105
+
106
+ Compute the performance statistics for the ticker.
107
+
108
+ :return: Dictionary containing performance statistics.
109
+ :rtype: dict
110
+
111
+ .. py:method:: performance_chart(height: Optional[int], width: Optional[int]) -> Plot
112
+
113
+ Display the performance chart for the ticker.
114
+
115
+ :param height: Optional height of the plot in pixels, defaults to None.
116
+ :type height: Optional[int]
117
+ :param width: Optional width of the plot in pixels, defaults to None.
118
+ :type width: Optional[int]
119
+ :return: Plot object containing the performance chart.
120
+ :rtype: Plot
121
+
122
+ .. py:method:: candlestick_chart(height: Optional[int], width: Optional[int]) -> Plot
123
+
124
+ Display the candlestick chart for the ticker.
125
+
126
+ :param height: Optional height of the plot in pixels, defaults to None.
127
+ :type height: Optional[int]
128
+ :param width: Optional width of the plot in pixels, defaults to None.
129
+ :type width: Optional[int]
130
+ :return: Plot object containing the candlestick chart.
131
+ :rtype: Plot
132
+
133
+ .. py:method:: options_chart(chart_type: str, height: Optional[int], width: Optional[int]) -> Plot
134
+
135
+ Display the options volatility surface, smile and term structure charts for the ticker.
136
+
137
+ :param chart_type: Type of options chart (volatility_surface, smile, term_structure).
138
+ :type chart_type: str
139
+ :param height: Optional height of the plot in pixels, defaults to None.
140
+ :type height: Optional[int]
141
+ :param width: Optional width of the plot in pixels, defaults to None.
142
+ :type width: Optional[int]
143
+ :return: Plot object containing the options chart.
144
+ :rtype: Plot
145
+
146
+
147
+ Portfolio Module
148
+ ----------------
149
+
150
+ .. py:class:: Portfolio
151
+
152
+ A class representing a Portfolio object.
153
+
154
+ .. py:method:: __init__(symbols: List[str], benchmark_symbol: str, start_date: str, end_date: str, interval: str, confidence_level: float, risk_free_rate: float, max_iterations: int, objective_function: str) -> Portfolio
155
+
156
+ Create a new Portfolio object.
157
+
158
+ :param symbols: List of ticker symbols for the assets in the portfolio.
159
+ :type symbols: List[str]
160
+ :param benchmark_symbol: The ticker symbol of the benchmark to compare against.
161
+ :type benchmark_symbol: str
162
+ :param start_date: The start date of the time period in the format YYYY-MM-DD.
163
+ :type start_date: str
164
+ :param end_date: The end date of the time period in the format YYYY-MM-DD.
165
+ :type end_date: str
166
+ :param interval: The interval of the data (2m, 5m, 15m, 30m, 1h, 1d, 1wk, 1mo, 3mo).
167
+ :type interval: str
168
+ :param confidence_level: The confidence level for the VaR and ES calculations.
169
+ :type confidence_level: float
170
+ :param risk_free_rate: The risk free rate to use in the calculations.
171
+ :type risk_free_rate: float
172
+ :param max_iterations: The maximum number of iterations to use in the optimization.
173
+ :type max_iterations: int
174
+ :param objective_function: The objective function to use in the optimization (max_sharpe, min_vol, max_return, min_var, min_cvar, min_drawdown).
175
+ :type objective_function: str
176
+ :return: A Portfolio object.
177
+ :rtype: Portfolio
178
+
179
+ .. py:method:: portfolio_chart(chart_type: str, height: Optional[int], width: Optional[int]) -> Plot
180
+
181
+ Display the Portfolio Optimization Chart.
182
+
183
+ :param chart_type: Type of portfolio chart (optimization, performance, asset_returns).
184
+ :type chart_type: str
185
+ :param height: Optional height of the plot in pixels, defaults to 800.
186
+ :type height: Optional[int]
187
+ :param width: Optional width of the plot in pixels, defaults to 1200.
188
+ :type width: Optional[int]
189
+ :return: Plot object containing the portfolio chart.
190
+ :rtype: Plot
191
+
192
+
193
+ Indices and tables
194
+ ==================
195
+
196
+ * :ref:`genindex`
197
+ * :ref:`modindex`
198
+ * :ref:`search`
@@ -1,6 +0,0 @@
1
- Welcome to Finalytics Documentation
2
- ====================================
3
-
4
- .. automodule:: extension
5
- :members:
6
- :undoc-members:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes