lp1_m1_code_120822 - provided code for Workflow: Transforming sync to async

This commit is contained in:
Paul Campbell 2024-03-10 13:26:49 +00:00
commit 7b069fb00b
5 changed files with 1472 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
[package]
authors = ["Claus Matzinger <claus.matzinger+kb@gmail.com>"]
edition = "2018"
name = "manning-lp-async-rust-project-1-m1"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
clap = {version = "3.1.8", features = ["derive"]}
yahoo_finance_api = { version = "1.1", features = ["blocking"] }

View file

@ -0,0 +1 @@
A, AAL, AAP, AAPL, ABBV, ABC, ABMD, ABT, ACGL, ACN, ADBE, ADI, ADM, ADP, ADSK, AEE, AEP, AES, AFL, AIG, AIZ, AJG, AKAM, ALB, ALGN, ALK, ALL, ALLE, AMAT, AMCR, AMD, AME, AMGN, AMP, AMT, AMZN, ANET, ANSS, AON, AOS, APA, APD, APH, APTV, ARE, ATO, ATVI, AVB, AVGO, AVY, AWK, AXP, AZO, BA, BAC, BALL, BAX, BBWI, BBY, BDX, BEN, BF.B, BIIB, BIO, BK, BKNG, BKR, BLK, BMY, BR, BRK.B, BRO, BSX, BWA, BXP, C, CAG, CAH, CARR, CAT, CB, CBOE, CBRE, CCI, CCL, CDAY, CDNS, CDW, CE, CEG, CF, CFG, CHD, CHRW, CHTR, CI, CINF, CL, CLX, CMA, CMCSA, CME, CMG, CMI, CMS, CNC, CNP, COF, COO, COP, COST, CPB, CPRT, CPT, CRL, CRM, CSCO, CSGP, CSX, CTAS, CTLT, CTRA, CTSH, CTVA, CVS, CVX, CZR, D, DAL, DD, DE, DFS, DG, DGX, DHI, DHR, DIS, DISH, DLR, DLTR, DOV, DOW, DPZ, DRI, DTE, DUK, DVA, DVN, DXC, DXCM, EA, EBAY, ECL, ED, EFX, EIX, EL, ELV, EMN, EMR, ENPH, EOG, EPAM, EQIX, EQR, EQT, ES, ESS, ETN, ETR, ETSY, EVRG, EW, EXC, EXPD, EXPE, EXR, F, FANG, FAST, FBHS, FCX, FDS, FDX, FE, FFIV, FIS, FISV, FITB, FLT, FMC, FOX, FOXA, FRC, FRT, FTNT, FTV, GD, GE, GEN, GILD, GIS, GL, GLW, GM, GNRC, GOOG, GOOGL, GPC, GPN, GRMN, GS, GWW, HAL, HAS, HBAN, HCA, HD, HES, HIG, HII, HLT, HOLX, HON, HPE, HPQ, HRL, HSIC, HST, HSY, HUM, HWM, IBM, ICE, IDXX, IEX, IFF, ILMN, INCY, INTC, INTU, INVH, IP, IPG, IQV, IR, IRM, ISRG, IT, ITW, IVZ, J, JBHT, JCI, JKHY, JNJ, JNPR, JPM, K, KDP, KEY, KEYS, KHC, KIM, KLAC, KMB, KMI, KMX, KO, KR, L, LDOS, LEN, LH, LHX, LIN, LKQ, LLY, LMT, LNC, LNT, LOW, LRCX, LUMN, LUV, LVS, LW, LYB, LYV, MA, MAA, MAR, MAS, MCD, MCHP, MCK, MCO, MDLZ, MDT, MET, META, MGM, MHK, MKC, MKTX, MLM, MMC, MMM, MNST, MO, MOH, MOS, MPC, MPWR, MRK, MRNA, MRO, MS, MSCI, MSFT, MSI, MTB, MTCH, MTD, MU, NCLH, NDAQ, NDSN, NEE, NEM, NFLX, NI, NKE, NOC, NOW, NRG, NSC, NTAP, NTRS, NUE, NVDA, NVR, NWL, NWS, NWSA, NXPI, O, ODFL, OGN, OKE, OMC, ON, ORCL, ORLY, OTIS, OXY, PARA, PAYC, PAYX, PCAR, PCG, PEAK, PEG, PEP, PFE, PFG, PG, PGR, PH, PHM, PKG, PKI, PLD, PM, PNC, PNR, PNW, POOL, PPG, PPL, PRU, PSA, PSX, PTC, PWR, PXD, PYPL, QCOM, QRVO, RCL, RE, REG, REGN, RF, RHI, RJF, RL, RMD, ROK, ROL, ROP, ROST, RSG, RTX, SBAC, SBNY, SBUX, SCHW, SEDG, SEE, SHW, SIVB, SJM, SLB, SNA, SNPS, SO, SPG, SPGI, SRE, STE, STT, STX, STZ, SWK, SWKS, SYF, SYK, SYY, T, TAP, TDG, TDY, TECH, TEL, TER, TFC, TFX, TGT, TJX, TMO, TMUS, TPR, TRGP, TRMB, TROW, TRV, TSCO, TSLA, TSN, TT, TTWO, TXN, TXT, TYL, UAL, UDR, UHS, ULTA, UNH, UNP, UPS, URI, USB, V, VFC, VICI, VLO, VMC, VNO, VRSK, VRSN, VRTX, VTR, VTRS, VZ, WAB, WAT, WBA, WBD, WDC, WEC, WELL, WFC, WHR, WM, WMB, WMT, WRB, WRK, WST, WTW, WY, WYNN, XEL, XOM, XRAY, XYL, YUM, ZBH, ZBRA, ZION, ZTS

