agent-finance-cli 0.1.0
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.
- package/Cargo.lock +2632 -0
- package/Cargo.toml +31 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +119 -0
- package/bin/agent-finance.js +27 -0
- package/npm/check-binary-links.js +50 -0
- package/npm/check-package.js +39 -0
- package/npm/create-platform-package.js +90 -0
- package/npm/platform.js +33 -0
- package/npm/postinstall.js +62 -0
- package/npm/resolve-binary.js +38 -0
- package/package.json +54 -0
- package/skills/core-full.md +74 -0
- package/skills/core.md +59 -0
- package/skills/futures.md +18 -0
- package/skills/history-indicators.md +42 -0
- package/skills/price.md +40 -0
- package/skills/providers.md +25 -0
- package/skills/research-data.md +34 -0
- package/src/app.rs +642 -0
- package/src/cache.rs +67 -0
- package/src/cli.rs +651 -0
- package/src/history.rs +150 -0
- package/src/http.rs +76 -0
- package/src/indicators.rs +82 -0
- package/src/lib.rs +15 -0
- package/src/main.rs +4 -0
- package/src/model.rs +347 -0
- package/src/output.rs +544 -0
- package/src/page_read.rs +443 -0
- package/src/price.rs +255 -0
- package/src/providers/binance_futures.rs +342 -0
- package/src/providers/capabilities.rs +322 -0
- package/src/providers/cnbc.rs +302 -0
- package/src/providers/mod.rs +117 -0
- package/src/providers/robinhood.rs +580 -0
- package/src/providers/sec_edgar.rs +399 -0
- package/src/providers/stooq/catalog.rs +159 -0
- package/src/providers/stooq.rs +904 -0
- package/src/providers/yahoo.rs +836 -0
- package/src/research/fetchers.rs +111 -0
- package/src/research/highlights.rs +345 -0
- package/src/research/mod.rs +943 -0
- package/src/research/tests.rs +42 -0
- package/src/skills.rs +58 -0
- package/src/stream.rs +356 -0
- package/src/time.rs +21 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use serde_json::Value;
|
|
3
|
+
use wreq::Client;
|
|
4
|
+
|
|
5
|
+
use super::QuoteSummaryKind;
|
|
6
|
+
use crate::cache;
|
|
7
|
+
use crate::http::utc_now;
|
|
8
|
+
use crate::providers::{cnbc, robinhood, sec_edgar, yahoo};
|
|
9
|
+
|
|
10
|
+
pub(super) async fn fetch_quote_summary_live(
|
|
11
|
+
client: &Client,
|
|
12
|
+
symbol: &str,
|
|
13
|
+
modules: &[&str],
|
|
14
|
+
key: &str,
|
|
15
|
+
) -> Result<(String, String, Value)> {
|
|
16
|
+
let payload = yahoo::fetch_quote_summary(client, symbol, modules).await?;
|
|
17
|
+
let fetched_at_utc = utc_now();
|
|
18
|
+
cache::write_json("yahoo-quote-summary", key, &fetched_at_utc, &payload)?;
|
|
19
|
+
Ok((fetched_at_utc, "live".to_string(), payload))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub(super) async fn fetch_options_live(
|
|
23
|
+
client: &Client,
|
|
24
|
+
symbol: &str,
|
|
25
|
+
expiry: Option<i64>,
|
|
26
|
+
key: &str,
|
|
27
|
+
) -> Result<(String, String, Value)> {
|
|
28
|
+
let payload = yahoo::fetch_options(client, symbol, expiry).await?;
|
|
29
|
+
let fetched_at_utc = utc_now();
|
|
30
|
+
cache::write_json("yahoo-options", key, &fetched_at_utc, &payload)?;
|
|
31
|
+
Ok((fetched_at_utc, "live".to_string(), payload))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub(super) async fn fetch_search_live(
|
|
35
|
+
client: &Client,
|
|
36
|
+
query: &str,
|
|
37
|
+
quotes_count: usize,
|
|
38
|
+
news_count: usize,
|
|
39
|
+
key: &str,
|
|
40
|
+
) -> Result<(String, String, Value)> {
|
|
41
|
+
let payload = yahoo::fetch_search(client, query, quotes_count, news_count).await?;
|
|
42
|
+
let fetched_at_utc = utc_now();
|
|
43
|
+
cache::write_json("yahoo-search", key, &fetched_at_utc, &payload)?;
|
|
44
|
+
Ok((fetched_at_utc, "live".to_string(), payload))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub(super) async fn fetch_screen_live(
|
|
48
|
+
client: &Client,
|
|
49
|
+
screener: &str,
|
|
50
|
+
count: usize,
|
|
51
|
+
key: &str,
|
|
52
|
+
) -> Result<(String, String, Value)> {
|
|
53
|
+
let payload = yahoo::fetch_screen(client, screener, count).await?;
|
|
54
|
+
let fetched_at_utc = utc_now();
|
|
55
|
+
cache::write_json("yahoo-screen", key, &fetched_at_utc, &payload)?;
|
|
56
|
+
Ok((fetched_at_utc, "live".to_string(), payload))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub(super) async fn fetch_sec_company_live(
|
|
60
|
+
client: &Client,
|
|
61
|
+
symbol: &str,
|
|
62
|
+
include_companyfacts: bool,
|
|
63
|
+
key: &str,
|
|
64
|
+
) -> Result<(String, String, Value)> {
|
|
65
|
+
let payload = sec_edgar::fetch_company_bundle(client, symbol, include_companyfacts).await?;
|
|
66
|
+
let fetched_at_utc = utc_now();
|
|
67
|
+
cache::write_json("sec-edgar-company", key, &fetched_at_utc, &payload)?;
|
|
68
|
+
Ok((fetched_at_utc, "live".to_string(), payload))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub(super) async fn fetch_robinhood_live(
|
|
72
|
+
client: &Client,
|
|
73
|
+
symbol: &str,
|
|
74
|
+
kind: QuoteSummaryKind,
|
|
75
|
+
key: &str,
|
|
76
|
+
) -> Result<(String, String, Value)> {
|
|
77
|
+
let payload = match kind {
|
|
78
|
+
QuoteSummaryKind::Fundamentals => {
|
|
79
|
+
robinhood::fetch_fundamentals_bundle(client, symbol).await?
|
|
80
|
+
}
|
|
81
|
+
QuoteSummaryKind::Events => robinhood::fetch_events_bundle(client, symbol).await?,
|
|
82
|
+
QuoteSummaryKind::Analysis | QuoteSummaryKind::Ownership => unreachable!(),
|
|
83
|
+
};
|
|
84
|
+
let fetched_at_utc = utc_now();
|
|
85
|
+
cache::write_json("robinhood-research", key, &fetched_at_utc, &payload)?;
|
|
86
|
+
Ok((fetched_at_utc, "live".to_string(), payload))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pub(super) async fn fetch_cnbc_live(
|
|
90
|
+
client: &Client,
|
|
91
|
+
symbol: &str,
|
|
92
|
+
key: &str,
|
|
93
|
+
) -> Result<(String, String, Value)> {
|
|
94
|
+
let payload = cnbc::fetch_quote_payload(client, symbol).await?;
|
|
95
|
+
let fetched_at_utc = utc_now();
|
|
96
|
+
cache::write_json("cnbc-fundamentals-lite", key, &fetched_at_utc, &payload)?;
|
|
97
|
+
Ok((fetched_at_utc, "live".to_string(), payload))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub(super) async fn fetch_robinhood_options_live(
|
|
101
|
+
client: &Client,
|
|
102
|
+
symbol: &str,
|
|
103
|
+
expiration_date: Option<&str>,
|
|
104
|
+
count: usize,
|
|
105
|
+
key: &str,
|
|
106
|
+
) -> Result<(String, String, Value)> {
|
|
107
|
+
let payload = robinhood::fetch_options_bundle(client, symbol, expiration_date, count).await?;
|
|
108
|
+
let fetched_at_utc = utc_now();
|
|
109
|
+
cache::write_json("robinhood-options", key, &fetched_at_utc, &payload)?;
|
|
110
|
+
Ok((fetched_at_utc, "live".to_string(), payload))
|
|
111
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
use serde_json::Value;
|
|
2
|
+
|
|
3
|
+
use crate::cli::ResearchProvider;
|
|
4
|
+
use crate::model::{ResearchHighlight, research_value_string};
|
|
5
|
+
|
|
6
|
+
pub(super) fn quote_summary_root(payload: &Value) -> Option<&Value> {
|
|
7
|
+
payload
|
|
8
|
+
.pointer("/quoteSummary/result/0")
|
|
9
|
+
.or_else(|| payload.pointer("/finance/result/0"))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
pub(super) fn fundamentals_highlights(root: Option<&Value>) -> Vec<ResearchHighlight> {
|
|
13
|
+
let mut rows = Vec::new();
|
|
14
|
+
push_path(&mut rows, root, "Company", "/price/longName");
|
|
15
|
+
push_path(&mut rows, root, "Exchange", "/price/exchangeName");
|
|
16
|
+
push_path(&mut rows, root, "Industry", "/summaryProfile/industry");
|
|
17
|
+
push_path(&mut rows, root, "Market cap", "/price/marketCap");
|
|
18
|
+
push_path(
|
|
19
|
+
&mut rows,
|
|
20
|
+
root,
|
|
21
|
+
"EV",
|
|
22
|
+
"/defaultKeyStatistics/enterpriseValue",
|
|
23
|
+
);
|
|
24
|
+
push_path(&mut rows, root, "Trailing PE", "/summaryDetail/trailingPE");
|
|
25
|
+
push_path(&mut rows, root, "Forward PE", "/summaryDetail/forwardPE");
|
|
26
|
+
push_path(&mut rows, root, "P/B", "/defaultKeyStatistics/priceToBook");
|
|
27
|
+
push_path(&mut rows, root, "Revenue", "/financialData/totalRevenue");
|
|
28
|
+
push_path(
|
|
29
|
+
&mut rows,
|
|
30
|
+
root,
|
|
31
|
+
"Revenue growth",
|
|
32
|
+
"/financialData/revenueGrowth",
|
|
33
|
+
);
|
|
34
|
+
push_path(
|
|
35
|
+
&mut rows,
|
|
36
|
+
root,
|
|
37
|
+
"Gross margin",
|
|
38
|
+
"/financialData/grossMargins",
|
|
39
|
+
);
|
|
40
|
+
push_path(
|
|
41
|
+
&mut rows,
|
|
42
|
+
root,
|
|
43
|
+
"Operating margin",
|
|
44
|
+
"/financialData/operatingMargins",
|
|
45
|
+
);
|
|
46
|
+
push_path(
|
|
47
|
+
&mut rows,
|
|
48
|
+
root,
|
|
49
|
+
"Free cash flow",
|
|
50
|
+
"/financialData/freeCashflow",
|
|
51
|
+
);
|
|
52
|
+
push_path(&mut rows, root, "Cash", "/financialData/totalCash");
|
|
53
|
+
push_path(&mut rows, root, "Debt", "/financialData/totalDebt");
|
|
54
|
+
rows
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub(super) fn analysis_highlights(root: Option<&Value>) -> Vec<ResearchHighlight> {
|
|
58
|
+
let mut rows = Vec::new();
|
|
59
|
+
push_path(
|
|
60
|
+
&mut rows,
|
|
61
|
+
root,
|
|
62
|
+
"Target mean price",
|
|
63
|
+
"/financialData/targetMeanPrice",
|
|
64
|
+
);
|
|
65
|
+
push_path(
|
|
66
|
+
&mut rows,
|
|
67
|
+
root,
|
|
68
|
+
"Target median price",
|
|
69
|
+
"/financialData/targetMedianPrice",
|
|
70
|
+
);
|
|
71
|
+
push_path(
|
|
72
|
+
&mut rows,
|
|
73
|
+
root,
|
|
74
|
+
"Target high price",
|
|
75
|
+
"/financialData/targetHighPrice",
|
|
76
|
+
);
|
|
77
|
+
push_path(
|
|
78
|
+
&mut rows,
|
|
79
|
+
root,
|
|
80
|
+
"Target low price",
|
|
81
|
+
"/financialData/targetLowPrice",
|
|
82
|
+
);
|
|
83
|
+
push_path(
|
|
84
|
+
&mut rows,
|
|
85
|
+
root,
|
|
86
|
+
"Recommendation key",
|
|
87
|
+
"/financialData/recommendationKey",
|
|
88
|
+
);
|
|
89
|
+
push_path(
|
|
90
|
+
&mut rows,
|
|
91
|
+
root,
|
|
92
|
+
"Analyst count",
|
|
93
|
+
"/financialData/numberOfAnalystOpinions",
|
|
94
|
+
);
|
|
95
|
+
push_path(
|
|
96
|
+
&mut rows,
|
|
97
|
+
root,
|
|
98
|
+
"Recommendation trend period",
|
|
99
|
+
"/recommendationTrend/trend/0/period",
|
|
100
|
+
);
|
|
101
|
+
push_path(
|
|
102
|
+
&mut rows,
|
|
103
|
+
root,
|
|
104
|
+
"Strong Buy",
|
|
105
|
+
"/recommendationTrend/trend/0/strongBuy",
|
|
106
|
+
);
|
|
107
|
+
push_path(&mut rows, root, "Buy", "/recommendationTrend/trend/0/buy");
|
|
108
|
+
push_path(&mut rows, root, "Hold", "/recommendationTrend/trend/0/hold");
|
|
109
|
+
push_path(
|
|
110
|
+
&mut rows,
|
|
111
|
+
root,
|
|
112
|
+
"EPS trend period",
|
|
113
|
+
"/earningsTrend/trend/0/period",
|
|
114
|
+
);
|
|
115
|
+
push_path(
|
|
116
|
+
&mut rows,
|
|
117
|
+
root,
|
|
118
|
+
"EPS estimate",
|
|
119
|
+
"/earningsTrend/trend/0/earningsEstimate/avg",
|
|
120
|
+
);
|
|
121
|
+
push_path(
|
|
122
|
+
&mut rows,
|
|
123
|
+
root,
|
|
124
|
+
"Revenue estimate",
|
|
125
|
+
"/earningsTrend/trend/0/revenueEstimate/avg",
|
|
126
|
+
);
|
|
127
|
+
rows
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
pub(super) fn ownership_highlights(root: Option<&Value>) -> Vec<ResearchHighlight> {
|
|
131
|
+
let mut rows = Vec::new();
|
|
132
|
+
push_path(
|
|
133
|
+
&mut rows,
|
|
134
|
+
root,
|
|
135
|
+
"Insider ownership",
|
|
136
|
+
"/majorHoldersBreakdown/insidersPercentHeld",
|
|
137
|
+
);
|
|
138
|
+
push_path(
|
|
139
|
+
&mut rows,
|
|
140
|
+
root,
|
|
141
|
+
"Institution ownership",
|
|
142
|
+
"/majorHoldersBreakdown/institutionsPercentHeld",
|
|
143
|
+
);
|
|
144
|
+
push_path(
|
|
145
|
+
&mut rows,
|
|
146
|
+
root,
|
|
147
|
+
"Institution float ownership",
|
|
148
|
+
"/majorHoldersBreakdown/institutionsFloatPercentHeld",
|
|
149
|
+
);
|
|
150
|
+
push_path(
|
|
151
|
+
&mut rows,
|
|
152
|
+
root,
|
|
153
|
+
"Institution count",
|
|
154
|
+
"/majorHoldersBreakdown/institutionsCount",
|
|
155
|
+
);
|
|
156
|
+
push_path(
|
|
157
|
+
&mut rows,
|
|
158
|
+
root,
|
|
159
|
+
"Insider transaction period",
|
|
160
|
+
"/netSharePurchaseActivity/period",
|
|
161
|
+
);
|
|
162
|
+
push_path(
|
|
163
|
+
&mut rows,
|
|
164
|
+
root,
|
|
165
|
+
"Insider net purchase",
|
|
166
|
+
"/netSharePurchaseActivity/netPercentInsiderShares",
|
|
167
|
+
);
|
|
168
|
+
rows
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
pub(super) fn events_highlights(root: Option<&Value>) -> Vec<ResearchHighlight> {
|
|
172
|
+
let mut rows = Vec::new();
|
|
173
|
+
push_path(
|
|
174
|
+
&mut rows,
|
|
175
|
+
root,
|
|
176
|
+
"Next earnings",
|
|
177
|
+
"/calendarEvents/earnings/earningsDate/0",
|
|
178
|
+
);
|
|
179
|
+
push_path(
|
|
180
|
+
&mut rows,
|
|
181
|
+
root,
|
|
182
|
+
"EPS mean",
|
|
183
|
+
"/calendarEvents/earnings/earningsAverage",
|
|
184
|
+
);
|
|
185
|
+
push_path(
|
|
186
|
+
&mut rows,
|
|
187
|
+
root,
|
|
188
|
+
"Revenue mean",
|
|
189
|
+
"/calendarEvents/earnings/revenueAverage",
|
|
190
|
+
);
|
|
191
|
+
push_path(
|
|
192
|
+
&mut rows,
|
|
193
|
+
root,
|
|
194
|
+
"Ex-dividend date",
|
|
195
|
+
"/summaryDetail/exDividendDate",
|
|
196
|
+
);
|
|
197
|
+
push_path(
|
|
198
|
+
&mut rows,
|
|
199
|
+
root,
|
|
200
|
+
"Dividend yield",
|
|
201
|
+
"/summaryDetail/dividendYield",
|
|
202
|
+
);
|
|
203
|
+
push_path(&mut rows, root, "SEC filing", "/secFilings/filings/0/type");
|
|
204
|
+
push_path(&mut rows, root, "SEC date", "/secFilings/filings/0/date");
|
|
205
|
+
push_path(
|
|
206
|
+
&mut rows,
|
|
207
|
+
root,
|
|
208
|
+
"SEC link",
|
|
209
|
+
"/secFilings/filings/0/edgarUrl",
|
|
210
|
+
);
|
|
211
|
+
rows
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
pub(super) fn options_highlights(payload: &Value) -> Vec<ResearchHighlight> {
|
|
215
|
+
let result = payload.pointer("/optionChain/result/0");
|
|
216
|
+
let mut rows = Vec::new();
|
|
217
|
+
push_yahoo_path(
|
|
218
|
+
&mut rows,
|
|
219
|
+
result,
|
|
220
|
+
"Underlying price",
|
|
221
|
+
"/quote/regularMarketPrice",
|
|
222
|
+
"options",
|
|
223
|
+
);
|
|
224
|
+
push_yahoo_path(
|
|
225
|
+
&mut rows,
|
|
226
|
+
result,
|
|
227
|
+
"Available expiries",
|
|
228
|
+
"/expirationDates",
|
|
229
|
+
"options",
|
|
230
|
+
);
|
|
231
|
+
if let Some(option) = result.and_then(|root| root.pointer("/options/0")) {
|
|
232
|
+
rows.push(ResearchHighlight::new(
|
|
233
|
+
"Call contracts",
|
|
234
|
+
option
|
|
235
|
+
.pointer("/calls")
|
|
236
|
+
.and_then(Value::as_array)
|
|
237
|
+
.map(|values| values.len().to_string())
|
|
238
|
+
.unwrap_or_else(|| "-".to_string()),
|
|
239
|
+
ResearchProvider::Yahoo.label(),
|
|
240
|
+
"options",
|
|
241
|
+
));
|
|
242
|
+
rows.push(ResearchHighlight::new(
|
|
243
|
+
"Put contracts",
|
|
244
|
+
option
|
|
245
|
+
.pointer("/puts")
|
|
246
|
+
.and_then(Value::as_array)
|
|
247
|
+
.map(|values| values.len().to_string())
|
|
248
|
+
.unwrap_or_else(|| "-".to_string()),
|
|
249
|
+
ResearchProvider::Yahoo.label(),
|
|
250
|
+
"options",
|
|
251
|
+
));
|
|
252
|
+
}
|
|
253
|
+
rows
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
pub(super) fn search_highlights(payload: &Value) -> Vec<ResearchHighlight> {
|
|
257
|
+
let mut rows = Vec::new();
|
|
258
|
+
if let Some(quotes) = payload.pointer("/quotes").and_then(Value::as_array) {
|
|
259
|
+
for quote in quotes.iter().take(8) {
|
|
260
|
+
let symbol =
|
|
261
|
+
research_value_string(quote.pointer("/symbol")).unwrap_or_else(|| "-".to_string());
|
|
262
|
+
let name = research_value_string(quote.pointer("/shortname"))
|
|
263
|
+
.or_else(|| research_value_string(quote.pointer("/longname")))
|
|
264
|
+
.unwrap_or_else(|| "-".to_string());
|
|
265
|
+
rows.push(ResearchHighlight::new(
|
|
266
|
+
&format!("Ticker {symbol}"),
|
|
267
|
+
name,
|
|
268
|
+
ResearchProvider::Yahoo.label(),
|
|
269
|
+
"search",
|
|
270
|
+
));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if let Some(news) = payload.pointer("/news").and_then(Value::as_array) {
|
|
274
|
+
for item in news.iter().take(5) {
|
|
275
|
+
let title =
|
|
276
|
+
research_value_string(item.pointer("/title")).unwrap_or_else(|| "-".to_string());
|
|
277
|
+
let publisher = research_value_string(item.pointer("/publisher"))
|
|
278
|
+
.unwrap_or_else(|| "-".to_string());
|
|
279
|
+
rows.push(ResearchHighlight::new(
|
|
280
|
+
&format!("News {publisher}"),
|
|
281
|
+
title,
|
|
282
|
+
ResearchProvider::Yahoo.label(),
|
|
283
|
+
"news",
|
|
284
|
+
));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
rows
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
pub(super) fn screen_highlights(payload: &Value) -> Vec<ResearchHighlight> {
|
|
291
|
+
let quotes = payload
|
|
292
|
+
.pointer("/finance/result/0/quotes")
|
|
293
|
+
.and_then(Value::as_array)
|
|
294
|
+
.or_else(|| payload.pointer("/quotes").and_then(Value::as_array));
|
|
295
|
+
let mut rows = Vec::new();
|
|
296
|
+
if let Some(quotes) = quotes {
|
|
297
|
+
for quote in quotes.iter().take(25) {
|
|
298
|
+
let symbol =
|
|
299
|
+
research_value_string(quote.pointer("/symbol")).unwrap_or_else(|| "-".to_string());
|
|
300
|
+
let name = research_value_string(quote.pointer("/shortName"))
|
|
301
|
+
.or_else(|| research_value_string(quote.pointer("/longName")))
|
|
302
|
+
.unwrap_or_else(|| "-".to_string());
|
|
303
|
+
let price = research_value_string(quote.pointer("/regularMarketPrice"))
|
|
304
|
+
.or_else(|| research_value_string(quote.pointer("/regularMarketPrice/fmt")))
|
|
305
|
+
.unwrap_or_else(|| "-".to_string());
|
|
306
|
+
rows.push(ResearchHighlight::new(
|
|
307
|
+
&symbol,
|
|
308
|
+
format!("{name} | {price}"),
|
|
309
|
+
ResearchProvider::Yahoo.label(),
|
|
310
|
+
"screen",
|
|
311
|
+
));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
rows
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
pub(super) fn push_path(
|
|
318
|
+
rows: &mut Vec<ResearchHighlight>,
|
|
319
|
+
root: Option<&Value>,
|
|
320
|
+
label: &str,
|
|
321
|
+
path: &str,
|
|
322
|
+
) {
|
|
323
|
+
push_yahoo_path(rows, root, label, path, "quoteSummary");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
pub(super) fn push_yahoo_path(
|
|
327
|
+
rows: &mut Vec<ResearchHighlight>,
|
|
328
|
+
root: Option<&Value>,
|
|
329
|
+
label: &str,
|
|
330
|
+
path: &str,
|
|
331
|
+
module: &str,
|
|
332
|
+
) {
|
|
333
|
+
let Some(root) = root else {
|
|
334
|
+
return;
|
|
335
|
+
};
|
|
336
|
+
if let Some(row) = ResearchHighlight::from_path(
|
|
337
|
+
Some(root),
|
|
338
|
+
label,
|
|
339
|
+
path,
|
|
340
|
+
ResearchProvider::Yahoo.label(),
|
|
341
|
+
module,
|
|
342
|
+
) {
|
|
343
|
+
rows.push(row);
|
|
344
|
+
}
|
|
345
|
+
}
|