lp1_m1_code_120822 - provided code for Workflow: Transforming sync to async
This commit is contained in:
commit
7b069fb00b
5 changed files with 1472 additions and 0 deletions
1235
1--transforming-sync-to-async/provided-code/Cargo.lock
generated
Normal file
1235
1--transforming-sync-to-async/provided-code/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
1--transforming-sync-to-async/provided-code/Cargo.toml
Normal file
12
1--transforming-sync-to-async/provided-code/Cargo.toml
Normal 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"] }
|
|
@ -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
|
|
@ -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
|
223
1--transforming-sync-to-async/provided-code/src/main.rs
Normal file
223
1--transforming-sync-to-async/provided-code/src/main.rs
Normal 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![]));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue