feat(indicators): A5a Fibonacci tools (price-level) (#171)
Adds the six price-level Fibonacci tools as a new **Fibonacci** family (catalogue 367 -> 373, twenty-four families). All build on the internal `pattern_swing` ZigZag tracker, are parameter-free (baked 5% swing threshold), and emit `Candle -> struct` outputs via custom Python/Node/WASM bindings. | Tool | Output | |------|--------| | `FibRetracement` | seven levels (0/23.6/38.2/50/61.8/78.6/100%) of the last swing leg | | `FibExtension` | five extension ratios (127.2/141.4/161.8/200/261.8%) projected beyond the leg | | `FibProjection` | A-B-C measured-move target zone (61.8/100/161.8/261.8%) | | `AutoFib` | retracement anchored on the dominant (largest-magnitude) recent leg | | `GoldenPocket` | the 0.618-0.65 optimal-trade-entry band (low/mid/high) | | `FibConfluence` | densest cluster of retracement levels across recent legs (price + strength) | Fully wired: core (100% unit-tested branches), Python/Node/WASM struct bindings, fuzz driver, reference + streaming-vs-batch tests, README/docs counter. The four geometric/time tools (Fan, Arcs, Channel, Time Zones) follow in A5b. Verification: `cargo test --workspace` green, clippy `-D warnings` clean, node 450 tests, python 760 tests.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
//! Auto-Fibonacci — retracement of the most significant recent swing leg.
|
||||
|
||||
use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// How many recent pivots to consider when picking the dominant leg.
|
||||
const PIVOT_HISTORY: usize = 6;
|
||||
|
||||
/// The seven canonical retracement ratios, in ascending order.
|
||||
const RATIOS: [f64; 7] = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0];
|
||||
|
||||
/// Auto-Fibonacci retracement levels for the dominant recent swing leg.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct AutoFibOutput {
|
||||
/// 0.0% — the dominant leg's end.
|
||||
pub level_0: f64,
|
||||
/// 23.6% retracement.
|
||||
pub level_236: f64,
|
||||
/// 38.2% retracement.
|
||||
pub level_382: f64,
|
||||
/// 50% retracement.
|
||||
pub level_500: f64,
|
||||
/// 61.8% retracement.
|
||||
pub level_618: f64,
|
||||
/// 78.6% retracement.
|
||||
pub level_786: f64,
|
||||
/// 100% — the dominant leg's start.
|
||||
pub level_1000: f64,
|
||||
}
|
||||
|
||||
/// Auto-Fibonacci (`AutoFib`).
|
||||
///
|
||||
/// Like [`crate::indicators::FibRetracement`], but instead of always using the
|
||||
/// immediate last leg it scans the last six confirmed pivots and anchors the
|
||||
/// retracement on the single largest-magnitude leg among them — the dominant
|
||||
/// swing the market is most likely respecting.
|
||||
///
|
||||
/// Parameter-free; construction is infallible. Returns `None` until two pivots
|
||||
/// have confirmed.
|
||||
///
|
||||
/// See `crates/wickra-core/src/indicators/auto_fib.rs`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AutoFib {
|
||||
swing: SwingTracker,
|
||||
}
|
||||
|
||||
impl AutoFib {
|
||||
/// Construct a new Auto-Fibonacci tracker.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
swing: SwingTracker::new(SWING_THRESHOLD, PIVOT_HISTORY),
|
||||
}
|
||||
}
|
||||
|
||||
fn levels(&self) -> Option<AutoFibOutput> {
|
||||
let dominant = self.swing.pivots().windows(2).max_by(|x, y| {
|
||||
(x[0].price - x[1].price)
|
||||
.abs()
|
||||
.total_cmp(&(y[0].price - y[1].price).abs())
|
||||
})?;
|
||||
let (start, end) = (dominant[0].price, dominant[1].price);
|
||||
let level = |r: f64| end + r * (start - end);
|
||||
Some(AutoFibOutput {
|
||||
level_0: level(RATIOS[0]),
|
||||
level_236: level(RATIOS[1]),
|
||||
level_382: level(RATIOS[2]),
|
||||
level_500: level(RATIOS[3]),
|
||||
level_618: level(RATIOS[4]),
|
||||
level_786: level(RATIOS[5]),
|
||||
level_1000: level(RATIOS[6]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AutoFib {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Indicator for AutoFib {
|
||||
type Input = Candle;
|
||||
type Output = AutoFibOutput;
|
||||
|
||||
fn update(&mut self, candle: Candle) -> Option<AutoFibOutput> {
|
||||
self.swing.update(candle);
|
||||
self.levels()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.swing.reset();
|
||||
}
|
||||
|
||||
fn warmup_period(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.swing.pivots().len() >= 2
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"AutoFib"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::indicators::pattern_swing::candles_for_pivots;
|
||||
use crate::traits::BatchExt;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn accessors_and_metadata() {
|
||||
let indicator = AutoFib::new();
|
||||
assert_eq!(indicator.name(), "AutoFib");
|
||||
assert_eq!(indicator.warmup_period(), 2);
|
||||
assert!(!indicator.is_ready());
|
||||
assert!(!AutoFib::default().is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_output_before_two_pivots() {
|
||||
let mut indicator = AutoFib::new();
|
||||
let outputs: Vec<_> = candles_for_pivots(&[120.0])
|
||||
.into_iter()
|
||||
.map(|c| indicator.update(c))
|
||||
.collect();
|
||||
assert!(outputs.iter().all(Option::is_none));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anchors_on_the_largest_leg() {
|
||||
// Pivots: 130 -> 120 (small, 10) -> 220 (large, 100) -> 200 (small, 20).
|
||||
// The dominant leg is 120 -> 220; its retracement spans [120, 220].
|
||||
let mut indicator = AutoFib::new();
|
||||
let mut last = None;
|
||||
for candle in candles_for_pivots(&[130.0, 120.0, 220.0, 200.0]) {
|
||||
last = indicator.update(candle);
|
||||
}
|
||||
let v = last.unwrap();
|
||||
assert!(indicator.is_ready());
|
||||
// Largest leg 120 -> 220: 0% on 220 (end), 100% on 120 (start).
|
||||
assert_relative_eq!(v.level_0, 220.0);
|
||||
assert_relative_eq!(v.level_1000, 120.0);
|
||||
assert_relative_eq!(v.level_500, 170.0);
|
||||
assert_relative_eq!(v.level_618, 220.0 + 0.618 * (120.0 - 220.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_state() {
|
||||
let mut indicator = AutoFib::new();
|
||||
for candle in candles_for_pivots(&[200.0, 100.0]) {
|
||||
let _ = indicator.update(candle);
|
||||
}
|
||||
assert!(indicator.is_ready());
|
||||
indicator.reset();
|
||||
assert!(!indicator.is_ready());
|
||||
let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
|
||||
assert!(indicator.update(c).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles = candles_for_pivots(&[130.0, 120.0, 220.0, 200.0]);
|
||||
let mut a = AutoFib::new();
|
||||
let mut b = AutoFib::new();
|
||||
assert_eq!(
|
||||
a.batch(&candles),
|
||||
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
//! Fibonacci Confluence — the strongest retracement cluster across recent legs.
|
||||
|
||||
use crate::indicators::pattern_swing::{
|
||||
approx_equal, SwingTracker, LEVEL_TOLERANCE, SWING_THRESHOLD,
|
||||
};
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// How many recent pivots to consider; six pivots yield up to five legs.
|
||||
const PIVOT_HISTORY: usize = 6;
|
||||
|
||||
/// The retracement ratios contributed by each leg to the confluence search.
|
||||
const RATIOS: [f64; 3] = [0.382, 0.5, 0.618];
|
||||
|
||||
/// The strongest Fibonacci confluence zone found across recent swing legs.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FibConfluenceOutput {
|
||||
/// Mean price of the densest cluster of retracement levels.
|
||||
pub price: f64,
|
||||
/// Number of retracement levels that fall inside the cluster (its strength).
|
||||
pub strength: f64,
|
||||
}
|
||||
|
||||
/// Fibonacci Confluence (`FibConfluence`).
|
||||
///
|
||||
/// Computes the 38.2% / 50% / 61.8% retracement prices of every leg among the
|
||||
/// last six confirmed pivots, then reports the densest price cluster — where
|
||||
/// levels from different legs stack up, the zone the market is most likely to
|
||||
/// react to. `price` is the cluster mean; `strength` is how many levels it
|
||||
/// gathers.
|
||||
///
|
||||
/// Parameter-free; construction is infallible. Returns `None` until at least two
|
||||
/// legs (three pivots) exist.
|
||||
///
|
||||
/// See `crates/wickra-core/src/indicators/fib_confluence.rs`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FibConfluence {
|
||||
swing: SwingTracker,
|
||||
}
|
||||
|
||||
impl FibConfluence {
|
||||
/// Construct a new Fibonacci Confluence tracker.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
swing: SwingTracker::new(SWING_THRESHOLD, PIVOT_HISTORY),
|
||||
}
|
||||
}
|
||||
|
||||
fn confluence(&self) -> Option<FibConfluenceOutput> {
|
||||
let pivots = self.swing.pivots();
|
||||
if pivots.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let levels: Vec<f64> = pivots
|
||||
.windows(2)
|
||||
.flat_map(|leg| {
|
||||
let (start, end) = (leg[0].price, leg[1].price);
|
||||
RATIOS.map(|r| end + r * (start - end))
|
||||
})
|
||||
.collect();
|
||||
// The `len < 3` guard guarantees at least two legs, hence a non-empty
|
||||
// level set, so `max_by` always yields a cluster.
|
||||
let (count, total) = levels
|
||||
.iter()
|
||||
.map(|¢er| {
|
||||
let members: Vec<f64> = levels
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&x| approx_equal(x, center, LEVEL_TOLERANCE))
|
||||
.collect();
|
||||
(members.len(), members.iter().sum::<f64>())
|
||||
})
|
||||
.max_by(|a, b| a.0.cmp(&b.0))
|
||||
.expect("at least two legs guarantee a non-empty level set");
|
||||
Some(FibConfluenceOutput {
|
||||
price: total / count as f64,
|
||||
strength: count as f64,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FibConfluence {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Indicator for FibConfluence {
|
||||
type Input = Candle;
|
||||
type Output = FibConfluenceOutput;
|
||||
|
||||
fn update(&mut self, candle: Candle) -> Option<FibConfluenceOutput> {
|
||||
self.swing.update(candle);
|
||||
self.confluence()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.swing.reset();
|
||||
}
|
||||
|
||||
fn warmup_period(&self) -> usize {
|
||||
3
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.swing.pivots().len() >= 3
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"FibConfluence"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::indicators::pattern_swing::candles_for_pivots;
|
||||
use crate::traits::BatchExt;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn accessors_and_metadata() {
|
||||
let indicator = FibConfluence::new();
|
||||
assert_eq!(indicator.name(), "FibConfluence");
|
||||
assert_eq!(indicator.warmup_period(), 3);
|
||||
assert!(!indicator.is_ready());
|
||||
assert!(!FibConfluence::default().is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_output_before_two_legs() {
|
||||
let mut indicator = FibConfluence::new();
|
||||
let outputs: Vec<_> = candles_for_pivots(&[200.0, 100.0])
|
||||
.into_iter()
|
||||
.map(|c| indicator.update(c))
|
||||
.collect();
|
||||
assert!(outputs.iter().all(Option::is_none));
|
||||
assert!(!indicator.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picks_the_densest_cluster() {
|
||||
// Legs 200->100 and 100->160. The 38.2% of each (138.2 and ~137.08)
|
||||
// sit within 3% of each other and form the densest cluster (strength 2).
|
||||
let mut indicator = FibConfluence::new();
|
||||
let mut last = None;
|
||||
for candle in candles_for_pivots(&[200.0, 100.0, 160.0]) {
|
||||
last = indicator.update(candle);
|
||||
}
|
||||
let v = last.unwrap();
|
||||
assert!(indicator.is_ready());
|
||||
assert_relative_eq!(v.strength, 2.0);
|
||||
let want = (138.2 + (160.0 + 0.382 * (100.0 - 160.0))) / 2.0;
|
||||
assert_relative_eq!(v.price, want, epsilon = 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_state() {
|
||||
let mut indicator = FibConfluence::new();
|
||||
for candle in candles_for_pivots(&[200.0, 100.0, 160.0]) {
|
||||
let _ = indicator.update(candle);
|
||||
}
|
||||
assert!(indicator.is_ready());
|
||||
indicator.reset();
|
||||
assert!(!indicator.is_ready());
|
||||
let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
|
||||
assert!(indicator.update(c).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles = candles_for_pivots(&[200.0, 100.0, 160.0, 120.0]);
|
||||
let mut a = FibConfluence::new();
|
||||
let mut b = FibConfluence::new();
|
||||
assert_eq!(
|
||||
a.batch(&candles),
|
||||
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
//! Fibonacci Extension of the most recent confirmed swing leg.
|
||||
|
||||
use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// The five canonical extension ratios, in ascending order. Each is a multiple
|
||||
/// of the swing leg measured from its origin, so `1.0` sits on the leg's end and
|
||||
/// every ratio here projects further in the direction of the move.
|
||||
const RATIOS: [f64; 5] = [1.272, 1.414, 1.618, 2.0, 2.618];
|
||||
|
||||
/// Fibonacci Extension levels for the most recent swing leg.
|
||||
///
|
||||
/// Each field is the price reached if the move continues to the matching
|
||||
/// multiple of the leg, measured from the leg's start.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FibExtensionOutput {
|
||||
/// 127.2% extension.
|
||||
pub level_1272: f64,
|
||||
/// 141.4% extension.
|
||||
pub level_1414: f64,
|
||||
/// 161.8% extension — the "golden" extension.
|
||||
pub level_1618: f64,
|
||||
/// 200% extension.
|
||||
pub level_2000: f64,
|
||||
/// 261.8% extension.
|
||||
pub level_2618: f64,
|
||||
}
|
||||
|
||||
/// Fibonacci Extension (`FibExtension`).
|
||||
///
|
||||
/// Tracks confirmed swing pivots with a baked-in 5% reversal threshold and, once
|
||||
/// two pivots exist, projects the leg between them to the canonical extension
|
||||
/// ratios — the price targets a continuation of the move would reach.
|
||||
///
|
||||
/// Parameter-free; construction is infallible. Returns `None` until the first
|
||||
/// leg is complete.
|
||||
///
|
||||
/// See `crates/wickra-core/src/indicators/fib_extension.rs`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FibExtension {
|
||||
swing: SwingTracker,
|
||||
}
|
||||
|
||||
impl FibExtension {
|
||||
/// Construct a new Fibonacci Extension tracker.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
swing: SwingTracker::new(SWING_THRESHOLD, 2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension price at ratio `e` for a leg from `start` to `end`: the total
|
||||
/// move is `e` times the leg, measured from `start`.
|
||||
fn level(start: f64, end: f64, e: f64) -> f64 {
|
||||
start + e * (end - start)
|
||||
}
|
||||
|
||||
fn levels(&self) -> Option<FibExtensionOutput> {
|
||||
let pivots = self.swing.pivots();
|
||||
let [start, end] = [pivots.first()?.price, pivots.get(1)?.price];
|
||||
Some(FibExtensionOutput {
|
||||
level_1272: Self::level(start, end, RATIOS[0]),
|
||||
level_1414: Self::level(start, end, RATIOS[1]),
|
||||
level_1618: Self::level(start, end, RATIOS[2]),
|
||||
level_2000: Self::level(start, end, RATIOS[3]),
|
||||
level_2618: Self::level(start, end, RATIOS[4]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FibExtension {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Indicator for FibExtension {
|
||||
type Input = Candle;
|
||||
type Output = FibExtensionOutput;
|
||||
|
||||
fn update(&mut self, candle: Candle) -> Option<FibExtensionOutput> {
|
||||
self.swing.update(candle);
|
||||
self.levels()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.swing.reset();
|
||||
}
|
||||
|
||||
fn warmup_period(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.swing.pivots().len() >= 2
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"FibExtension"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::indicators::pattern_swing::candles_for_pivots;
|
||||
use crate::traits::BatchExt;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn accessors_and_metadata() {
|
||||
let indicator = FibExtension::new();
|
||||
assert_eq!(indicator.name(), "FibExtension");
|
||||
assert_eq!(indicator.warmup_period(), 2);
|
||||
assert!(!indicator.is_ready());
|
||||
assert!(!FibExtension::default().is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_output_before_two_pivots() {
|
||||
let mut indicator = FibExtension::new();
|
||||
let outputs: Vec<_> = candles_for_pivots(&[120.0])
|
||||
.into_iter()
|
||||
.map(|c| indicator.update(c))
|
||||
.collect();
|
||||
assert!(outputs.iter().all(Option::is_none));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_levels_of_a_down_leg() {
|
||||
// Leg start = 200 (high), end = 100 (low): a 100-point drop continued.
|
||||
let mut indicator = FibExtension::new();
|
||||
let mut last = None;
|
||||
for candle in candles_for_pivots(&[200.0, 100.0]) {
|
||||
last = indicator.update(candle);
|
||||
}
|
||||
let v = last.unwrap();
|
||||
assert!(indicator.is_ready());
|
||||
// 161.8% extension projects 1.618 * (-100) below the 200 origin.
|
||||
assert_relative_eq!(v.level_1272, 200.0 - 127.2);
|
||||
assert_relative_eq!(v.level_1414, 200.0 - 141.4);
|
||||
assert_relative_eq!(v.level_1618, 200.0 - 161.8);
|
||||
assert_relative_eq!(v.level_2000, 0.0);
|
||||
assert_relative_eq!(v.level_2618, 200.0 - 261.8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_state() {
|
||||
let mut indicator = FibExtension::new();
|
||||
for candle in candles_for_pivots(&[200.0, 100.0]) {
|
||||
let _ = indicator.update(candle);
|
||||
}
|
||||
indicator.reset();
|
||||
assert!(!indicator.is_ready());
|
||||
let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
|
||||
assert!(indicator.update(c).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
|
||||
let mut a = FibExtension::new();
|
||||
let mut b = FibExtension::new();
|
||||
assert_eq!(
|
||||
a.batch(&candles),
|
||||
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
//! Fibonacci Projection — a measured move from the last three swing pivots.
|
||||
|
||||
use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// The four canonical projection ratios, in ascending order. Each scales the
|
||||
/// A→B leg and projects it from C; `1.0` is the classic AB=CD measured move.
|
||||
const RATIOS: [f64; 4] = [0.618, 1.0, 1.618, 2.618];
|
||||
|
||||
/// Fibonacci Projection levels (the C→D target zone of a measured move).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FibProjectionOutput {
|
||||
/// 61.8% projection of the A→B leg from C.
|
||||
pub level_618: f64,
|
||||
/// 100% projection — the AB=CD measured move.
|
||||
pub level_1000: f64,
|
||||
/// 161.8% projection.
|
||||
pub level_1618: f64,
|
||||
/// 261.8% projection.
|
||||
pub level_2618: f64,
|
||||
}
|
||||
|
||||
/// Fibonacci Projection (`FibProjection`).
|
||||
///
|
||||
/// Reads the last three confirmed swing pivots as the points A, B and C of a
|
||||
/// measured move and projects the A→B leg from C at the canonical ratios — the
|
||||
/// price targets for the C→D leg.
|
||||
///
|
||||
/// Parameter-free; construction is infallible. Returns `None` until three
|
||||
/// pivots have confirmed.
|
||||
///
|
||||
/// See `crates/wickra-core/src/indicators/fib_projection.rs`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FibProjection {
|
||||
swing: SwingTracker,
|
||||
}
|
||||
|
||||
impl FibProjection {
|
||||
/// Construct a new Fibonacci Projection tracker.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
swing: SwingTracker::new(SWING_THRESHOLD, 3),
|
||||
}
|
||||
}
|
||||
|
||||
fn levels(&self) -> Option<FibProjectionOutput> {
|
||||
let pivots = self.swing.pivots();
|
||||
let [a, b, c] = [
|
||||
pivots.first()?.price,
|
||||
pivots.get(1)?.price,
|
||||
pivots.get(2)?.price,
|
||||
];
|
||||
let project = |p: f64| c + p * (b - a);
|
||||
Some(FibProjectionOutput {
|
||||
level_618: project(RATIOS[0]),
|
||||
level_1000: project(RATIOS[1]),
|
||||
level_1618: project(RATIOS[2]),
|
||||
level_2618: project(RATIOS[3]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FibProjection {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Indicator for FibProjection {
|
||||
type Input = Candle;
|
||||
type Output = FibProjectionOutput;
|
||||
|
||||
fn update(&mut self, candle: Candle) -> Option<FibProjectionOutput> {
|
||||
self.swing.update(candle);
|
||||
self.levels()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.swing.reset();
|
||||
}
|
||||
|
||||
fn warmup_period(&self) -> usize {
|
||||
3
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.swing.pivots().len() >= 3
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"FibProjection"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::indicators::pattern_swing::candles_for_pivots;
|
||||
use crate::traits::BatchExt;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn accessors_and_metadata() {
|
||||
let indicator = FibProjection::new();
|
||||
assert_eq!(indicator.name(), "FibProjection");
|
||||
assert_eq!(indicator.warmup_period(), 3);
|
||||
assert!(!indicator.is_ready());
|
||||
assert!(!FibProjection::default().is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_output_before_three_pivots() {
|
||||
let mut indicator = FibProjection::new();
|
||||
let outputs: Vec<_> = candles_for_pivots(&[200.0, 100.0])
|
||||
.into_iter()
|
||||
.map(|c| indicator.update(c))
|
||||
.collect();
|
||||
assert!(outputs.iter().all(Option::is_none));
|
||||
assert!(!indicator.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn measured_move_from_three_pivots() {
|
||||
// A = 200 (high), B = 160 (low), C = 190 (high). A->B = -40, projected
|
||||
// down from C.
|
||||
let mut indicator = FibProjection::new();
|
||||
let mut last = None;
|
||||
for candle in candles_for_pivots(&[200.0, 160.0, 190.0]) {
|
||||
last = indicator.update(candle);
|
||||
}
|
||||
let v = last.unwrap();
|
||||
assert!(indicator.is_ready());
|
||||
let (a, b, c) = (200.0, 160.0, 190.0);
|
||||
assert_relative_eq!(v.level_618, c + 0.618 * (b - a));
|
||||
assert_relative_eq!(v.level_1000, c + (b - a));
|
||||
assert_relative_eq!(v.level_1618, c + 1.618 * (b - a));
|
||||
assert_relative_eq!(v.level_2618, c + 2.618 * (b - a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_state() {
|
||||
let mut indicator = FibProjection::new();
|
||||
for candle in candles_for_pivots(&[200.0, 160.0, 190.0]) {
|
||||
let _ = indicator.update(candle);
|
||||
}
|
||||
assert!(indicator.is_ready());
|
||||
indicator.reset();
|
||||
assert!(!indicator.is_ready());
|
||||
let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
|
||||
assert!(indicator.update(c).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles = candles_for_pivots(&[200.0, 160.0, 190.0, 150.0]);
|
||||
let mut a = FibProjection::new();
|
||||
let mut b = FibProjection::new();
|
||||
assert_eq!(
|
||||
a.batch(&candles),
|
||||
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//! Fibonacci Retracement of the most recent confirmed swing leg.
|
||||
|
||||
use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// The seven canonical retracement ratios, in ascending order. `0.0` marks the
|
||||
/// most recent swing extreme (the end of the leg) and `1.0` the swing origin
|
||||
/// (its start); the interior ratios are the classic Fibonacci pullbacks.
|
||||
const RATIOS: [f64; 7] = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0];
|
||||
|
||||
/// Fibonacci Retracement levels for the most recent swing leg.
|
||||
///
|
||||
/// Each field is the price at the matching retracement ratio, measured from the
|
||||
/// leg's end (`level_0`, the latest confirmed extreme) back toward its start
|
||||
/// (`level_1000`, the prior pivot).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FibRetracementOutput {
|
||||
/// 0.0% — the most recent confirmed swing extreme.
|
||||
pub level_0: f64,
|
||||
/// 23.6% retracement.
|
||||
pub level_236: f64,
|
||||
/// 38.2% retracement.
|
||||
pub level_382: f64,
|
||||
/// 50% retracement (not a Fibonacci ratio, but conventionally drawn).
|
||||
pub level_500: f64,
|
||||
/// 61.8% retracement — the "golden ratio" pullback.
|
||||
pub level_618: f64,
|
||||
/// 78.6% retracement.
|
||||
pub level_786: f64,
|
||||
/// 100% — the swing origin.
|
||||
pub level_1000: f64,
|
||||
}
|
||||
|
||||
/// Fibonacci Retracement (`FibRetracement`).
|
||||
///
|
||||
/// Tracks confirmed swing pivots with a baked-in 5% reversal threshold (the
|
||||
/// same non-repainting logic as [`crate::indicators::ZigZag`]) and, once two
|
||||
/// pivots exist, reports the seven retracement levels of the leg between them.
|
||||
///
|
||||
/// The levels are recomputed each time a new pivot confirms; between
|
||||
/// confirmations [`Indicator::update`] returns the locked levels of the current
|
||||
/// leg. Before the first leg is complete it returns `None`.
|
||||
///
|
||||
/// Parameter-free: the threshold is a compile-time constant, mirroring the
|
||||
/// chart- and harmonic-pattern detectors, so construction is infallible.
|
||||
///
|
||||
/// See `crates/wickra-core/src/indicators/fib_retracement.rs`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FibRetracement {
|
||||
swing: SwingTracker,
|
||||
}
|
||||
|
||||
impl FibRetracement {
|
||||
/// Construct a new Fibonacci Retracement tracker.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
swing: SwingTracker::new(SWING_THRESHOLD, 2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retracement price at ratio `r` for a leg from `start` to `end`: `0.0`
|
||||
/// sits on `end`, `1.0` on `start`.
|
||||
fn level(start: f64, end: f64, r: f64) -> f64 {
|
||||
end + r * (start - end)
|
||||
}
|
||||
|
||||
fn levels(&self) -> Option<FibRetracementOutput> {
|
||||
let pivots = self.swing.pivots();
|
||||
let [start, end] = [pivots.first()?.price, pivots.get(1)?.price];
|
||||
Some(FibRetracementOutput {
|
||||
level_0: Self::level(start, end, RATIOS[0]),
|
||||
level_236: Self::level(start, end, RATIOS[1]),
|
||||
level_382: Self::level(start, end, RATIOS[2]),
|
||||
level_500: Self::level(start, end, RATIOS[3]),
|
||||
level_618: Self::level(start, end, RATIOS[4]),
|
||||
level_786: Self::level(start, end, RATIOS[5]),
|
||||
level_1000: Self::level(start, end, RATIOS[6]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FibRetracement {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Indicator for FibRetracement {
|
||||
type Input = Candle;
|
||||
type Output = FibRetracementOutput;
|
||||
|
||||
fn update(&mut self, candle: Candle) -> Option<FibRetracementOutput> {
|
||||
self.swing.update(candle);
|
||||
self.levels()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.swing.reset();
|
||||
}
|
||||
|
||||
fn warmup_period(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.swing.pivots().len() >= 2
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"FibRetracement"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::indicators::pattern_swing::candles_for_pivots;
|
||||
use crate::traits::BatchExt;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn accessors_and_metadata() {
|
||||
let indicator = FibRetracement::new();
|
||||
assert_eq!(indicator.name(), "FibRetracement");
|
||||
assert_eq!(indicator.warmup_period(), 2);
|
||||
assert!(!indicator.is_ready());
|
||||
assert!(!FibRetracement::default().is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_output_before_two_pivots() {
|
||||
let mut indicator = FibRetracement::new();
|
||||
// A single confirmed pivot is not enough to define a leg.
|
||||
let candles = candles_for_pivots(&[120.0]);
|
||||
let outputs: Vec<_> = candles.into_iter().map(|c| indicator.update(c)).collect();
|
||||
assert!(outputs.iter().all(Option::is_none));
|
||||
assert!(!indicator.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retracement_levels_of_a_down_leg() {
|
||||
// Leg start = 200 (high), end = 100 (low): a 100-point drop.
|
||||
let mut indicator = FibRetracement::new();
|
||||
let mut last = None;
|
||||
for candle in candles_for_pivots(&[200.0, 100.0]) {
|
||||
last = indicator.update(candle);
|
||||
}
|
||||
let v = last.unwrap();
|
||||
assert!(indicator.is_ready());
|
||||
// 0% on the low (end), 100% on the high (start).
|
||||
assert_relative_eq!(v.level_0, 100.0);
|
||||
assert_relative_eq!(v.level_1000, 200.0);
|
||||
// 61.8% retracement of a 100-point drop, measured up from the low.
|
||||
assert_relative_eq!(v.level_618, 161.8);
|
||||
assert_relative_eq!(v.level_500, 150.0);
|
||||
assert_relative_eq!(v.level_382, 138.2);
|
||||
assert_relative_eq!(v.level_236, 123.6);
|
||||
assert_relative_eq!(v.level_786, 178.6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levels_refresh_on_a_new_leg() {
|
||||
// Four pivots, cap = 2: once the third and fourth confirm, the reported
|
||||
// leg shifts to the latest pair (130 high -> 90 low).
|
||||
let mut indicator = FibRetracement::new();
|
||||
let mut last = None;
|
||||
for candle in candles_for_pivots(&[200.0, 100.0, 130.0, 90.0]) {
|
||||
last = indicator.update(candle);
|
||||
}
|
||||
let v = last.unwrap();
|
||||
assert_relative_eq!(v.level_0, 90.0);
|
||||
assert_relative_eq!(v.level_1000, 130.0);
|
||||
assert_relative_eq!(v.level_618, 90.0 + 0.618 * 40.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_state() {
|
||||
let mut indicator = FibRetracement::new();
|
||||
for candle in candles_for_pivots(&[200.0, 100.0]) {
|
||||
let _ = indicator.update(candle);
|
||||
}
|
||||
assert!(indicator.is_ready());
|
||||
indicator.reset();
|
||||
assert!(!indicator.is_ready());
|
||||
let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
|
||||
assert!(indicator.update(c).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
|
||||
let mut a = FibRetracement::new();
|
||||
let mut b = FibRetracement::new();
|
||||
assert_eq!(
|
||||
a.batch(&candles),
|
||||
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
//! Golden Pocket — the 0.618-0.65 optimal-trade-entry zone of the last swing.
|
||||
|
||||
use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// Lower bound of the golden pocket (the 61.8% retracement).
|
||||
const RATIO_LOW: f64 = 0.618;
|
||||
/// Upper bound of the golden pocket (the 65% retracement).
|
||||
const RATIO_HIGH: f64 = 0.65;
|
||||
|
||||
/// The golden-pocket zone of the most recent swing leg.
|
||||
///
|
||||
/// `low`/`high` bracket the 0.618-0.65 retracement band (sorted, so `low <=
|
||||
/// high` regardless of swing direction); `mid` is their midpoint.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct GoldenPocketOutput {
|
||||
/// Lower price of the golden-pocket band.
|
||||
pub low: f64,
|
||||
/// Midpoint of the band.
|
||||
pub mid: f64,
|
||||
/// Upper price of the golden-pocket band.
|
||||
pub high: f64,
|
||||
}
|
||||
|
||||
/// Golden Pocket (`GoldenPocket`).
|
||||
///
|
||||
/// The 0.618-0.65 retracement band of the most recent confirmed swing leg — the
|
||||
/// "optimal trade entry" zone many swing traders watch for continuation.
|
||||
///
|
||||
/// Parameter-free; construction is infallible. Returns `None` until the first
|
||||
/// leg is complete.
|
||||
///
|
||||
/// See `crates/wickra-core/src/indicators/golden_pocket.rs`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GoldenPocket {
|
||||
swing: SwingTracker,
|
||||
}
|
||||
|
||||
impl GoldenPocket {
|
||||
/// Construct a new Golden Pocket tracker.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
swing: SwingTracker::new(SWING_THRESHOLD, 2),
|
||||
}
|
||||
}
|
||||
|
||||
fn zone(&self) -> Option<GoldenPocketOutput> {
|
||||
let pivots = self.swing.pivots();
|
||||
let [start, end] = [pivots.first()?.price, pivots.get(1)?.price];
|
||||
let span = start - end;
|
||||
let edge_low = end + RATIO_LOW * span;
|
||||
let edge_high = end + RATIO_HIGH * span;
|
||||
let low = edge_low.min(edge_high);
|
||||
let high = edge_low.max(edge_high);
|
||||
Some(GoldenPocketOutput {
|
||||
low,
|
||||
mid: f64::midpoint(low, high),
|
||||
high,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GoldenPocket {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Indicator for GoldenPocket {
|
||||
type Input = Candle;
|
||||
type Output = GoldenPocketOutput;
|
||||
|
||||
fn update(&mut self, candle: Candle) -> Option<GoldenPocketOutput> {
|
||||
self.swing.update(candle);
|
||||
self.zone()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.swing.reset();
|
||||
}
|
||||
|
||||
fn warmup_period(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.swing.pivots().len() >= 2
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"GoldenPocket"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::indicators::pattern_swing::candles_for_pivots;
|
||||
use crate::traits::BatchExt;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn accessors_and_metadata() {
|
||||
let indicator = GoldenPocket::new();
|
||||
assert_eq!(indicator.name(), "GoldenPocket");
|
||||
assert_eq!(indicator.warmup_period(), 2);
|
||||
assert!(!indicator.is_ready());
|
||||
assert!(!GoldenPocket::default().is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_output_before_two_pivots() {
|
||||
let mut indicator = GoldenPocket::new();
|
||||
let outputs: Vec<_> = candles_for_pivots(&[120.0])
|
||||
.into_iter()
|
||||
.map(|c| indicator.update(c))
|
||||
.collect();
|
||||
assert!(outputs.iter().all(Option::is_none));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zone_of_a_down_leg() {
|
||||
// Leg 200 (high) -> 100 (low), span = 100.
|
||||
let mut indicator = GoldenPocket::new();
|
||||
let mut last = None;
|
||||
for candle in candles_for_pivots(&[200.0, 100.0]) {
|
||||
last = indicator.update(candle);
|
||||
}
|
||||
let v = last.unwrap();
|
||||
assert!(indicator.is_ready());
|
||||
// 61.8% = 161.8, 65% = 165 → sorted band [161.8, 165], mid 163.4.
|
||||
assert_relative_eq!(v.low, 161.8);
|
||||
assert_relative_eq!(v.high, 165.0);
|
||||
assert_relative_eq!(v.mid, 163.4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn band_is_sorted_for_an_up_leg() {
|
||||
// Latest leg 100 (low) -> 250 (high): span negative, edges flip, but
|
||||
// low <= high must still hold.
|
||||
let mut indicator = GoldenPocket::new();
|
||||
let mut last = None;
|
||||
for candle in candles_for_pivots(&[200.0, 100.0, 250.0]) {
|
||||
last = indicator.update(candle);
|
||||
}
|
||||
let v = last.unwrap();
|
||||
assert!(v.low <= v.high);
|
||||
assert_relative_eq!(v.mid, f64::midpoint(v.low, v.high));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_state() {
|
||||
let mut indicator = GoldenPocket::new();
|
||||
for candle in candles_for_pivots(&[200.0, 100.0]) {
|
||||
let _ = indicator.update(candle);
|
||||
}
|
||||
indicator.reset();
|
||||
assert!(!indicator.is_ready());
|
||||
let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
|
||||
assert!(indicator.update(c).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
|
||||
let mut a = GoldenPocket::new();
|
||||
let mut b = GoldenPocket::new();
|
||||
assert_eq!(
|
||||
a.batch(&candles),
|
||||
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ mod aroon_oscillator;
|
||||
mod atr;
|
||||
mod atr_bands;
|
||||
mod atr_trailing_stop;
|
||||
mod auto_fib;
|
||||
mod autocorrelation;
|
||||
mod average_daily_range;
|
||||
mod average_drawdown;
|
||||
@@ -110,6 +111,10 @@ mod evening_doji_star;
|
||||
mod evwma;
|
||||
mod falling_three_methods;
|
||||
mod fama;
|
||||
mod fib_confluence;
|
||||
mod fib_extension;
|
||||
mod fib_projection;
|
||||
mod fib_retracement;
|
||||
mod fibonacci_pivots;
|
||||
mod fisher_transform;
|
||||
mod flag_pennant;
|
||||
@@ -125,6 +130,7 @@ mod gain_loss_ratio;
|
||||
mod gap_side_by_side_white;
|
||||
mod garman_klass;
|
||||
mod gartley;
|
||||
mod golden_pocket;
|
||||
mod granger_causality;
|
||||
mod gravestone_doji;
|
||||
mod hammer;
|
||||
@@ -401,6 +407,7 @@ pub use aroon_oscillator::AroonOscillator;
|
||||
pub use atr::Atr;
|
||||
pub use atr_bands::{AtrBands, AtrBandsOutput};
|
||||
pub use atr_trailing_stop::AtrTrailingStop;
|
||||
pub use auto_fib::{AutoFib, AutoFibOutput};
|
||||
pub use autocorrelation::Autocorrelation;
|
||||
pub use average_daily_range::AverageDailyRange;
|
||||
pub use average_drawdown::AverageDrawdown;
|
||||
@@ -477,6 +484,10 @@ pub use evening_doji_star::EveningDojiStar;
|
||||
pub use evwma::Evwma;
|
||||
pub use falling_three_methods::FallingThreeMethods;
|
||||
pub use fama::Fama;
|
||||
pub use fib_confluence::{FibConfluence, FibConfluenceOutput};
|
||||
pub use fib_extension::{FibExtension, FibExtensionOutput};
|
||||
pub use fib_projection::{FibProjection, FibProjectionOutput};
|
||||
pub use fib_retracement::{FibRetracement, FibRetracementOutput};
|
||||
pub use fibonacci_pivots::{FibonacciPivots, FibonacciPivotsOutput};
|
||||
pub use fisher_transform::FisherTransform;
|
||||
pub use flag_pennant::FlagPennant;
|
||||
@@ -492,6 +503,7 @@ pub use gain_loss_ratio::GainLossRatio;
|
||||
pub use gap_side_by_side_white::GapSideBySideWhite;
|
||||
pub use garman_klass::GarmanKlassVolatility;
|
||||
pub use gartley::Gartley;
|
||||
pub use golden_pocket::{GoldenPocket, GoldenPocketOutput};
|
||||
pub use granger_causality::GrangerCausality;
|
||||
pub use gravestone_doji::GravestoneDoji;
|
||||
pub use hammer::Hammer;
|
||||
@@ -1222,6 +1234,17 @@ pub const FAMILIES: &[(&str, &[&str])] = &[
|
||||
"ThreeDrives",
|
||||
],
|
||||
),
|
||||
(
|
||||
"Fibonacci",
|
||||
&[
|
||||
"FibRetracement",
|
||||
"FibExtension",
|
||||
"FibProjection",
|
||||
"AutoFib",
|
||||
"GoldenPocket",
|
||||
"FibConfluence",
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1250,6 +1273,6 @@ mod family_tests {
|
||||
// the actual indicator count is the early-warning signal that an
|
||||
// indicator was added without being assigned a family.
|
||||
let total: usize = FAMILIES.iter().map(|(_, ns)| ns.len()).sum();
|
||||
assert_eq!(total, 367, "FAMILIES total drifted from indicator count");
|
||||
assert_eq!(total, 373, "FAMILIES total drifted from indicator count");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user