diff --git a/.gitignore b/.gitignore index ea8c4bf..3a8cabc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.idea diff --git a/sp500.dec.2022.csv b/sp500.dec.2022.csv new file mode 100644 index 0000000..bf69e47 --- /dev/null +++ b/sp500.dec.2022.csv @@ -0,0 +1,503 @@ +MMM +AOS +ABT +ABBV +ACN +ATVI +ADM +ADBE +ADP +AAP +AES +AFL +A +APD +AKAM +ALK +ALB +ARE +ALGN +ALLE +LNT +ALL +GOOGL +GOOG +MO +AMZN +AMCR +AMD +AEE +AAL +AEP +AXP +AIG +AMT +AWK +AMP +ABC +AME +AMGN +APH +ADI +ANSS +AON +APA +AAPL +AMAT +APTV +ACGL +ANET +AJG +AIZ +T +ATO +ADSK +AZO +AVB +AVY +AXON +BKR +BALL +BAC +BBWI +BAX +BDX +WRB +BRK.B +BBY +BIO +TECH +BIIB +BLK +BK +BA +BKNG +BWA +BXP +BSX +BMY +AVGO +BR +BRO +BF.B +BG +CHRW +CDNS +CZR +CPT +CPB +COF +CAH +KMX +CCL +CARR +CTLT +CAT +CBOE +CBRE +CDW +CE +CNC +CNP +CDAY +CF +CRL +SCHW +CHTR +CVX +CMG +CB +CHD +CI +CINF +CTAS +CSCO +C +CFG +CLX +CME +CMS +KO +CTSH +CL +CMCSA +CMA +CAG +COP +ED +STZ +CEG +COO +CPRT +GLW +CTVA +CSGP +COST +CTRA +CCI +CSX +CMI +CVS +DHI +DHR +DRI +DVA +DE +DAL +XRAY +DVN +DXCM +FANG +DLR +DFS +DISH +DIS +DG +DLTR +D +DPZ +DOV +DOW +DTE +DUK +DD +DXC +EMN +ETN +EBAY +ECL +EIX +EW +EA +ELV +LLY +EMR +ENPH +ETR +EOG +EPAM +EQT +EFX +EQIX +EQR +ESS +EL +ETSY +RE +EVRG +ES +EXC +EXPE +EXPD +EXR +XOM +FFIV +FDS +FICO +FAST +FRT +FDX +FITB +FSLR +FE +FIS +FISV +FLT +FMC +F +FTNT +FTV +FOXA +FOX +BEN +FCX +GRMN +IT +GEHC +GEN +GNRC +GD +GE +GIS +GM +GPC +GILD +GL +GPN +GS +HAL +HIG +HAS +HCA +PEAK +HSIC +HSY +HES +HPE +HLT +HOLX +HD +HON +HRL +HST +HWM +HPQ +HUM +HBAN +HII +IBM +IEX +IDXX +ITW +ILMN +INCY +IR +PODD +INTC +ICE +IFF +IP +IPG +INTU +ISRG +IVZ +INVH +IQV +IRM +JBHT +JKHY +J +JNJ +JCI +JPM +JNPR +K +KDP +KEY +KEYS +KMB +KIM +KMI +KLAC +KHC +KR +LHX +LH +LRCX +LW +LVS +LDOS +LEN +LNC +LIN +LYV +LKQ +LMT +L +LOW +LYB +MTB +MRO +MPC +MKTX +MAR +MMC +MLM +MAS +MA +MTCH +MKC +MCD +MCK +MDT +MRK +META +MET +MTD +MGM +MCHP +MU +MSFT +MAA +MRNA +MHK +MOH +TAP +MDLZ +MPWR +MNST +MCO +MS +MOS +MSI +MSCI +NDAQ +NTAP +NFLX +NWL +NEM +NWSA +NWS +NEE +NKE +NI +NDSN +NSC +NTRS +NOC +NCLH +NRG +NUE +NVDA +NVR +NXPI +ORLY +OXY +ODFL +OMC +ON +OKE +ORCL +OGN +OTIS +PCAR +PKG +PARA +PH +PAYX +PAYC +PYPL +PNR +PEP +PKI +PFE +PCG +PM +PSX +PNW +PXD +PNC +POOL +PPG +PPL +PFG +PG +PGR +PLD +PRU +PEG +PTC +PSA +PHM +QRVO +PWR +QCOM +DGX +RL +RJF +RTX +O +REG +REGN +RF +RSG +RMD +RHI +ROK +ROL +ROP +ROST +RCL +SPGI +CRM +SBAC +SLB +STX +SEE +SRE +NOW +SHW +SPG +SWKS +SJM +SNA +SEDG +SO +LUV +SWK +SBUX +STT +STLD +STE +SYK +SYF +SNPS +SYY +TMUS +TROW +TTWO +TPR +TRGP +TGT +TEL +TDY +TFX +TER +TSLA +TXN +TXT +TMO +TJX +TSCO +TT +TDG +TRV +TRMB +TFC +TYL +TSN +USB +UDR +ULTA +UNP +UAL +UPS +URI +UNH +UHS +VLO +VTR +VRSN +VRSK +VZ +VRTX +VFC +VTRS +VICI +V +VMC +WAB +WBA +WMT +WBD +WM +WAT +WEC +WFC +WELL +WST +WDC +WRK +WY +WHR +WMB +WTW +GWW +WYNN +XEL +XYL +YUM +ZBRA +ZBH +ZION +ZTS \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9187683..66f574a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,18 @@ #![feature(associated_type_defaults)] -use chrono::prelude::*; -use clap::Parser; -use std::io::{Error, ErrorKind}; -use clap::error::ContextKind::MaxOccurrences; +use std::fs::File; +use chrono; +use std::io::{BufRead, BufReader, Error, ErrorKind}; +use std::sync::Arc; + use yahoo_finance_api as yahoo; use async_trait::async_trait; use std::time::{Duration, UNIX_EPOCH}; -use time::{macros::datetime, OffsetDateTime}; +use time::{OffsetDateTime}; +use tokio::{time as ttime}; use futures::future::join_all; -#[derive(Parser, Debug)] -#[clap( -version = "1.0", -author = "Claus Matzinger", -about = "A Manning LiveProject: async Rust" -)] -struct Opts { - #[clap(short, long, default_value = "AAPL,MSFT,UBER,GOOG,META,TSM,LYFT,AMD")] - symbols: String, - #[clap(short, long, default_value = "2023-05-05T12:00:09Z")] - from: String, -} - - -struct QuoteDataFuture {} +const SYMBOL_PATH: &str = "sp500.dec.2022.csv"; struct PriceDifference {} @@ -143,7 +131,6 @@ async fn fetch_closing_data( end: &OffsetDateTime, ) -> std::io::Result> { let provider = yahoo::YahooConnector::new(); - //let st = datetime!(2020-1-1 0:00:00.00 UTC); let response = provider .get_quote_history(symbol, *beginning, *end) .await @@ -162,50 +149,72 @@ async fn fetch_closing_data( } } +async fn process_symbol_data(symbol: &str, + from: &OffsetDateTime, + end: &OffsetDateTime) { + let closes = fetch_closing_data(&symbol, &from, &end).await.unwrap(); + if !closes.is_empty() { + let max_price = MaxPrice {}; + let min_price = MinPrice {}; + let price_difference = PriceDifference {}; + let windowed_sma = WindowedSMA { window_size: 30 }; + + // min/max of the period. unwrap() because those are Option types + let (period_max, period_min, price_diff, sma) + = tokio::join!( + max_price.calculate(&closes), + min_price.calculate(&closes), + price_difference.calculate(&closes), + windowed_sma.calculate( &closes) + ); + let (_, pct_change) = price_diff.unwrap_or((0.0, 0.0)); + let last_price = *closes.last().unwrap_or(&0.0); + + // a simple way to output CSV data + println!( + "{},{},${:.2},{:.2}%,${:.2},${:.2},${:.2}", + from.to_string(), + symbol, + last_price, + pct_change * 100.0, + period_min.unwrap(), + period_max.unwrap(), + sma.unwrap_or_default().last().unwrap_or(&0.0) + ); + } +} + +async fn read_data(file_path: &str) -> std::io::Result> { + let file = File::open(file_path).expect("Failed to open the file"); + let reader = BufReader::new(file); + + let data = reader.lines() + .filter_map(Result::ok) + .collect(); + + Ok(data) +} + #[tokio::main] async fn main() -> std::io::Result<()> { - let opts = Opts::parse(); - let symbols: Vec = opts.symbols.split(',').map(|s| s.to_string()).collect(); - //let symbols: &'static str = opts.symbols.into_boxed_str().as_ref(); - let from_utc: DateTime = opts.from.parse().expect("Couldn't parse 'from' date"); - let from = OffsetDateTime::from_unix_timestamp(from_utc.timestamp()).expect("Failed to convert to offset date time"); - let to = OffsetDateTime::from_unix_timestamp(Utc::now().timestamp()).expect("Failed to convert to offset date time"); + let sp500_symbols = read_data(SYMBOL_PATH).await?; + let total_symbols = sp500_symbols.len(); + let symbols = Arc::new(sp500_symbols); // a simple way to output a CSV header println!("period start,symbol,price,change %,min,max,30d avg"); let mut tasks = vec![]; - for symbol in symbols { + for s in 0..total_symbols { + let arc_symbols = symbols.clone(); + let symbol = (arc_symbols.as_ref())[s].clone(); let handle = tokio::spawn(async move { - let closes = fetch_closing_data(&symbol, &from, &to).await; - let closes = closes.expect("BOOM"); - if !closes.is_empty() { - let max_price = MaxPrice {}; - let min_price = MinPrice {}; - let price_difference = PriceDifference {}; - let windowed_sma = WindowedSMA { window_size: 30 }; - - // min/max of the period. unwrap() because those are Option types - let (period_max, period_min, price_diff, sma) - = tokio::join!( - max_price.calculate(&closes), - min_price.calculate(&closes), - price_difference.calculate(&closes), - windowed_sma.calculate( &closes) - ); - let (_, pct_change) = price_diff.unwrap_or((0.0, 0.0)); - let last_price = *closes.last().unwrap_or(&0.0); - - // a simple way to output CSV data - println!( - "{},{},${:.2},{:.2}%,${:.2},${:.2},${:.2}", - from_utc.to_rfc3339(), - symbol, - last_price, - pct_change * 100.0, - period_min.unwrap(), - period_max.unwrap(), - sma.unwrap_or_default().last().unwrap_or(&0.0) - ); + let mut interval = ttime::interval(std::time::Duration::from_secs(30)); + loop { + interval.tick().await; + let elapsed_time = chrono::Utc::now() + chrono::Duration::seconds(30); + let from = OffsetDateTime::from_unix_timestamp(elapsed_time.timestamp()).expect("Failed to convert to offset date time"); + let to = OffsetDateTime::from_unix_timestamp(chrono::Utc::now().timestamp()).expect("Failed to convert to offset date time"); + process_symbol_data(&symbol, &to, &from).await; } }); tasks.push(handle); @@ -284,4 +293,15 @@ mod tests { let signal = WindowedSMA { window_size: 10 }; assert_eq!(signal.calculate(&series).await, Some(vec![])); } + + #[tokio::test] + async fn test_reading_from_file() { + let symbols = vec!["MMM", "AOS", "ABT", "ABBV", "ACN", "ATVI", "ADM", "ADBE", "ADP", "AAP"]; + + let res = read_data("test.csv").await; + assert_eq!( + res.unwrap(), + symbols + ); + } } diff --git a/test.csv b/test.csv new file mode 100644 index 0000000..55e52a5 --- /dev/null +++ b/test.csv @@ -0,0 +1,10 @@ +MMM +AOS +ABT +ABBV +ACN +ATVI +ADM +ADBE +ADP +AAP \ No newline at end of file