From e02b8c1369799eee31fed5c5d8b44a1b044e2780 Mon Sep 17 00:00:00 2001 From: JSDurand Date: Fri, 9 Oct 2026 16:12:46 +0800 Subject: first commit This is code written by claude for a static stock analysis page generator. I intend to test difference analysis indicators to see if some claims make any sense. --- src/main.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..24f4ccc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,87 @@ +mod data; +mod indicators; +mod signals; + +use serde_json::json; +use std::path::PathBuf; +use std::process::ExitCode; + +const TEMPLATE: &str = include_str!("../web/template.html"); +const USAGE: &str = "usage: stock [-o out.html] [--rsi 14] [--kd 9] [--swing 5]"; +// --swing: minimum reversal in percent for a price swing (ZigZag) used by divergence detection + +struct Args { + input: PathBuf, + output: Option, + rsi: usize, + kd: usize, + swing: f64, +} + +fn parse_args() -> Result { + let mut it = std::env::args().skip(1); + let (mut input, mut output, mut rsi, mut kd, mut swing) = (None, None, 14, 9, 5.0); + while let Some(a) = it.next() { + let mut val = |name: &str| it.next().ok_or(format!("{name} needs a value")); + match a.as_str() { + "-o" => output = Some(PathBuf::from(val("-o")?)), + "--rsi" => rsi = val("--rsi")?.parse().map_err(|e| format!("--rsi: {e}"))?, + "--kd" => kd = val("--kd")?.parse().map_err(|e| format!("--kd: {e}"))?, + "--swing" => swing = val("--swing")?.parse().map_err(|e| format!("--swing: {e}"))?, + "-h" | "--help" => return Err(String::new()), + _ if a.starts_with('-') => return Err(format!("unknown option {a}")), + _ => input = Some(PathBuf::from(a)), + } + } + if rsi == 0 || kd == 0 || !(swing > 0.0 && swing < 100.0) { + return Err("periods must be positive and --swing within (0, 100)".into()); + } + Ok(Args { input: input.ok_or("missing input.csv")?, output, rsi, kd, swing }) +} + +fn run() -> Result { + let args = parse_args()?; + let candles = data::load_csv(&args.input)?; + let rsi = indicators::rsi(&candles, args.rsi); + let (k, d) = indicators::kd(&candles, args.kd); + + let swings = signals::zigzag(&candles, args.swing); + let mut divs = signals::divergences(&swings, &rsi, "RSI"); + divs.extend(signals::divergences(&swings, &k, "K")); + divs.sort_by_key(|d| d.confirmed); + print!("{}", signals::forward_stats(&candles, &divs, &[5, 10, 20])); + let payload = json!({ + "name": args.input.file_stem().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default(), + "rsiPeriod": args.rsi, + "kdPeriod": args.kd, + "candles": candles, + "rsi": rsi, + "k": k, + "d": d, + "crosses": signals::crosses(&k, &d), + "divergences": divs, + }); + // " element early. + let json = payload.to_string().replace(" ExitCode { + match run() { + Ok(out) => { + println!("wrote {}", out.display()); + ExitCode::SUCCESS + } + Err(e) => { + if !e.is_empty() { + eprintln!("error: {e}"); + } + eprintln!("{USAGE}"); + ExitCode::FAILURE + } + } +} -- cgit v1.2.3-18-g5258