View file

@ -0,0 +1 @@
MMM,AOS,ABT,ABBV,ABMD,ACN,ATVI,ADBE,AAP,AMD,AES,AFL,A,APD,AKAM,ALK,ALB,ARE,ALXN,ALGN,ALLE,ADS,LNT,ALL,GOOGL,GOOG,MO,AMZN,AMCR,AEE,AAL,AEP,AXP,AIG,AMT,AWK,AMP,ABC,AME,AMGN,APH,ADI,ANSS,ANTM,AON,APA,AIV,AAPL,AMAT,APTV,ADM,ANET,AJG,AIZ,T,ATO,ADSK,ADP,AZO,AVB,AVY,BKR,BLL,BAC,BAX,BDX,BRK.B,BBY,BIIB,BLK,BA,BKNG,BWA,BXP,BSX,BMY,AVGO,BR,BF.B,CHRW,COG,CDNS,CPB,COF,CAH,KMX,CCL,CARR,CAT,CBOE,CBRE,CDW,CE,CNC,CNP,CTL,CERN,CF,SCHW,CHTR,CVX,CMG,CB,CHD,CI,CINF,CTAS,CSCO,C,CFG,CTXS,CME,CMS,KO,CTSH,CL,CMCSA,CMA,CAG,CXO,COP,ED,STZ,CPRT,GLW,CTVA,COST,COTY,CCI,CSX,CMI,CVS,DHI,DHR,DRI,DVA,DE,DAL,XRAY,DVN,DXCM,FANG,DLR,DFS,DISCA,DISCK,DISH,DG,DLTR,D,DPZ,DOV,DOW,DTE,DUK,DRE,DD,DXC,ETFC,EMN,ETN,EBAY,ECL,EIX,EW,EA,EMR,ETR,EOG,EFX,EQIX,EQR,ESS,EL,RE,EVRG,ES,EXC,EXPE,EXPD,EXR,XOM,FFIV,FB,FAST,FRT,FDX,FIS,FITB,FRC,FE,FISV,FLT,FLIR,FLS,FMC,F,FTNT,FTV,FBHS,FOXA,FOX,BEN,FCX,GPS,GRMN,IT,GD,GE,GIS,GM,GPC,GILD,GPN,GL,GS,GWW,HRB,HAL,HBI,HOG,HIG,HAS,HCA,PEAK,HSIC,HES,HPE,HLT,HFC,HOLX,HD,HON,HRL,HST,HWM,HPQ,HUM,HBAN,HII,IEX,IDXX,INFO,ITW,ILMN,INCY,IR,INTC,ICE,IBM,IP,IPG,IFF,INTU,ISRG,IVZ,IPGP,IQV,IRM,JBHT,JKHY,J,SJM,JNJ,JCI,JPM,JNPR,KSU,K,KEY,KEYS,KMB,KIM,KMI,KLAC,KSS,KHC,KR,LB,LHX,LH,LRCX,LW,LVS,LEG,LDOS,LEN,LLY,LNC,LIN,LYV,LKQ,LMT,L,LOW,LYB,MTB,MRO,MPC,MKTX,MAR,MMC,MLM,MAS,MA,MXIM,MKC,MCD,MCK,MDT,MRK,MET,MTD,MGM,MCHP,MU,MSFT,MAA,MHK,TAP,MDLZ,MNST,MCO,MS,MSI,MSCI,MYL,NDAQ,NOV,NTAP,NFLX,NWL,NEM,NWSA,NWS,NEE,NLSN,NKE,NI,NBL,JWN,NSC,NTRS,NOC,NLOK,NCLH,NRG,NUE,NVDA,NVR,ORLY,OXY,ODFL,OMC,OKE,ORCL,OTIS,PCAR,PKG,PH,PAYX,PAYC,PYPL,PNR,PBCT,PEP,PKI,PRGO,PFE,PM,PSX,PNW,PXD,PNC,PPG,PPL,PFG,PG,PGR,PLD,PRU,PEG,PSA,PHM,PVH,QRVO,QCOM,PWR,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,SLG,SNA,SO,LUV,SWK,SBUX,STT,STE,SYK,SIVB,SYF,SNPS,SYY,TMUS,TROW,TTWO,TPR,TGT,TEL,FTI,TFX,TXN,TXT,BK,CLX,COO,HSY,MOS,TRV,DIS,TMO,TIF,TJX,TSCO,TT,TDG,TFC,TWTR,TSN,USB,UDR,ULTA,UAA,UA,UNP,UAL,UNH,UPS,URI,UHS,UNM,VFC,VLO,VAR,VTR,VRSN,VRSK,VZ,VRTX,VIAC,V,VNO,VMC,WRB,WAB,WBA,WMT,WM,WAT,WEC,WFC,WELL,WST,WDC,WU,WRK,WY,WHR,WMB,WLTW,WYNN,XEL,XRX,XLNX,XYL,YUM,ZBRA,ZBH,ZION,ZTS

