feat: add Alt-Chart Bars (Renko, Kagi, Point & Figure) via a BarBuilder trait (#146)
Introduces a BarBuilder trait for price-driven chart constructors that emit a variable number of bars per candle (deliberately not Indicator). Adds Renko (box-size bricks, 2-box reversal), Kagi (reversal-amount segments) and Point & Figure (box-size X/O columns, N-box reversal) in a new Alt-Chart Bars family, with custom Python/Node/WASM bindings, a dedicated fuzz target, tests and docs. Indicator count 292 -> 295.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
//! Core traits: the [`Indicator`] state machine and the [`BatchExt`] blanket extension.
|
||||
|
||||
use crate::ohlcv::Candle;
|
||||
|
||||
/// A streaming technical indicator.
|
||||
///
|
||||
/// Every indicator in Wickra implements this trait. The contract is:
|
||||
@@ -88,6 +90,58 @@ pub trait BatchExt: Indicator {
|
||||
|
||||
impl<T: Indicator> BatchExt for T {}
|
||||
|
||||
/// A streaming *bar builder* — an alternative-chart constructor (Renko, Kagi,
|
||||
/// Point-and-Figure) that turns a candle stream into a stream of price-driven
|
||||
/// bars.
|
||||
///
|
||||
/// Bar builders are deliberately **not** [`Indicator`]s: a single input candle
|
||||
/// may complete zero, one, or many bars (a large move can print several Renko
|
||||
/// bricks at once), which breaks the `update -> Option<Output>` one-in-one-out
|
||||
/// contract and the `batch == repeated update` length invariant. They get their
|
||||
/// own trait instead, returning a `Vec` of freshly completed bars per candle.
|
||||
///
|
||||
/// The contract is:
|
||||
///
|
||||
/// - [`update`](BarBuilder::update) ingests one candle and returns every bar it
|
||||
/// *completed* on that candle, in chronological order. An empty vector means
|
||||
/// the move was not large enough to finish a bar yet.
|
||||
/// - [`reset`](BarBuilder::reset) clears all state, returning the builder to the
|
||||
/// configuration it had immediately after construction.
|
||||
/// - [`batch`](BarBuilder::batch) concatenates the bars from replaying `update`
|
||||
/// over a slice; the flattened length is data-dependent, not the input length.
|
||||
///
|
||||
/// Bar builders cannot participate in [`Chain`] (which requires
|
||||
/// `Indicator<Input = f64, Output = f64>`); feed a downstream indicator from the
|
||||
/// bars' close prices manually if you need to chain off them.
|
||||
///
|
||||
/// ```text
|
||||
/// let mut renko = RenkoBars::new(1.0).unwrap();
|
||||
/// let bricks = renko.update(candle); // Vec<RenkoBrick>: 0..n completed bricks
|
||||
/// ```
|
||||
pub trait BarBuilder {
|
||||
/// Type of one completed bar.
|
||||
type Bar;
|
||||
|
||||
/// Feed one candle and return every bar completed on it (possibly none).
|
||||
fn update(&mut self, candle: Candle) -> Vec<Self::Bar>;
|
||||
|
||||
/// Reset all internal state to the freshly-constructed configuration.
|
||||
fn reset(&mut self);
|
||||
|
||||
/// Stable, human-readable builder name.
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Replay `update` over a slice, concatenating all completed bars. The
|
||||
/// result length is data-dependent (not the input length).
|
||||
fn batch(&mut self, candles: &[Candle]) -> Vec<Self::Bar> {
|
||||
let mut out = Vec::new();
|
||||
for candle in candles {
|
||||
out.extend(self.update(*candle));
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
/// Chain two indicators so the output of the first becomes the input of the second.
|
||||
///
|
||||
/// Both indicators must agree on `f64` as the bridging type, which is the common
|
||||
|
||||
Reference in New Issue
Block a user