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
package/src/history.rs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
use crate::cli::HistoryAdjustment;
|
|
2
|
+
use crate::model::OhlcBar;
|
|
3
|
+
|
|
4
|
+
pub fn apply_history_adjustment_and_repair(
|
|
5
|
+
bars: &mut [OhlcBar],
|
|
6
|
+
adjustment: HistoryAdjustment,
|
|
7
|
+
repair: bool,
|
|
8
|
+
) -> bool {
|
|
9
|
+
let repair_applied = if repair {
|
|
10
|
+
repair_obvious_100x_errors(bars)
|
|
11
|
+
} else {
|
|
12
|
+
false
|
|
13
|
+
};
|
|
14
|
+
apply_adjustment(bars, adjustment);
|
|
15
|
+
repair_applied
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fn apply_adjustment(bars: &mut [OhlcBar], adjustment: HistoryAdjustment) {
|
|
19
|
+
if adjustment == HistoryAdjustment::Raw {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
for bar in bars {
|
|
23
|
+
let Some(adj_close) = bar.adj_close else {
|
|
24
|
+
continue;
|
|
25
|
+
};
|
|
26
|
+
if bar.close == 0.0 {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
let ratio = adj_close / bar.close;
|
|
30
|
+
bar.open = bar.open.map(|value| value * ratio);
|
|
31
|
+
bar.high = bar.high.map(|value| value * ratio);
|
|
32
|
+
bar.low = bar.low.map(|value| value * ratio);
|
|
33
|
+
if adjustment == HistoryAdjustment::Auto {
|
|
34
|
+
bar.close = adj_close;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn repair_obvious_100x_errors(bars: &mut [OhlcBar]) -> bool {
|
|
40
|
+
let closes = bars.iter().map(|bar| bar.close).collect::<Vec<_>>();
|
|
41
|
+
let mut repaired = false;
|
|
42
|
+
for (index, bar) in bars.iter_mut().enumerate() {
|
|
43
|
+
let Some(reference) = neighbor_reference(&closes, index) else {
|
|
44
|
+
continue;
|
|
45
|
+
};
|
|
46
|
+
if reference <= 0.0 {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
let close = bar.close;
|
|
50
|
+
let factor = if within(close / 100.0, reference) {
|
|
51
|
+
Some(0.01)
|
|
52
|
+
} else if within(close * 100.0, reference) {
|
|
53
|
+
Some(100.0)
|
|
54
|
+
} else {
|
|
55
|
+
None
|
|
56
|
+
};
|
|
57
|
+
let Some(factor) = factor else {
|
|
58
|
+
continue;
|
|
59
|
+
};
|
|
60
|
+
scale_bar(bar, factor);
|
|
61
|
+
bar.repaired = true;
|
|
62
|
+
repaired = true;
|
|
63
|
+
}
|
|
64
|
+
repaired
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn neighbor_reference(closes: &[f64], index: usize) -> Option<f64> {
|
|
68
|
+
let previous = (index > 0)
|
|
69
|
+
.then(|| closes[index - 1])
|
|
70
|
+
.filter(|value| *value > 0.0);
|
|
71
|
+
let next = closes.get(index + 1).copied().filter(|value| *value > 0.0);
|
|
72
|
+
match (previous, next) {
|
|
73
|
+
(Some(previous), Some(next)) => Some((previous + next) / 2.0),
|
|
74
|
+
(Some(previous), None) => Some(previous),
|
|
75
|
+
(None, Some(next)) => Some(next),
|
|
76
|
+
(None, None) => None,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn within(value: f64, reference: f64) -> bool {
|
|
81
|
+
value >= reference * 0.8 && value <= reference * 1.2
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn scale_bar(bar: &mut OhlcBar, factor: f64) {
|
|
85
|
+
bar.open = bar.open.map(|value| value * factor);
|
|
86
|
+
bar.high = bar.high.map(|value| value * factor);
|
|
87
|
+
bar.low = bar.low.map(|value| value * factor);
|
|
88
|
+
bar.close *= factor;
|
|
89
|
+
bar.adj_close = bar.adj_close.map(|value| value * factor);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#[cfg(test)]
|
|
93
|
+
mod tests {
|
|
94
|
+
use super::*;
|
|
95
|
+
|
|
96
|
+
fn bar(open: f64, high: f64, low: f64, close: f64, adj_close: Option<f64>) -> OhlcBar {
|
|
97
|
+
OhlcBar {
|
|
98
|
+
symbol: "TST".to_string(),
|
|
99
|
+
provider: "fixture".to_string(),
|
|
100
|
+
open_time: "2026-01-01T00:00:00Z".to_string(),
|
|
101
|
+
close_time: None,
|
|
102
|
+
open: Some(open),
|
|
103
|
+
high: Some(high),
|
|
104
|
+
low: Some(low),
|
|
105
|
+
close,
|
|
106
|
+
adj_close,
|
|
107
|
+
volume: Some(100.0),
|
|
108
|
+
quote_volume: None,
|
|
109
|
+
trades: None,
|
|
110
|
+
dividend: None,
|
|
111
|
+
stock_split: None,
|
|
112
|
+
capital_gain: None,
|
|
113
|
+
repaired: false,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#[test]
|
|
118
|
+
fn auto_adjust_replaces_close_and_scales_ohl() {
|
|
119
|
+
let mut bars = vec![bar(100.0, 110.0, 90.0, 100.0, Some(50.0))];
|
|
120
|
+
let repaired =
|
|
121
|
+
apply_history_adjustment_and_repair(&mut bars, HistoryAdjustment::Auto, false);
|
|
122
|
+
assert!(!repaired);
|
|
123
|
+
assert_eq!(bars[0].open, Some(50.0));
|
|
124
|
+
assert_eq!(bars[0].high, Some(55.0));
|
|
125
|
+
assert_eq!(bars[0].low, Some(45.0));
|
|
126
|
+
assert_eq!(bars[0].close, 50.0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
fn back_adjust_scales_ohl_but_keeps_raw_close() {
|
|
131
|
+
let mut bars = vec![bar(100.0, 110.0, 90.0, 100.0, Some(50.0))];
|
|
132
|
+
apply_history_adjustment_and_repair(&mut bars, HistoryAdjustment::Back, false);
|
|
133
|
+
assert_eq!(bars[0].open, Some(50.0));
|
|
134
|
+
assert_eq!(bars[0].close, 100.0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[test]
|
|
138
|
+
fn repair_marks_and_scales_isolated_100x_error() {
|
|
139
|
+
let mut bars = vec![
|
|
140
|
+
bar(100.0, 102.0, 98.0, 100.0, Some(100.0)),
|
|
141
|
+
bar(10100.0, 10200.0, 9800.0, 10100.0, Some(10100.0)),
|
|
142
|
+
bar(102.0, 103.0, 99.0, 102.0, Some(102.0)),
|
|
143
|
+
];
|
|
144
|
+
let repaired = apply_history_adjustment_and_repair(&mut bars, HistoryAdjustment::Raw, true);
|
|
145
|
+
assert!(repaired);
|
|
146
|
+
assert!(bars[1].repaired);
|
|
147
|
+
assert_eq!(bars[1].close, 101.0);
|
|
148
|
+
assert_eq!(bars[1].high, Some(102.0));
|
|
149
|
+
}
|
|
150
|
+
}
|
package/src/http.rs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
use std::time::Duration;
|
|
2
|
+
|
|
3
|
+
use anyhow::{Context, Result};
|
|
4
|
+
use chrono::{SecondsFormat, TimeZone, Utc};
|
|
5
|
+
use wreq::{Client, Proxy};
|
|
6
|
+
use wreq_util::Emulation;
|
|
7
|
+
|
|
8
|
+
pub fn http_client(timeout_seconds: u64, proxy: Option<&str>, no_proxy: bool) -> Result<Client> {
|
|
9
|
+
let mut builder = Client::builder()
|
|
10
|
+
.emulation(Emulation::Chrome137)
|
|
11
|
+
.timeout(Duration::from_secs(timeout_seconds))
|
|
12
|
+
.cookie_store(true);
|
|
13
|
+
|
|
14
|
+
if let Some(proxy) = selected_proxy(proxy, no_proxy) {
|
|
15
|
+
builder = builder
|
|
16
|
+
.proxy(Proxy::all(&proxy).with_context(|| format!("invalid proxy URL: {proxy}"))?);
|
|
17
|
+
} else if no_proxy {
|
|
18
|
+
builder = builder.no_proxy();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
builder.build().context("failed to build HTTP client")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn selected_proxy(proxy: Option<&str>, no_proxy: bool) -> Option<String> {
|
|
25
|
+
if no_proxy {
|
|
26
|
+
return None;
|
|
27
|
+
}
|
|
28
|
+
proxy
|
|
29
|
+
.map(str::to_string)
|
|
30
|
+
.or_else(|| std::env::var("AGENT_FINANCE_PROXY").ok())
|
|
31
|
+
.or_else(|| std::env::var("ALL_PROXY").ok())
|
|
32
|
+
.or_else(|| std::env::var("HTTPS_PROXY").ok())
|
|
33
|
+
.or_else(|| std::env::var("HTTP_PROXY").ok())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn utc_now() -> String {
|
|
37
|
+
Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn timestamp_ms_to_utc(timestamp: i64) -> Option<String> {
|
|
41
|
+
Utc.timestamp_millis_opt(timestamp)
|
|
42
|
+
.single()
|
|
43
|
+
.map(|datetime| datetime.to_rfc3339_opts(SecondsFormat::Secs, true))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
pub fn timestamp_sec_to_utc(timestamp: i64) -> Option<String> {
|
|
47
|
+
Utc.timestamp_opt(timestamp, 0)
|
|
48
|
+
.single()
|
|
49
|
+
.map(|datetime| datetime.to_rfc3339_opts(SecondsFormat::Secs, true))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pub fn change_pct(price: f64, previous_close: Option<f64>) -> Option<f64> {
|
|
53
|
+
let previous_close = previous_close?;
|
|
54
|
+
if previous_close == 0.0 {
|
|
55
|
+
None
|
|
56
|
+
} else {
|
|
57
|
+
Some((price - previous_close) / previous_close * 100.0)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub fn parse_optional_f64(value: Option<&str>) -> Option<f64> {
|
|
62
|
+
let value = clean_text(value)?;
|
|
63
|
+
value.parse::<f64>().ok()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn parse_optional_u64(value: Option<&str>) -> Option<u64> {
|
|
67
|
+
let value = clean_text(value)?;
|
|
68
|
+
value.parse::<f64>().ok().map(|number| number as u64)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub fn clean_text(value: Option<&str>) -> Option<&str> {
|
|
72
|
+
match value.map(str::trim) {
|
|
73
|
+
Some("") | Some("N/D") | None => None,
|
|
74
|
+
Some(value) => Some(value),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
use crate::model::{DerivedIndicator, HistoryBatch, OhlcBar};
|
|
2
|
+
|
|
3
|
+
pub fn compute_indicator(history: &HistoryBatch) -> DerivedIndicator {
|
|
4
|
+
let bars = &history.bars;
|
|
5
|
+
DerivedIndicator {
|
|
6
|
+
symbol: history.symbol.clone(),
|
|
7
|
+
provider: history.provider.clone(),
|
|
8
|
+
latest_close: bars.last().map(|bar| bar.close),
|
|
9
|
+
latest_time: bars.last().map(|bar| bar.open_time.clone()),
|
|
10
|
+
return_1_bar_pct: return_pct(bars, 1),
|
|
11
|
+
return_5_bar_pct: return_pct(bars, 5),
|
|
12
|
+
return_20_bar_pct: return_pct(bars, 20),
|
|
13
|
+
sma_20: sma(bars, 20),
|
|
14
|
+
sma_50: sma(bars, 50),
|
|
15
|
+
high_20: rolling_high(bars, 20),
|
|
16
|
+
low_20: rolling_low(bars, 20),
|
|
17
|
+
realized_vol_20_annualized_pct: realized_vol_annualized_pct(bars, 20),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn return_pct(bars: &[OhlcBar], lookback: usize) -> Option<f64> {
|
|
22
|
+
if bars.len() <= lookback {
|
|
23
|
+
return None;
|
|
24
|
+
}
|
|
25
|
+
let latest = bars.last()?.close;
|
|
26
|
+
let base = bars.get(bars.len() - 1 - lookback)?.close;
|
|
27
|
+
if base == 0.0 {
|
|
28
|
+
None
|
|
29
|
+
} else {
|
|
30
|
+
Some((latest - base) / base * 100.0)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn sma(bars: &[OhlcBar], lookback: usize) -> Option<f64> {
|
|
35
|
+
if bars.len() < lookback {
|
|
36
|
+
return None;
|
|
37
|
+
}
|
|
38
|
+
let values = bars.iter().rev().take(lookback).map(|bar| bar.close);
|
|
39
|
+
Some(values.sum::<f64>() / lookback as f64)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn rolling_high(bars: &[OhlcBar], lookback: usize) -> Option<f64> {
|
|
43
|
+
bars.iter()
|
|
44
|
+
.rev()
|
|
45
|
+
.take(lookback)
|
|
46
|
+
.filter_map(|bar| bar.high.or(Some(bar.close)))
|
|
47
|
+
.reduce(f64::max)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn rolling_low(bars: &[OhlcBar], lookback: usize) -> Option<f64> {
|
|
51
|
+
bars.iter()
|
|
52
|
+
.rev()
|
|
53
|
+
.take(lookback)
|
|
54
|
+
.filter_map(|bar| bar.low.or(Some(bar.close)))
|
|
55
|
+
.reduce(f64::min)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn realized_vol_annualized_pct(bars: &[OhlcBar], lookback: usize) -> Option<f64> {
|
|
59
|
+
if bars.len() <= lookback {
|
|
60
|
+
return None;
|
|
61
|
+
}
|
|
62
|
+
let returns = bars
|
|
63
|
+
.windows(2)
|
|
64
|
+
.rev()
|
|
65
|
+
.take(lookback)
|
|
66
|
+
.filter_map(|window| {
|
|
67
|
+
let previous = window.first()?.close;
|
|
68
|
+
let latest = window.last()?.close;
|
|
69
|
+
(previous > 0.0).then(|| (latest / previous).ln())
|
|
70
|
+
})
|
|
71
|
+
.collect::<Vec<_>>();
|
|
72
|
+
if returns.len() < 2 {
|
|
73
|
+
return None;
|
|
74
|
+
}
|
|
75
|
+
let mean = returns.iter().sum::<f64>() / returns.len() as f64;
|
|
76
|
+
let variance = returns
|
|
77
|
+
.iter()
|
|
78
|
+
.map(|value| (value - mean).powi(2))
|
|
79
|
+
.sum::<f64>()
|
|
80
|
+
/ (returns.len() - 1) as f64;
|
|
81
|
+
Some(variance.sqrt() * 252.0_f64.sqrt() * 100.0)
|
|
82
|
+
}
|
package/src/lib.rs
ADDED
package/src/main.rs
ADDED
package/src/model.rs
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
use std::collections::BTreeMap;
|
|
2
|
+
|
|
3
|
+
use serde::Serialize;
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
|
|
6
|
+
pub const SESSION_REGULAR: &str = "regular";
|
|
7
|
+
pub const SESSION_PRE: &str = "pre";
|
|
8
|
+
pub const SESSION_POST: &str = "post";
|
|
9
|
+
pub const SESSION_EXTENDED: &str = "extended";
|
|
10
|
+
pub const SESSION_OVERNIGHT: &str = "overnight";
|
|
11
|
+
pub const SESSION_24H_PROXY: &str = "24h_proxy";
|
|
12
|
+
|
|
13
|
+
#[derive(Debug, Clone, Serialize)]
|
|
14
|
+
pub struct Quote {
|
|
15
|
+
pub symbol: String,
|
|
16
|
+
pub price: f64,
|
|
17
|
+
pub currency: Option<String>,
|
|
18
|
+
pub provider: String,
|
|
19
|
+
pub session: Option<String>,
|
|
20
|
+
pub fetched_at_utc: String,
|
|
21
|
+
pub market_time: Option<String>,
|
|
22
|
+
pub previous_close: Option<f64>,
|
|
23
|
+
pub open: Option<f64>,
|
|
24
|
+
pub high: Option<f64>,
|
|
25
|
+
pub low: Option<f64>,
|
|
26
|
+
pub volume: Option<u64>,
|
|
27
|
+
pub exchange: Option<String>,
|
|
28
|
+
pub provider_symbol: Option<String>,
|
|
29
|
+
pub change_pct: Option<f64>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[derive(Debug, Clone, Serialize)]
|
|
33
|
+
pub struct OhlcBar {
|
|
34
|
+
pub symbol: String,
|
|
35
|
+
pub provider: String,
|
|
36
|
+
pub open_time: String,
|
|
37
|
+
pub close_time: Option<String>,
|
|
38
|
+
pub open: Option<f64>,
|
|
39
|
+
pub high: Option<f64>,
|
|
40
|
+
pub low: Option<f64>,
|
|
41
|
+
pub close: f64,
|
|
42
|
+
pub adj_close: Option<f64>,
|
|
43
|
+
pub volume: Option<f64>,
|
|
44
|
+
pub quote_volume: Option<f64>,
|
|
45
|
+
pub trades: Option<u64>,
|
|
46
|
+
pub dividend: Option<f64>,
|
|
47
|
+
pub stock_split: Option<f64>,
|
|
48
|
+
pub capital_gain: Option<f64>,
|
|
49
|
+
pub repaired: bool,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[derive(Debug, Serialize)]
|
|
53
|
+
pub struct HistoryBatch {
|
|
54
|
+
pub symbol: String,
|
|
55
|
+
pub provider: String,
|
|
56
|
+
pub interval: String,
|
|
57
|
+
pub adjustment: String,
|
|
58
|
+
pub actions_included: bool,
|
|
59
|
+
pub repair_requested: bool,
|
|
60
|
+
pub repair_applied: bool,
|
|
61
|
+
pub bars: Vec<OhlcBar>,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[derive(Debug, Serialize)]
|
|
65
|
+
pub struct DerivedIndicator {
|
|
66
|
+
pub symbol: String,
|
|
67
|
+
pub provider: String,
|
|
68
|
+
pub latest_close: Option<f64>,
|
|
69
|
+
pub latest_time: Option<String>,
|
|
70
|
+
pub return_1_bar_pct: Option<f64>,
|
|
71
|
+
pub return_5_bar_pct: Option<f64>,
|
|
72
|
+
pub return_20_bar_pct: Option<f64>,
|
|
73
|
+
pub sma_20: Option<f64>,
|
|
74
|
+
pub sma_50: Option<f64>,
|
|
75
|
+
pub high_20: Option<f64>,
|
|
76
|
+
pub low_20: Option<f64>,
|
|
77
|
+
pub realized_vol_20_annualized_pct: Option<f64>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[derive(Debug, Serialize)]
|
|
81
|
+
pub struct FuturesStats {
|
|
82
|
+
pub symbol: String,
|
|
83
|
+
pub provider: String,
|
|
84
|
+
pub fetched_at_utc: String,
|
|
85
|
+
pub ticker_24h: Option<FuturesTicker24h>,
|
|
86
|
+
pub mark_price: Option<FuturesMarkPrice>,
|
|
87
|
+
pub open_interest: Option<FuturesOpenInterest>,
|
|
88
|
+
pub funding_rates: Vec<FuturesFundingRate>,
|
|
89
|
+
pub errors: BTreeMap<String, String>,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#[derive(Debug, Serialize)]
|
|
93
|
+
pub struct FuturesTicker24h {
|
|
94
|
+
pub last_price: Option<f64>,
|
|
95
|
+
pub price_change: Option<f64>,
|
|
96
|
+
pub price_change_pct: Option<f64>,
|
|
97
|
+
pub weighted_avg_price: Option<f64>,
|
|
98
|
+
pub open_price: Option<f64>,
|
|
99
|
+
pub high_price: Option<f64>,
|
|
100
|
+
pub low_price: Option<f64>,
|
|
101
|
+
pub volume: Option<f64>,
|
|
102
|
+
pub quote_volume: Option<f64>,
|
|
103
|
+
pub count: Option<u64>,
|
|
104
|
+
pub open_time: Option<String>,
|
|
105
|
+
pub close_time: Option<String>,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[derive(Debug, Serialize)]
|
|
109
|
+
pub struct FuturesMarkPrice {
|
|
110
|
+
pub mark_price: Option<f64>,
|
|
111
|
+
pub index_price: Option<f64>,
|
|
112
|
+
pub estimated_settle_price: Option<f64>,
|
|
113
|
+
pub last_funding_rate: Option<f64>,
|
|
114
|
+
pub interest_rate: Option<f64>,
|
|
115
|
+
pub next_funding_time: Option<String>,
|
|
116
|
+
pub time: Option<String>,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[derive(Debug, Serialize)]
|
|
120
|
+
pub struct FuturesOpenInterest {
|
|
121
|
+
pub open_interest: Option<f64>,
|
|
122
|
+
pub time: Option<String>,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[derive(Debug, Serialize)]
|
|
126
|
+
pub struct FuturesFundingRate {
|
|
127
|
+
pub funding_rate: Option<f64>,
|
|
128
|
+
pub funding_time: Option<String>,
|
|
129
|
+
pub mark_price: Option<f64>,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#[derive(Debug, Clone, Serialize)]
|
|
133
|
+
pub struct PricePoint {
|
|
134
|
+
pub label: String,
|
|
135
|
+
pub symbol: String,
|
|
136
|
+
pub price: Option<f64>,
|
|
137
|
+
pub currency: Option<String>,
|
|
138
|
+
pub provider: String,
|
|
139
|
+
pub session: Option<String>,
|
|
140
|
+
pub market_time_utc: Option<String>,
|
|
141
|
+
pub market_time_local: Option<String>,
|
|
142
|
+
pub change_pct: Option<f64>,
|
|
143
|
+
pub previous_close: Option<f64>,
|
|
144
|
+
pub open: Option<f64>,
|
|
145
|
+
pub high: Option<f64>,
|
|
146
|
+
pub low: Option<f64>,
|
|
147
|
+
pub volume: Option<u64>,
|
|
148
|
+
pub exchange: Option<String>,
|
|
149
|
+
pub note: Option<String>,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[derive(Debug, Clone, Serialize)]
|
|
153
|
+
pub struct RegularBasis {
|
|
154
|
+
pub previous_close: Option<f64>,
|
|
155
|
+
pub open: Option<f64>,
|
|
156
|
+
pub high: Option<f64>,
|
|
157
|
+
pub low: Option<f64>,
|
|
158
|
+
pub volume: Option<u64>,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[derive(Debug, Serialize)]
|
|
162
|
+
pub struct PriceSummary {
|
|
163
|
+
pub symbol: String,
|
|
164
|
+
pub timezone: String,
|
|
165
|
+
pub fetched_at_utc: String,
|
|
166
|
+
pub fetched_at_local: String,
|
|
167
|
+
pub current: Option<PricePoint>,
|
|
168
|
+
pub regular_basis: RegularBasis,
|
|
169
|
+
pub sessions: Vec<PricePoint>,
|
|
170
|
+
pub proxy: Option<PricePoint>,
|
|
171
|
+
pub errors: BTreeMap<String, String>,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[derive(Debug, Serialize)]
|
|
175
|
+
pub struct ResearchReport {
|
|
176
|
+
pub symbol: String,
|
|
177
|
+
pub category: String,
|
|
178
|
+
pub fetched_at_utc: String,
|
|
179
|
+
pub fetched_at_local: String,
|
|
180
|
+
pub sources: Vec<ResearchSource>,
|
|
181
|
+
pub modules: Vec<ResearchModule>,
|
|
182
|
+
pub coverage_gaps: Vec<ResearchCoverageGap>,
|
|
183
|
+
pub highlights: Vec<ResearchHighlight>,
|
|
184
|
+
pub payload: Value,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#[derive(Debug, Serialize)]
|
|
188
|
+
pub struct ResearchSource {
|
|
189
|
+
pub provider: String,
|
|
190
|
+
pub cache_status: String,
|
|
191
|
+
pub fetched_at_utc: String,
|
|
192
|
+
pub fetched_at_local: String,
|
|
193
|
+
pub note: Option<String>,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[derive(Debug, Serialize)]
|
|
197
|
+
pub struct ResearchModule {
|
|
198
|
+
pub name: String,
|
|
199
|
+
pub provider: String,
|
|
200
|
+
pub status: String,
|
|
201
|
+
pub note: Option<String>,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[derive(Debug, Serialize)]
|
|
205
|
+
pub struct ResearchCoverageGap {
|
|
206
|
+
pub module: String,
|
|
207
|
+
pub reason: String,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#[derive(Debug, Serialize)]
|
|
211
|
+
pub struct ResearchHighlight {
|
|
212
|
+
pub label: String,
|
|
213
|
+
pub value: String,
|
|
214
|
+
pub provider: String,
|
|
215
|
+
pub module: String,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
impl ResearchHighlight {
|
|
219
|
+
pub fn new(label: &str, value: impl Into<String>, provider: &str, module: &str) -> Self {
|
|
220
|
+
Self {
|
|
221
|
+
label: label.to_string(),
|
|
222
|
+
value: value.into(),
|
|
223
|
+
provider: provider.to_string(),
|
|
224
|
+
module: module.to_string(),
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
pub fn from_path(
|
|
229
|
+
root: Option<&Value>,
|
|
230
|
+
label: &str,
|
|
231
|
+
path: &str,
|
|
232
|
+
provider: &str,
|
|
233
|
+
module: &str,
|
|
234
|
+
) -> Option<Self> {
|
|
235
|
+
let value = research_value_string(root?.pointer(path))?;
|
|
236
|
+
Some(Self::new(label, value, provider, module))
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
pub fn research_value_string(value: Option<&Value>) -> Option<String> {
|
|
241
|
+
match value? {
|
|
242
|
+
Value::Null => None,
|
|
243
|
+
Value::String(value) => Some(value.clone()),
|
|
244
|
+
Value::Number(value) => Some(value.to_string()),
|
|
245
|
+
Value::Bool(value) => Some(value.to_string()),
|
|
246
|
+
Value::Array(values) => Some(
|
|
247
|
+
values
|
|
248
|
+
.iter()
|
|
249
|
+
.filter_map(|value| research_value_string(Some(value)))
|
|
250
|
+
.collect::<Vec<_>>()
|
|
251
|
+
.join(", "),
|
|
252
|
+
),
|
|
253
|
+
Value::Object(map) => map
|
|
254
|
+
.get("fmt")
|
|
255
|
+
.and_then(|value| value.as_str().map(ToString::to_string))
|
|
256
|
+
.or_else(|| {
|
|
257
|
+
map.get("raw")
|
|
258
|
+
.and_then(|value| research_value_string(Some(value)))
|
|
259
|
+
}),
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#[derive(Debug, Clone, Serialize)]
|
|
264
|
+
pub struct ProviderProfile {
|
|
265
|
+
pub provider: String,
|
|
266
|
+
pub requires_api_key: bool,
|
|
267
|
+
pub official_status: String,
|
|
268
|
+
pub stability: String,
|
|
269
|
+
pub best_for: String,
|
|
270
|
+
pub large_download: bool,
|
|
271
|
+
pub capabilities: Vec<ProviderCapability>,
|
|
272
|
+
pub limitations: Vec<String>,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#[derive(Debug, Clone, Serialize)]
|
|
276
|
+
pub struct ProviderCapability {
|
|
277
|
+
pub module: String,
|
|
278
|
+
pub status: String,
|
|
279
|
+
pub note: String,
|
|
280
|
+
pub implemented: bool,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#[derive(Debug, Clone, Serialize)]
|
|
284
|
+
pub struct StooqCatalog {
|
|
285
|
+
pub fetched_at_utc: String,
|
|
286
|
+
pub source_url: String,
|
|
287
|
+
pub entries: Vec<StooqCatalogEntry>,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#[derive(Debug, Clone, Serialize)]
|
|
291
|
+
pub struct StooqCatalogEntry {
|
|
292
|
+
pub frequency: String,
|
|
293
|
+
pub market: String,
|
|
294
|
+
pub asset: String,
|
|
295
|
+
pub label: String,
|
|
296
|
+
pub approx_size_mb: Option<f64>,
|
|
297
|
+
pub listing_url: String,
|
|
298
|
+
pub direct_download_requires_captcha: bool,
|
|
299
|
+
pub cache_key: String,
|
|
300
|
+
pub cached_zip_path: Option<String>,
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#[derive(Debug, Clone, Serialize)]
|
|
304
|
+
pub struct StooqSyncReport {
|
|
305
|
+
pub provider: String,
|
|
306
|
+
pub frequency: String,
|
|
307
|
+
pub market: String,
|
|
308
|
+
pub asset: String,
|
|
309
|
+
pub cache_key: String,
|
|
310
|
+
pub zip_path: String,
|
|
311
|
+
pub bytes: u64,
|
|
312
|
+
pub imported_at_utc: String,
|
|
313
|
+
pub source: String,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[derive(Debug, Serialize)]
|
|
317
|
+
pub struct SearchReport {
|
|
318
|
+
pub category: String,
|
|
319
|
+
pub query: String,
|
|
320
|
+
pub provider: String,
|
|
321
|
+
pub fetched_at_utc: String,
|
|
322
|
+
pub fetched_at_local: String,
|
|
323
|
+
pub cache_status: String,
|
|
324
|
+
pub highlights: Vec<ResearchHighlight>,
|
|
325
|
+
pub payload: Value,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#[derive(Debug, Clone, Serialize)]
|
|
329
|
+
pub struct StreamQuote {
|
|
330
|
+
pub symbol: String,
|
|
331
|
+
pub price: f64,
|
|
332
|
+
pub time_utc: Option<String>,
|
|
333
|
+
pub time_local: Option<String>,
|
|
334
|
+
pub currency: Option<String>,
|
|
335
|
+
pub exchange: Option<String>,
|
|
336
|
+
pub quote_type: Option<i32>,
|
|
337
|
+
pub market_hours: Option<i32>,
|
|
338
|
+
pub change_pct: Option<f64>,
|
|
339
|
+
pub day_volume: Option<i64>,
|
|
340
|
+
pub day_high: Option<f64>,
|
|
341
|
+
pub day_low: Option<f64>,
|
|
342
|
+
pub change: Option<f64>,
|
|
343
|
+
pub short_name: Option<String>,
|
|
344
|
+
pub open: Option<f64>,
|
|
345
|
+
pub previous_close: Option<f64>,
|
|
346
|
+
pub provider: String,
|
|
347
|
+
}
|