diff options
| author | JSDurand <mmemmew@gmail.com> | 2026-10-09 16:12:46 +0800 |
|---|---|---|
| committer | JSDurand <mmemmew@gmail.com> | 2026-10-09 16:12:46 +0800 |
| commit | e02b8c1369799eee31fed5c5d8b44a1b044e2780 (patch) | |
| tree | 0f559ea030c6b814e36f04501c2dbdd15427d418 /src/main.rs | |
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.
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 87 |
1 files changed, 87 insertions, 0 deletions
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 <input.csv> [-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<PathBuf>, + rsi: usize, + kd: usize, + swing: f64, +} + +fn parse_args() -> Result<Args, String> { + 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<PathBuf, String> { + 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, + }); + // "</" would end the <script> element early. + let json = payload.to_string().replace("</", "<\\/"); + let html = TEMPLATE.replace("/*__DATA__*/null", &json); + + let out = args.output.unwrap_or_else(|| args.input.with_extension("html")); + std::fs::write(&out, html).map_err(|e| format!("{}: {e}", out.display()))?; + Ok(out) +} + +fn main() -> ExitCode { + match run() { + Ok(out) => { + println!("wrote {}", out.display()); + ExitCode::SUCCESS + } + Err(e) => { + if !e.is_empty() { + eprintln!("error: {e}"); + } + eprintln!("{USAGE}"); + ExitCode::FAILURE + } + } +} |
