Files
wickra/examples/rust/src/bin/multi_timeframe.rs
T
kingchenc 962ced0712 examples: add multi-timeframe demos for Rust and Node
Python's examples/python/multi_timeframe.py had no Rust or Node sibling.
Add both — the Rust version uses wickra-data's `Resampler` /
`resample_all` (the canonical path; no manual roll-up), the Node version
mirrors the Python one's inline aggregation because wickra-data's
resampler is currently Rust-only.

* examples/rust/src/bin/multi_timeframe.rs — reads the bundled 1m CSV via
  `CandleReader`, resamples to 5m / 15m / 1h / 4h / 1d via `resample_all`,
  prints last RSI(14), MACD(12,26,9) histogram and ADX(14) per timeframe.
* examples/node/multi_timeframe.js — same outputs from a hand-rolled
  bucket aggregator; reuses the new examples/data/ default path.
* examples/README.md gains the new rows.

Run side by side: the Rust and Node summaries are bit-identical at every
timeframe (50000 / 10000 / 3334 / 834 / 209 / 35 bars; same RSI, MACD
histogram and ADX to two decimals) — confirming both the Rust resampler
and the inline Node aggregator produce the same OHLC buckets.
2026-05-23 00:16:08 +02:00

86 lines
2.8 KiB
Rust

//! Multi-timeframe indicators with the Wickra Rust stack.
//!
//! Reads the bundled 1-minute BTCUSDT CSV (or a path passed on the command
//! line), resamples it via [`wickra_data::resample::resample_all`] to 5m /
//! 15m / 1h / 4h / 1d, and prints the last RSI(14), MACD(12,26,9) histogram
//! and ADX(14) at each timeframe. The Rust counterpart of
//! `examples/python/multi_timeframe.py`.
//!
//! Run with:
//! ```text
//! cargo run --release -p wickra-examples --bin multi_timeframe
//! ```
use std::env;
use std::path::PathBuf;
use wickra::{Adx, Candle, Indicator, MacdIndicator, Rsi};
use wickra_data::aggregator::Timeframe;
use wickra_data::csv::CandleReader;
use wickra_data::resample::resample_all;
const ONE_MINUTE_MS: i64 = 60_000;
fn default_csv() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("btcusdt-1m.csv")
}
fn summarize(label: &str, candles: &[Candle]) {
if candles.is_empty() {
println!(" {label:<5} (empty)");
return;
}
let mut rsi = Rsi::new(14).expect("RSI(14) constructor cannot fail");
let mut macd = MacdIndicator::new(12, 26, 9).expect("MACD(12,26,9) constructor cannot fail");
let mut adx = Adx::new(14).expect("ADX(14) constructor cannot fail");
let mut last_rsi: Option<f64> = None;
let mut last_hist: Option<f64> = None;
let mut last_adx: Option<f64> = None;
for c in candles {
if let Some(v) = rsi.update(c.close) {
last_rsi = Some(v);
}
if let Some(m) = macd.update(c.close) {
last_hist = Some(m.histogram);
}
if let Some(a) = adx.update(*c) {
last_adx = Some(a.adx);
}
}
let last_close = candles.last().expect("non-empty").close;
println!(
" {label:<5} bars={:>5} last_close={last_close:>10.2} rsi={} macd_hist={} adx={}",
candles.len(),
last_rsi.map_or_else(|| " --".to_string(), |v| format!("{v:>6.2}")),
last_hist.map_or_else(|| " -- ".to_string(), |v| format!("{v:+6.2}")),
last_adx.map_or_else(|| " --".to_string(), |v| format!("{v:>6.2}")),
);
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = env::args().nth(1).map_or_else(default_csv, PathBuf::from);
println!("Multi-timeframe view of {}", path.display());
let mut reader = CandleReader::open(&path)?;
let ones: Vec<Candle> = reader.read_all()?;
summarize("1m", &ones);
for &(label, minutes) in &[
("5m", 5_i64),
("15m", 15),
("1h", 60),
("4h", 240),
("1d", 1440),
] {
let tf = Timeframe::millis(minutes * ONE_MINUTE_MS)?;
let resampled = resample_all(tf, ones.iter().copied().map(Ok))?;
summarize(label, &resampled);
}
Ok(())
}