View file

@ -0,0 +1,223 @@
use chrono::prelude::*;
use clap::Parser;
use std::io::{Error, ErrorKind};
use yahoo_finance_api as yahoo;
#[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")]
symbols: String,
#[clap(short, long)]
from: String,
}
///
/// A trait to provide a common interface for all signal calculations.
///
trait AsyncStockSignal {
///
/// The signal's data type.
///
type SignalType;
///
/// Calculate the signal on the provided series.
///
/// # Returns
///
/// The signal (using the provided type) or `None` on error/invalid data.
///
fn calculate(&self, series: &[f64]) -> Option<Self::SignalType>;
}
///
/// Calculates the absolute and relative difference between the beginning and ending of an f64 series. The relative difference is relative to the beginning.
///
/// # Returns
///
/// A tuple `(absolute, relative)` difference.
///
fn price_diff(a: &[f64]) -> Option<(f64, f64)> {
if !a.is_empty() {
// unwrap is safe here even if first == last
let (first, last) = (a.first().unwrap(), a.last().unwrap());
let abs_diff = last - first;
let first = if *first == 0.0 { 1.0 } else { *first };
let rel_diff = abs_diff / first;
Some((abs_diff, rel_diff))
} else {
None
}
}
///
/// Window function to create a simple moving average
///
fn n_window_sma(n: usize, series: &[f64]) -> Option<Vec<f64>> {
if !series.is_empty() && n > 1 {
Some(
series
.windows(n)
.map(|w| w.iter().sum::<f64>() / w.len() as f64)
.collect(),
)
} else {
None
}
}
///
/// Find the maximum in a series of f64
///
fn max(series: &[f64]) -> Option<f64> {
if series.is_empty() {
None
} else {
Some(series.iter().fold(f64::MIN, |acc, q| acc.max(*q)))
}
}
///
/// Find the minimum in a series of f64
///
fn min(series: &[f64]) -> Option<f64> {
if series.is_empty() {
None
} else {
Some(series.iter().fold(f64::MAX, |acc, q| acc.min(*q)))
}
}
///
/// Retrieve data from a data source and extract the closing prices. Errors during download are mapped onto io::Errors as InvalidData.
///
fn fetch_closing_data(
symbol: &str,
beginning: &DateTime<Utc>,
end: &DateTime<Utc>,
) -> std::io::Result<Vec<f64>> {
let provider = yahoo::YahooConnector::new();
let response = provider
.get_quote_history(symbol, *beginning, *end)
.map_err(|_| Error::from(ErrorKind::InvalidData))?;
let mut quotes = response
.quotes()
.map_err(|_| Error::from(ErrorKind::InvalidData))?;
if !quotes.is_empty() {
quotes.sort_by_cached_key(|k| k.timestamp);
Ok(quotes.iter().map(|q| q.adjclose as f64).collect())
} else {
Ok(vec![])
}
}
fn main() -> std::io::Result<()> {
let opts = Opts::parse();
let from: DateTime<Utc> = opts.from.parse().expect("Couldn't parse 'from' date");
let to = Utc::now();
// a simple way to output a CSV header
println!("period start,symbol,price,change %,min,max,30d avg");
for symbol in opts.symbols.split(',') {
let closes = fetch_closing_data(&symbol, &from, &to)?;
if !closes.is_empty() {
// min/max of the period. unwrap() because those are Option types
let period_max: f64 = max(&closes).unwrap();
let period_min: f64 = min(&closes).unwrap();
let last_price = *closes.last().unwrap_or(&0.0);
let (_, pct_change) = price_diff(&closes).unwrap_or((0.0, 0.0));
let sma = n_window_sma(30, &closes).unwrap_or_default();
// a simple way to output CSV data
println!(
"{},{},${:.2},{:.2}%,${:.2},${:.2},${:.2}",
from.to_rfc3339(),
symbol,
last_price,
pct_change * 100.0,
period_min,
period_max,
sma.last().unwrap_or(&0.0)
);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(non_snake_case)]
use super::*;
#[test]
fn test_PriceDifference_calculate() {
let signal = PriceDifference {};
assert_eq!(signal.calculate(&[]), None);
assert_eq!(signal.calculate(&[1.0]), Some((0.0, 0.0)));
assert_eq!(signal.calculate(&[1.0, 0.0]), Some((-1.0, -1.0)));
assert_eq!(
signal.calculate(&[2.0, 3.0, 5.0, 6.0, 1.0, 2.0, 10.0]),
Some((8.0, 4.0))
);
assert_eq!(
signal.calculate(&[0.0, 3.0, 5.0, 6.0, 1.0, 2.0, 1.0]),
Some((1.0, 1.0))
);
}
#[test]
fn test_MinPrice_calculate() {
let signal = MinPrice {};
assert_eq!(signal.calculate(&[]), None);
assert_eq!(signal.calculate(&[1.0]), Some(1.0));
assert_eq!(signal.calculate(&[1.0, 0.0]), Some(0.0));
assert_eq!(
signal.calculate(&[2.0, 3.0, 5.0, 6.0, 1.0, 2.0, 10.0]),
Some(1.0)
);
assert_eq!(
signal.calculate(&[0.0, 3.0, 5.0, 6.0, 1.0, 2.0, 1.0]),
Some(0.0)
);
}
#[test]
fn test_MaxPrice_calculate() {
let signal = MaxPrice {};
assert_eq!(signal.calculate(&[]), None);
assert_eq!(signal.calculate(&[1.0]), Some(1.0));
assert_eq!(signal.calculate(&[1.0, 0.0]), Some(1.0));
assert_eq!(
signal.calculate(&[2.0, 3.0, 5.0, 6.0, 1.0, 2.0, 10.0]),
Some(10.0)
);
assert_eq!(
signal.calculate(&[0.0, 3.0, 5.0, 6.0, 1.0, 2.0, 1.0]),
Some(6.0)
);
}
#[test]
fn test_WindowedSMA_calculate() {
let series = vec![2.0, 4.5, 5.3, 6.5, 4.7];
let signal = WindowedSMA { window_size: 3 };
assert_eq!(
signal.calculate(&series),
Some(vec![3.9333333333333336, 5.433333333333334, 5.5])
);
let signal = WindowedSMA { window_size: 5 };
assert_eq!(signal.calculate(&series), Some(vec![4.6]));
let signal = WindowedSMA { window_size: 10 };
assert_eq!(signal.calculate(&series), Some(vec![]));
}
}