Skip to content

Commit bba945f

Browse files
committed
fix: added market open, market close indications, added top gainers, losers, most active
1 parent a334526 commit bba945f

8 files changed

Lines changed: 841 additions & 55 deletions

File tree

Cargo.lock

Lines changed: 133 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ assets = [
2222
ratatui = "0.28"
2323
crossterm = "0.28"
2424
tokio = { version = "1", features = ["full"] }
25-
ureq = { version = "2.10", features = ["json"] }
25+
ureq = { version = "2.10", features = ["json", "cookies"] }
2626
serde = { version = "1.0", features = ["derive"] }
2727
serde_json = "1.0"
2828
chrono = "0.4"
2929
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
3030
futures-util = "0.3"
3131
rand = "0.8"
3232
dotenv = "0.15"
33-
dirs = "5"
33+
dirs = "5"
34+
cookie_store = "=0.21.1"

src/main.rs

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ mod ui;
1414
mod watchlist;
1515
mod websocket;
1616

17-
use ui::{App, AppState, LandingPanel, WebSocketStatus};
17+
use ui::{App, AppState, LandingPanel, MarketPanel, WebSocketStatus};
18+
use std::collections::HashMap;
19+
use crate::stock::QuoteSnapshot;
1820
use websocket::LivePrice;
1921
use std::fs::OpenOptions;
2022
use std::io::Write;
@@ -34,6 +36,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3436

3537
let (tx, mut rx) = mpsc::unbounded_channel::<LivePrice>();
3638
let (status_tx, mut status_rx) = mpsc::unbounded_channel::<WebSocketStatus>();
39+
let (quotes_tx, mut quotes_rx) = mpsc::unbounded_channel::<HashMap<String, QuoteSnapshot>>();
40+
41+
// Fetch landing quotes in background so terminal opens immediately
42+
tokio::spawn(async move {
43+
let result = tokio::task::spawn_blocking(move || {
44+
match crate::stock::YahooSession::new() {
45+
Ok(session) => {
46+
let syms: Vec<&str> = vec![
47+
"^GSPC", "^DJI", "^IXIC", "SPY", "QQQ",
48+
"AAPL", "MSFT", "GOOGL", "AMZN", "TSLA", "NVDA", "META",
49+
];
50+
crate::stock::fetch_batch_quotes(&session, &syms).ok()
51+
}
52+
Err(_) => None,
53+
}
54+
}).await;
55+
if let Ok(Some(quotes)) = result {
56+
let _ = quotes_tx.send(quotes);
57+
}
58+
});
3759

3860
// Setup terminal
3961
enable_raw_mode()?;
@@ -43,7 +65,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
4365
let mut terminal = Terminal::new(backend)?;
4466

4567
// Run the app
46-
let res = run_app(&mut terminal, &mut app, &mut rx, &mut status_rx, tx, status_tx).await;
68+
let res = run_app(&mut terminal, &mut app, &mut rx, &mut status_rx, &mut quotes_rx, tx, status_tx).await;
4769

4870
// Restore terminal
4971
disable_raw_mode()?;
@@ -62,6 +84,7 @@ async fn run_app(
6284
app: &mut App,
6385
rx: &mut mpsc::UnboundedReceiver<LivePrice>,
6486
status_rx: &mut mpsc::UnboundedReceiver<WebSocketStatus>,
87+
quotes_rx: &mut mpsc::UnboundedReceiver<HashMap<String, QuoteSnapshot>>,
6588
tx: mpsc::UnboundedSender<LivePrice>,
6689
status_tx: mpsc::UnboundedSender<WebSocketStatus>,
6790
) -> Result<(), io::Error> {
@@ -72,13 +95,17 @@ async fn run_app(
7295

7396
// Check for WebSocket status updates
7497
while let Ok(status) = status_rx.try_recv() {
75-
// Add errors to error log
7698
if let WebSocketStatus::Error { ref message, .. } = status {
7799
app.add_error_to_log(message.clone());
78100
}
79101
app.ws_status = status;
80102
}
81103

104+
// Check for background quote updates
105+
if let Ok(quotes) = quotes_rx.try_recv() {
106+
app.landing_quotes = quotes;
107+
}
108+
82109
// Check for live price updates with throttling
83110
// Drain all pending messages to prevent unbounded queueing
84111
let mut latest_price = None;
@@ -203,11 +230,44 @@ async fn handle_input(
203230
KeyCode::Char('h') => {
204231
app.show_help = !app.show_help;
205232
}
233+
KeyCode::Char('m') => {
234+
app.state = AppState::Market;
235+
app.fetch_market_data();
236+
}
237+
KeyCode::Char('r') => {
238+
app.refresh_landing_quotes();
239+
}
206240
_ => {}
207241
}
208242
}
209243
false
210244
}
245+
AppState::Market => {
246+
match key {
247+
KeyCode::Char('q') => return true,
248+
KeyCode::Char('b') | KeyCode::Esc => {
249+
app.state = AppState::Landing;
250+
}
251+
KeyCode::Char('r') => {
252+
app.fetch_market_data();
253+
}
254+
KeyCode::Tab => {
255+
app.market_panel = match app.market_panel {
256+
MarketPanel::Gainers => MarketPanel::Losers,
257+
MarketPanel::Losers => MarketPanel::Active,
258+
MarketPanel::Active => MarketPanel::Gainers,
259+
};
260+
}
261+
KeyCode::Up => app.previous_market(),
262+
KeyCode::Down => app.next_market(),
263+
KeyCode::Enter => {
264+
stop_websocket(ws_task_handle, &app.ws_should_stop).await;
265+
app.select_market();
266+
}
267+
_ => {}
268+
}
269+
false
270+
}
211271
AppState::Chart => {
212272
// Handle popups first
213273
if app.show_error_log {

0 commit comments

Comments
 (0)