Repository: tarkah/tickrs (language: rust)

Issue:
Futures
Are futures symbols supported?  This may be a case of me just not entering the symbols correctly, but I can't seem to figure it out.  I know Yahoo! does expose the futures here (https://finance.yahoo.com/commodities/).  Taking the S&P ES as an example, (https://finance.yahoo.com/quote/ES%3DF?p=ES%3DF), I've tried variations on the symbol... (ES, ES=F, ES=F?,ES=F?,ES=F?p) to no avail.  

(Also - Very cool tool that you've developed!  I was looking to do something vaguely similar in Python until I came across this).

Thanks!

 


Discussion / hints:
Yeah, so it appears the API does support it! The reason nothing is showing up is because one of the pieces of API data I'm expecting is missing (luckily nothing critical, just "Company Info" which doesn't relate here). I can easily fix this issue to get things to show up.

In addition, to better support futures, I need to adjust the `1D` chart since it's currently assuming 9am-4pm ET for market hours. Luckily the API tells me the trading hours for any instrument so I can leverage that.

Fix patch:
diff --git a/api/src/client.rs b/api/src/client.rs
index 08273e0..64931a5 100644
--- a/api/src/client.rs
+++ b/api/src/client.rs
@@ -218,7 +218,7 @@ mod tests {
     async fn test_company_data() {
         let client = Client::new();
 
-        let symbols = vec!["SPY", "AAPL", "AMD", "TSLA"];
+        let symbols = vec!["SPY", "AAPL", "AMD", "TSLA", "ES=F"];
 
         for symbol in symbols {
             let data = client.get_company_data(symbol).await;
diff --git a/api/src/model.rs b/api/src/model.rs
index 2c1562a..dc708f3 100644
--- a/api/src/model.rs
+++ b/api/src/model.rs
@@ -70,8 +70,21 @@ pub struct ChartData {
 pub struct ChartMeta {
     pub regular_market_price: f32,
     pub chart_previous_close: f32,
+    pub current_trading_period: Option<ChartCurrentTradingPeriod>,
 }
 
+#[serde(rename_all = "camelCase")]
+#[derive(Debug, Deserialize, Clone)]
+pub struct ChartCurrentTradingPeriod {
+    pub regular: ChartTradingPeriod,
+}
+
+#[serde(rename_all = "camelCase")]
+#[derive(Debug, Deserialize, Clone)]
+pub struct ChartTradingPeriod {
+    pub start: i64,
+    pub end: i64,
+}
 #[serde(rename_all = "camelCase")]
 #[derive(Debug, Deserialize, Clone)]
 pub struct ChartIndicators {
@@ -82,6 +95,7 @@ pub struct ChartIndicators {
 #[serde(rename_all = "camelCase")]
 #[derive(Debug, Deserialize, Clone)]
 pub struct ChartAdjClose {
+    #[serde(deserialize_with = "deserialize_vec")]
     pub adjclose: Vec<f32>,
 }
 
@@ -118,7 +132,7 @@ pub struct CompanyStatus {
 #[derive(Debug, Deserialize, Clone)]
 pub struct CompanyData {
     #[serde(rename = "assetProfile")]
-    pub profile: CompanyProfile,
+    pub profile: Option<CompanyProfile>,
     pub price: CompanyPrice,
 }
 
@@ -139,7 +153,7 @@ pub struct CompanyProfile {
 pub struct CompanyPrice {
     pub symbol: String,
     pub short_name: String,
-    pub long_name: String,
+    pub long_name: Option<String>,
     pub regular_market_price: CompanyRegularMarketPrice,
     pub regular_market_previous_close: CompanyRegularMarketPreviousClose,
     pub currency: Option<String>,
diff --git a/src/common.rs b/src/common.rs
index 0fa5247..6dd7e8b 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -114,7 +114,7 @@ impl TimeFrame {
     }
 }
 
-pub struct MarketHours(i64, i64);
+pub struct MarketHours(pub i64, pub i64);
 
 impl Default for MarketHours {
     fn default() -> Self {
diff --git a/src/service/stock.rs b/src/service/stock.rs
index 38b4042..118c41c 100644
--- a/src/service/stock.rs
+++ b/src/service/stock.rs
@@ -2,12 +2,12 @@ use super::*;
 use crate::common::*;
 use crate::task::*;
 
-use api::model::CompanyData;
+use api::model::{ChartTradingPeriod, CompanyData};
 
 pub struct StockService {
     symbol: String,
     current_price_handle: AsyncTaskHandle<f32>,
-    prices_handle: AsyncTaskHandle<Vec<Price>>,
+    prices_handle: AsyncTaskHandle<(Option<ChartTradingPeriod>, Vec<Price>)>,
     company_handle: AsyncTaskHandle<CompanyData>,
 }
 
@@ -41,7 +41,7 @@ impl StockService {
 #[derive(Debug)]
 pub enum Update {
     NewPrice(f32),
-    Prices(Vec<Price>),
+    Prices((Option<ChartTradingPeriod>, Vec<Price>)),
     CompanyData(CompanyData),
 }
 
diff --git a/src/task/prices.rs b/src/task/prices.rs
index 54d7ddd..5a6d4a1 100644
--- a/src/task/prices.rs
+++ b/src/task/prices.rs
@@ -1,7 +1,7 @@
 use super::*;
 use crate::common::{chart_data_to_prices, Price, TimeFrame};
 
-use api::Interval;
+use api::{model::ChartTradingPeriod, Interval};
 use async_std::sync::Arc;
 use futures::future::BoxFuture;
 
@@ -19,7 +19,7 @@ impl Prices {
 
 impl AsyncTask for Prices {
     type Input = (String, TimeFrame, api::Client);
-    type Response = Vec<Price>;
+    type Response = (Option<ChartTradingPeriod>, Vec<Price>);
 
     fn update_interval(&self) -> Option<Duration> {
         Some(self.time_frame.update_interval())
@@ -48,7 +48,14 @@ impl AsyncTask for Prices {
                 .get_chart_data(&symbol, interval, time_frame.as_range())
                 .await
             {
-                Some(chart_data_to_prices(response))
+                Some((
+                    response
+                        .meta
+                        .current_trading_period
+                        .as_ref()
+                        .map(|p| p.regular.to_owned()),
+                    chart_data_to_prices(response),
+                ))
             } else {
                 None
             }
diff --git a/src/widget/stock.rs b/src/widget/stock.rs
index 19b47b7..758c2a2 100644
--- a/src/widget/stock.rs
+++ b/src/widget/stock.rs
@@ -4,7 +4,7 @@ use crate::draw::{add_padding, PaddingDirection};
 use crate::service::{self, Service};
 use crate::{HIDE_PREV_CLOSE, HIDE_TOGGLE, SHOW_X_LABELS, TIME_FRAME};
 
-use api::model::CompanyData;
+use api::model::{ChartTradingPeriod, CompanyData};
 use tui::buffer::Buffer;
 use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
 use tui::style::{Color, Modifier, Style};
@@ -26,6 +26,7 @@ pub struct StockState {
     pub options: Option<OptionsState>,
     pub loading_tick: usize,
     pub prev_state_loaded: bool,
+    pub current_trading_period: Option<ChartTradingPeriod>,
 }
 
 impl StockState {
@@ -45,6 +46,7 @@ impl StockState {
             options: None,
             loading_tick: NUM_LOADING_TICKS,
             prev_state_loaded: false,
+            current_trading_period: None,
         }
     }
 
@@ -78,8 +80,9 @@ impl StockState {
                 service::stock::Update::NewPrice(price) => {
                     self.current_price = price;
                 }
-                service::stock::Update::Prices(prices) => {
+                service::stock::Update::Prices((trading_period, prices)) => {
                     self.prices[self.time_frame.idx()] = prices;
+                    self.current_trading_period = trading_period;
                 }
                 service::stock::Update::CompanyData(data) => {
                     self.profile = Some(data);
@@ -153,18 +156,20 @@ impl StockState {
         (max, min)
     }
 
-    pub fn x_bounds(&self) -> [f64; 2] {
+    pub fn x_bounds(&self, start: i64, end: i64) -> [f64; 2] {
+        let num_points = ((end - start) / 60) as f64;
+
         match self.time_frame {
-            TimeFrame::Day1 => [0.0, 390.0],
+            TimeFrame::Day1 => [0.0, num_points],
             _ => [0.0, (self.prices().len() + 1) as f64],
         }
     }
 
-    pub fn x_labels(&self, width: u16) -> Vec<String> {
+    pub fn x_labels(&self, width: u16, start: i64, end: i64) -> Vec<String> {
         let mut labels = vec![];
 
         let dates = if self.time_frame == TimeFrame::Day1 {
-            MarketHours::default().collect()
+            MarketHours(start, end).collect()
         } else {
             self.prices().iter().map(|p| p.date).collect::<Vec<_>>()
         };
@@ -425,6 +430,16 @@ impl StatefulWidget for StockWidget {
         // Draw graph
         {
             let (min, max) = state.min_max();
+            let start = state
+                .current_trading_period
+                .as_ref()
+                .map(|p| p.start)
+                .unwrap_or(52200);
+            let end = state
+                .current_trading_period
+                .as_ref()
+                .map(|p| p.end)
+                .unwrap_or(75600);
 
             let mut prices: Vec<_> = state
                 .prices()
@@ -444,7 +459,7 @@ impl StatefulWidget for StockWidget {
             };
 
             let x_labels = if show_x_labes {
-                state.x_labels(chunks[2].width)
+                state.x_labels(chunks[2].width, start, end)
             } else {
                 vec![]
             };
@@ -460,8 +475,10 @@ impl StatefulWidget for StockWidget {
             };
 
             let data_2 = if state.time_frame == TimeFrame::Day1 && loaded && !*HIDE_PREV_CLOSE {
+                let num_points = (end - start) / 60 + 1;
+
                 Some(
-                    (0..391)
+                    (0..num_points)
                         .map(|i| {
                             (
                                 (i + 1) as f64,
@@ -508,7 +525,7 @@ impl StatefulWidget for StockWidget {
                         .border_style(Style::default()),
                 )
                 .x_axis({
-                    let axis = Axis::default().bounds(state.x_bounds());
+                    let axis = Axis::default().bounds(state.x_bounds(start, end));
 
                     if show_x_labes && loaded {
                         axis.labels(&x_labels)
diff --git a/src/widget/stock_summary.rs b/src/widget/stock_summary.rs
index 7e8bdbc..bdd26a2 100644
--- a/src/widget/stock_summary.rs
+++ b/src/widget/stock_summary.rs
@@ -128,6 +128,16 @@ impl StatefulWidget for StockSummaryWidget {
             layout[1] = add_padding(layout[1], 1, PaddingDirection::Top);
 
             let (min, max) = state.min_max();
+            let start = state
+                .current_trading_period
+                .as_ref()
+                .map(|p| p.start)
+                .unwrap_or(52200);
+            let end = state
+                .current_trading_period
+                .as_ref()
+                .map(|p| p.end)
+                .unwrap_or(75600);
 
             let mut prices: Vec<_> = state
                 .prices()
@@ -200,7 +210,7 @@ impl StatefulWidget for StockSummaryWidget {
 
             Chart::<String, String>::default()
                 .block(Block::default().border_style(Style::default()))
-                .x_axis(Axis::default().bounds(state.x_bounds()))
+                .x_axis(Axis::default().bounds(state.x_bounds(start, end)))
                 .y_axis(
                     Axis::default()
                         .bounds(state.y_bounds(min, max))


Test patch: