1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! Fetch time series stock data from [IEX](https://iexcloud.io/docs/), implements Publisher trait
///
/// Example - retrieves historical data for Apple's daily stock price:
/// Reference https://iexcloud.io/docs/api/#historical-prices
/// https://api.iex.cloud/v1/stock/AAPL/chart/2y?token=YOUR-TOKEN-HERE
///
/// Reference https://iexcloud.io/docs/core/HISTORICAL_PRICES
/// https://api.iex.cloud/v1/data/core/historical_prices/aapl?range=2y&token=YOUR-TOKEN-HERE
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::{
    client::{MarketSeries, Series},
    errors::{MarketError, MarketResult},
    publishers::Publisher,
    rest_call::Client,
};

const BASE_URL: &str = "https://api.iex.cloud/v1/stock/";

/// Fetch time series stock data from [IEX](https://iexcloud.io/docs/), implements Publisher trait
#[derive(Debug, Default)]
pub struct Iex {
    token: String,
    symbol: String,
    range: String,
    endpoint: Option<url::Url>,
    data: Option<Vec<IexDailyPrices>>,
}

impl Iex {
    pub fn new(token: String) -> Self {
        Iex {
            token: token,
            ..Default::default()
        }
    }
    pub fn for_series(&mut self, symbol: String, range: String) -> () {
        self.symbol = symbol;
        self.range = range;
    }
}

impl Publisher for Iex {
    fn create_endpoint(&mut self) -> MarketResult<()> {
        let base_url = Url::parse(BASE_URL)?;
        let constructed_url = base_url.join(&format!(
            "{}/chart/{}?token={}",
            self.symbol, self.range, self.token
        ))?;
        self.endpoint = Some(constructed_url);
        Ok(())
    }

    #[cfg(feature = "use-sync")]
    fn get_data(&mut self) -> MarketResult<()> {
        let rest_client = Client::new(
            self.endpoint
                .clone()
                .expect("Use create_endpoint method first to construct the URL"),
        );
        let response = rest_client.get_data()?;
        let body = response.into_string()?;

        let prices: Vec<IexDailyPrices> = serde_json::from_str(&body)?;
        self.data = Some(prices);

        Ok(())
    }

    #[cfg(feature = "use-async")]
    async fn get_data(&mut self) -> MarketResult<()> {
        let client = Client::new(
            self.endpoint
                .clone()
                .expect("Use create_endpoint method first to construct the URL"),
        );
        let response = client.get_data().await?;
        let body = response.text().await?;

        let prices: Vec<IexDailyPrices> = serde_json::from_str(&body)?;
        self.data = Some(prices);

        Ok(())
    }

    fn to_writer(&self, writer: impl std::io::Write) -> MarketResult<()> {
        if let Some(data) = &self.data {
            serde_json::to_writer(writer, data).map_err(|err| {
                MarketError::ToWriter(format!("Unable to write to writer, got the error: {}", err))
            })?;
        }
        Ok(())
    }

    fn transform_data(&self) -> MarketResult<MarketSeries> {
        if let Some(data) = self.data.as_ref() {
            let mut data_series: Vec<Series> = Vec::with_capacity(data.len());
            for series in data.iter() {
                let date: NaiveDate =
                    NaiveDate::parse_from_str(&series.date, "%Y-%m-%d").map_err(|e| {
                        MarketError::ParsingError(format!("Unable to parse Date field: {}", e))
                    })?;
                data_series.push(Series {
                    date: date,
                    open: series.open,
                    close: series.close,
                    high: series.high,
                    low: series.low,
                    volume: series.volume as f32,
                })
            }

            // sort the series by date
            data_series.sort_by_key(|item| item.date);

            Ok(MarketSeries {
                symbol: self.symbol.clone(),
                data: data_series,
            })
        } else {
            Err(MarketError::DownloadedData(
                "No data downloaded".to_string(),
            ))
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
struct IexDailyPrices {
    close: f32,
    high: f32,
    low: f32,
    open: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "priceDate"))]
    price_date: String,
    #[allow(dead_code)]
    symbol: String,
    volume: u64,
    #[allow(dead_code)]
    id: String,
    #[allow(dead_code)]
    key: String,
    #[allow(dead_code)]
    subkey: String,
    date: String,
    #[allow(dead_code)]
    updated: u64,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "changeOverTime"))]
    change_over_time: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "marketChangeOverTime"))]
    market_change_over_time: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "uOpen"))]
    u_open: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "uClose"))]
    u_close: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "uHigh"))]
    u_high: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "uLow"))]
    u_low: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "uVolume"))]
    u_volume: u64,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "fOpen"))]
    f_open: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "fClose"))]
    f_close: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "fHigh"))]
    f_high: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "fLow"))]
    f_low: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "fVolume"))]
    f_volume: u64,
    #[allow(dead_code)]
    label: String,
    #[allow(dead_code)]
    change: f32,
    #[allow(dead_code)]
    #[serde(rename(deserialize = "changePercent"))]
    change_percent: f32,
}

// HistoricalPrice struct fields:
// close    number  Adjusted data for historical dates. Split adjusted only.
// high     number	Adjusted data for historical dates. Split adjusted only.
// low	    number	Adjusted data for historical dates. Split adjusted only.
// open	    number	Adjusted data for historical dates. Split adjusted only.
// symbol	string	Associated symbol or ticker
// volume	number	Adjusted data for historical dates. Split adjusted only.
// changeOverTime	number	Percent change of each interval relative to first value. Useful for comparing multiple stocks.
// marketChangeOverTime	number	Percent change of each interval relative to first value. 15 minute delayed consolidated data.
// uOpen	number	Unadjusted data for historical dates.
// uClose	number	Unadjusted data for historical dates.
// uHigh	number	Unadjusted data for historical dates.
// uLow	    number	Unadjusted data for historical dates.
// uVolume	number	Unadjusted data for historical dates.
// fOpen	number	Fully adjusted for historical dates.
// fClose	number	Fully adjusted for historical dates.
// fHigh	number	Fully adjusted for historical dates.
// fLow	    number	Fully adjusted for historical dates.
// fVolume	number	Fully adjusted for historical dates.
// label	number	A human readable format of the date depending on the range.
// change	number	Change from previous trading day.
// changePercent	number	Change percent from previous trading day.