diff --git a/CHANGELOG.md b/CHANGELOG.md index 18651a05..359c958e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Belt-hold** — a single-bar reversal that opens at one extreme of its range and runs the other way; bullish +1, bearish -1 (`CDLBELTHOLD`). - **Breakaway** — a 5-bar reversal that gaps with the trend, drifts two more bars, then snaps back into the bar1/bar2 body gap; bullish +1, bearish -1 (`CDLBREAKAWAY`). - **Counterattack** — a 2-bar reversal where an opposite-coloured second bar closes level with the first (the counterattack line); bullish +1, bearish -1 (`CDLCOUNTERATTACK`). + - **Doji Star** — a long body followed by a doji gapping away in the trend direction; bullish +1, bearish -1 (`CDLDOJISTAR`). + - **Dragonfly Doji** — a doji opening and closing at the high with a long lower shadow, a bullish reversal; +1 (`CDLDRAGONFLYDOJI`). + - **Gravestone Doji** — a doji opening and closing at the low with a long upper shadow, a bearish reversal; -1 (`CDLGRAVESTONEDOJI`). + - **Long-Legged Doji** — a doji with long shadows on both sides, an indecision signal; +1 detection (`CDLLONGLEGGEDDOJI`). + - **Rickshaw Man** — a long-legged doji with the body centred in the range, an indecision signal; +1 detection (`CDLRICKSHAWMAN`). - **Derivatives family — funding & open interest (part 1).** A new family of indicators that consume a perpetual / futures tick (`DerivativesTick`, bundling funding rate, mark / index / futures price, open interest, diff --git a/README.md b/README.md index 5bcd82e2..41ca9c24 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Wickra — streaming-first technical indicators + Wickra — streaming-first technical indicators

[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml) @@ -47,7 +47,7 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**: [Node](https://docs.wickra.org/Quickstart-Node), [WASM](https://docs.wickra.org/Quickstart-WASM). - **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for - every one of the 254 indicators; start at the + every one of the 259 indicators; start at the [indicators overview](https://docs.wickra.org/Indicators-Overview). - **Reference** — [warmup periods](https://docs.wickra.org/Warmup-Periods), [streaming vs batch](https://docs.wickra.org/Streaming-vs-Batch), @@ -135,7 +135,7 @@ python -m benchmarks.compare_libraries ## Indicators -254 streaming-first indicators across eighteen families. Every one passes the +259 streaming-first indicators across eighteen families. Every one passes the `batch == streaming` equivalence test, reference-value tests, and reset semantics tests. Each has a per-indicator deep dive (formula, parameters, warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview). @@ -155,7 +155,7 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview). | Pivots & S/R | Classic Pivots, Fibonacci Pivots, Camarilla, Woodie Pivots, DeMark Pivots, Williams Fractals, ZigZag | | DeMark | TD Setup, TD Sequential, TD DeMarker, TD REI, TD Pressure, TD Combo, TD Countdown, TD Lines, TD Range Projection, TD Differential, TD Open, TD Risk Level | | Ichimoku & Charts | Ichimoku Kinko Hyo (Tenkan, Kijun, Senkou A/B, Chikou), Heikin-Ashi | -| Candlestick Patterns | Doji, Hammer, Inverted Hammer, Hanging Man, Shooting Star, Engulfing, Harami, Morning/Evening Star, Three White Soldiers/Black Crows, Piercing Line/Dark Cloud Cover, Marubozu, Tweezer, Spinning Top, Three Inside Up/Down, Three Outside Up/Down, Two Crows, Upside Gap Two Crows, Identical Three Crows, Three Line Strike, Three Stars in the South, Abandoned Baby, Advance Block, Belt-hold, Breakaway, Counterattack | +| Candlestick Patterns | Doji, Hammer, Inverted Hammer, Hanging Man, Shooting Star, Engulfing, Harami, Morning/Evening Star, Three White Soldiers/Black Crows, Piercing Line/Dark Cloud Cover, Marubozu, Tweezer, Spinning Top, Three Inside Up/Down, Three Outside Up/Down, Two Crows, Upside Gap Two Crows, Identical Three Crows, Three Line Strike, Three Stars in the South, Abandoned Baby, Advance Block, Belt-hold, Breakaway, Counterattack, Doji Star, Dragonfly Doji, Gravestone Doji, Long-Legged Doji, Rickshaw Man | | Microstructure | Order-Book Imbalance (Top-1 / Top-N / Full), Microprice, Quoted Spread, Depth Slope, Signed Volume, Cumulative Volume Delta, Trade Imbalance, Effective Spread, Realized Spread, Kyle's Lambda, Footprint | | Derivatives | Funding Rate, Funding Rate Mean, Funding Rate Z-Score, Funding Basis, Open-Interest Delta, OI / Price Divergence, OI-Weighted Price, Long/Short Ratio, Taker Buy/Sell Ratio, Liquidation Features, Term-Structure Basis, Calendar Spread | | Market Profile | Value Area (POC / VAH / VAL), Initial Balance, Opening Range | @@ -238,7 +238,7 @@ A Python live-trading example using the public `websockets` package lives at ``` wickra/ ├── crates/ -│ ├── wickra-core/ core engine + all 254 indicators +│ ├── wickra-core/ core engine + all 259 indicators │ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/ │ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds ├── bindings/ diff --git a/bindings/node/__tests__/indicators.test.js b/bindings/node/__tests__/indicators.test.js index e2b99c7f..0ef5335a 100644 --- a/bindings/node/__tests__/indicators.test.js +++ b/bindings/node/__tests__/indicators.test.js @@ -245,6 +245,11 @@ const candleScalar = { BeltHold: { make: () => new wickra.BeltHold(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, Breakaway: { make: () => new wickra.Breakaway(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, Counterattack: { make: () => new wickra.Counterattack(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + DojiStar: { make: () => new wickra.DojiStar(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + DragonflyDoji: { make: () => new wickra.DragonflyDoji(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + GravestoneDoji: { make: () => new wickra.GravestoneDoji(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + LongLeggedDoji: { make: () => new wickra.LongLeggedDoji(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + RickshawMan: { make: () => new wickra.RickshawMan(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, }; for (const [name, d] of Object.entries(candleScalar)) { diff --git a/bindings/node/index.d.ts b/bindings/node/index.d.ts index 2104720b..c602cd96 100644 --- a/bindings/node/index.d.ts +++ b/bindings/node/index.d.ts @@ -2300,6 +2300,51 @@ export declare class Counterattack { isReady(): boolean warmupPeriod(): number } +export type DojiStarNode = DojiStar +export declare class DojiStar { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} +export type DragonflyDojiNode = DragonflyDoji +export declare class DragonflyDoji { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} +export type GravestoneDojiNode = GravestoneDoji +export declare class GravestoneDoji { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} +export type LongLeggedDojiNode = LongLeggedDoji +export declare class LongLeggedDoji { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} +export type RickshawManNode = RickshawMan +export declare class RickshawMan { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} export type OrderBookImbalanceTop1Node = OrderBookImbalanceTop1 export declare class OrderBookImbalanceTop1 { constructor() diff --git a/bindings/node/index.js b/bindings/node/index.js index f8b0a262..cb2921db 100644 --- a/bindings/node/index.js +++ b/bindings/node/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, McGinleyDynamic, FRAMA, SuperSmoother, FisherTransform, Decycler, CenterOfGravity, CyberneticCycle, InstantaneousTrendline, EhlersStochastic, RVIVolatility, Variance, CoefficientOfVariation, Skewness, Kurtosis, StandardError, DetrendedStdDev, RSquared, MedianAbsoluteDeviation, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration, RelativeStrengthAB, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, ADXR, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, Inertia, ConnorsRSI, LaguerreRSI, SMI, KST, PGO, RVI, AwesomeOscillatorHistogram, STC, ElderImpulse, ZeroLagMACD, CFO, APO, KAMA, EVWMA, Alligator, JMA, VIDYA, ALMA, T3, TSI, PMO, TII, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, NVI, PVI, VolumeOscillator, KVO, WilliamsAD, AnchoredVWAP, DemandIndex, TSV, VZO, MarketFacilitationIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, HiLoActivator, VoltyStop, YoyoExit, DonchianStop, PercentageTrailingStop, StepTrailingStop, RenkoTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, YangZhangVolatility, RogersSatchellVolatility, GarmanKlassVolatility, ParkinsonVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, WaveTrend, RWI, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA, MaEnvelope, AccelerationBands, StarcBands, AtrBands, HurstChannel, LinRegChannel, StandardErrorBands, DoubleBollinger, TtmSqueeze, FractalChaosBands, VwapStdDevBands, ClassicPivots, FibonacciPivots, Camarilla, WoodiePivots, DemarkPivots, WilliamsFractals, ZigZag, TDSetup, TDSequential, TDDeMarker, TDREI, TDPressure, TDCombo, TDCountdown, TDLines, TDRangeProjection, TDDifferential, TDOpen, TDRiskLevel, InverseFisherTransform, DecyclerOscillator, RoofingFilter, EmpiricalModeDecomposition, HilbertDominantCycle, AdaptiveCycle, SineWave, MAMA, FAMA, Ichimoku, HeikinAshi, ValueArea, InitialBalance, OpeningRange, Doji, Hammer, InvertedHammer, HangingMan, ShootingStar, Engulfing, Harami, MorningEveningStar, ThreeSoldiersOrCrows, PiercingDarkCloud, Marubozu, Tweezer, SpinningTop, ThreeInside, ThreeOutside, TwoCrows, UpsideGapTwoCrows, IdenticalThreeCrows, ThreeLineStrike, ThreeStarsInSouth, AbandonedBaby, AdvanceBlock, BeltHold, Breakaway, Counterattack, OrderBookImbalanceTop1, OrderBookImbalanceFull, Microprice, QuotedSpread, DepthSlope, OrderBookImbalanceTopN, SignedVolume, CumulativeVolumeDelta, TradeImbalance, EffectiveSpread, RealizedSpread, KylesLambda, Footprint, FundingRate, FundingRateMean, FundingRateZScore, FundingBasis, OpenInterestDelta, OIPriceDivergence, OIWeighted, LongShortRatio, TakerBuySellRatio, LiquidationFeatures, TermStructureBasis, CalendarSpread, SharpeRatio, SortinoRatio, CalmarRatio, OmegaRatio, MaxDrawdown, AverageDrawdown, DrawdownDuration, PainIndex, ValueAtRisk, ConditionalValueAtRisk, ProfitFactor, GainLossRatio, RecoveryFactor, KellyCriterion, TreynorRatio, InformationRatio, Alpha } = nativeBinding +const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, McGinleyDynamic, FRAMA, SuperSmoother, FisherTransform, Decycler, CenterOfGravity, CyberneticCycle, InstantaneousTrendline, EhlersStochastic, RVIVolatility, Variance, CoefficientOfVariation, Skewness, Kurtosis, StandardError, DetrendedStdDev, RSquared, MedianAbsoluteDeviation, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration, RelativeStrengthAB, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, ADXR, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, Inertia, ConnorsRSI, LaguerreRSI, SMI, KST, PGO, RVI, AwesomeOscillatorHistogram, STC, ElderImpulse, ZeroLagMACD, CFO, APO, KAMA, EVWMA, Alligator, JMA, VIDYA, ALMA, T3, TSI, PMO, TII, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, NVI, PVI, VolumeOscillator, KVO, WilliamsAD, AnchoredVWAP, DemandIndex, TSV, VZO, MarketFacilitationIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, HiLoActivator, VoltyStop, YoyoExit, DonchianStop, PercentageTrailingStop, StepTrailingStop, RenkoTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, YangZhangVolatility, RogersSatchellVolatility, GarmanKlassVolatility, ParkinsonVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, WaveTrend, RWI, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA, MaEnvelope, AccelerationBands, StarcBands, AtrBands, HurstChannel, LinRegChannel, StandardErrorBands, DoubleBollinger, TtmSqueeze, FractalChaosBands, VwapStdDevBands, ClassicPivots, FibonacciPivots, Camarilla, WoodiePivots, DemarkPivots, WilliamsFractals, ZigZag, TDSetup, TDSequential, TDDeMarker, TDREI, TDPressure, TDCombo, TDCountdown, TDLines, TDRangeProjection, TDDifferential, TDOpen, TDRiskLevel, InverseFisherTransform, DecyclerOscillator, RoofingFilter, EmpiricalModeDecomposition, HilbertDominantCycle, AdaptiveCycle, SineWave, MAMA, FAMA, Ichimoku, HeikinAshi, ValueArea, InitialBalance, OpeningRange, Doji, Hammer, InvertedHammer, HangingMan, ShootingStar, Engulfing, Harami, MorningEveningStar, ThreeSoldiersOrCrows, PiercingDarkCloud, Marubozu, Tweezer, SpinningTop, ThreeInside, ThreeOutside, TwoCrows, UpsideGapTwoCrows, IdenticalThreeCrows, ThreeLineStrike, ThreeStarsInSouth, AbandonedBaby, AdvanceBlock, BeltHold, Breakaway, Counterattack, DojiStar, DragonflyDoji, GravestoneDoji, LongLeggedDoji, RickshawMan, OrderBookImbalanceTop1, OrderBookImbalanceFull, Microprice, QuotedSpread, DepthSlope, OrderBookImbalanceTopN, SignedVolume, CumulativeVolumeDelta, TradeImbalance, EffectiveSpread, RealizedSpread, KylesLambda, Footprint, FundingRate, FundingRateMean, FundingRateZScore, FundingBasis, OpenInterestDelta, OIPriceDivergence, OIWeighted, LongShortRatio, TakerBuySellRatio, LiquidationFeatures, TermStructureBasis, CalendarSpread, SharpeRatio, SortinoRatio, CalmarRatio, OmegaRatio, MaxDrawdown, AverageDrawdown, DrawdownDuration, PainIndex, ValueAtRisk, ConditionalValueAtRisk, ProfitFactor, GainLossRatio, RecoveryFactor, KellyCriterion, TreynorRatio, InformationRatio, Alpha } = nativeBinding module.exports.version = version module.exports.SMA = SMA @@ -525,6 +525,11 @@ module.exports.AdvanceBlock = AdvanceBlock module.exports.BeltHold = BeltHold module.exports.Breakaway = Breakaway module.exports.Counterattack = Counterattack +module.exports.DojiStar = DojiStar +module.exports.DragonflyDoji = DragonflyDoji +module.exports.GravestoneDoji = GravestoneDoji +module.exports.LongLeggedDoji = LongLeggedDoji +module.exports.RickshawMan = RickshawMan module.exports.OrderBookImbalanceTop1 = OrderBookImbalanceTop1 module.exports.OrderBookImbalanceFull = OrderBookImbalanceFull module.exports.Microprice = Microprice diff --git a/bindings/node/src/lib.rs b/bindings/node/src/lib.rs index 8b105f8e..e2c279a4 100644 --- a/bindings/node/src/lib.rs +++ b/bindings/node/src/lib.rs @@ -8775,6 +8775,11 @@ node_candle_pattern!(AdvanceBlockNode, wc::AdvanceBlock, "AdvanceBlock"); node_candle_pattern!(BeltHoldNode, wc::BeltHold, "BeltHold"); node_candle_pattern!(BreakawayNode, wc::Breakaway, "Breakaway"); node_candle_pattern!(CounterattackNode, wc::Counterattack, "Counterattack"); +node_candle_pattern!(DojiStarNode, wc::DojiStar, "DojiStar"); +node_candle_pattern!(DragonflyDojiNode, wc::DragonflyDoji, "DragonflyDoji"); +node_candle_pattern!(GravestoneDojiNode, wc::GravestoneDoji, "GravestoneDoji"); +node_candle_pattern!(LongLeggedDojiNode, wc::LongLeggedDoji, "LongLeggedDoji"); +node_candle_pattern!(RickshawManNode, wc::RickshawMan, "RickshawMan"); // ============================== Microstructure: Order Book ============================== // diff --git a/bindings/python/python/wickra/__init__.py b/bindings/python/python/wickra/__init__.py index 17122b2c..636b9477 100644 --- a/bindings/python/python/wickra/__init__.py +++ b/bindings/python/python/wickra/__init__.py @@ -250,6 +250,11 @@ from ._wickra import ( BeltHold, Breakaway, Counterattack, + DojiStar, + DragonflyDoji, + GravestoneDoji, + LongLeggedDoji, + RickshawMan, # Microstructure: order book OrderBookImbalanceTop1, OrderBookImbalanceTopN, @@ -527,6 +532,11 @@ __all__ = [ "BeltHold", "Breakaway", "Counterattack", + "DojiStar", + "DragonflyDoji", + "GravestoneDoji", + "LongLeggedDoji", + "RickshawMan", # Microstructure: order book "OrderBookImbalanceTop1", "OrderBookImbalanceTopN", diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index f31acfa6..6e0aec9c 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -11638,6 +11638,11 @@ candle_pattern_no_param!(PyAdvanceBlock, wc::AdvanceBlock, "AdvanceBlock"); candle_pattern_no_param!(PyBeltHold, wc::BeltHold, "BeltHold"); candle_pattern_no_param!(PyBreakaway, wc::Breakaway, "Breakaway"); candle_pattern_no_param!(PyCounterattack, wc::Counterattack, "Counterattack"); +candle_pattern_no_param!(PyDojiStar, wc::DojiStar, "DojiStar"); +candle_pattern_no_param!(PyDragonflyDoji, wc::DragonflyDoji, "DragonflyDoji"); +candle_pattern_no_param!(PyGravestoneDoji, wc::GravestoneDoji, "GravestoneDoji"); +candle_pattern_no_param!(PyLongLeggedDoji, wc::LongLeggedDoji, "LongLeggedDoji"); +candle_pattern_no_param!(PyRickshawMan, wc::RickshawMan, "RickshawMan"); // ============================== Microstructure: Order Book ============================== // // Order-book indicators consume a depth snapshot rather than OHLCV. Streaming @@ -14144,6 +14149,11 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; // Microstructure: order book. m.add_class::()?; m.add_class::()?; diff --git a/bindings/python/tests/test_new_indicators.py b/bindings/python/tests/test_new_indicators.py index f0501ad4..f0d32ab0 100644 --- a/bindings/python/tests/test_new_indicators.py +++ b/bindings/python/tests/test_new_indicators.py @@ -563,6 +563,26 @@ CANDLE_SCALAR = { lambda: ta.Counterattack(), lambda ind, h, l, c, v: ind.batch(c, h, l, c), ), + "DojiStar": ( + lambda: ta.DojiStar(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), + "DragonflyDoji": ( + lambda: ta.DragonflyDoji(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), + "GravestoneDoji": ( + lambda: ta.GravestoneDoji(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), + "LongLeggedDoji": ( + lambda: ta.LongLeggedDoji(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), + "RickshawMan": ( + lambda: ta.RickshawMan(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), } @@ -1685,6 +1705,32 @@ def test_counterattack_reference(): assert t.update((20.0, 20.1, 14.9, 15.0, 1.0, 0)) == pytest.approx(0.0) assert t.update((10.0, 15.1, 9.9, 15.0, 1.0, 1)) == pytest.approx(1.0) + +def test_doji_star_reference(): + t = ta.DojiStar() + assert t.update((20.0, 20.2, 14.8, 15.0, 1.0, 0)) == pytest.approx(0.0) + assert t.update((13.0, 13.1, 12.9, 13.0, 1.0, 1)) == pytest.approx(1.0) + + +def test_dragonfly_doji_reference(): + t = ta.DragonflyDoji() + assert t.update((10.0, 10.05, 6.0, 10.0, 1.0, 0)) == pytest.approx(1.0) + + +def test_gravestone_doji_reference(): + t = ta.GravestoneDoji() + assert t.update((10.0, 14.0, 9.95, 10.0, 1.0, 0)) == pytest.approx(-1.0) + + +def test_long_legged_doji_reference(): + t = ta.LongLeggedDoji() + assert t.update((10.0, 12.0, 8.0, 10.05, 1.0, 0)) == pytest.approx(1.0) + + +def test_rickshaw_man_reference(): + t = ta.RickshawMan() + assert t.update((10.0, 12.0, 8.0, 10.0, 1.0, 0)) == pytest.approx(1.0) + # --- Lifecycle ------------------------------------------------------------ diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index b37c1f7b..4d9dd599 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -6352,6 +6352,11 @@ wasm_candle_pattern!(WasmAdvanceBlock, wc::AdvanceBlock, AdvanceBlock); wasm_candle_pattern!(WasmBeltHold, wc::BeltHold, BeltHold); wasm_candle_pattern!(WasmBreakaway, wc::Breakaway, Breakaway); wasm_candle_pattern!(WasmCounterattack, wc::Counterattack, Counterattack); +wasm_candle_pattern!(WasmDojiStar, wc::DojiStar, DojiStar); +wasm_candle_pattern!(WasmDragonflyDoji, wc::DragonflyDoji, DragonflyDoji); +wasm_candle_pattern!(WasmGravestoneDoji, wc::GravestoneDoji, GravestoneDoji); +wasm_candle_pattern!(WasmLongLeggedDoji, wc::LongLeggedDoji, LongLeggedDoji); +wasm_candle_pattern!(WasmRickshawMan, wc::RickshawMan, RickshawMan); // ============================== Microstructure: Order Book ============================== // diff --git a/crates/wickra-core/src/indicators/doji_star.rs b/crates/wickra-core/src/indicators/doji_star.rs new file mode 100644 index 00000000..85b2d22e --- /dev/null +++ b/crates/wickra-core/src/indicators/doji_star.rs @@ -0,0 +1,205 @@ +//! Doji Star candlestick pattern. + +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Doji Star — a 2-bar reversal warning. A long trending body is followed by a +/// doji whose tiny body gaps away in the direction of the trend, the indecision +/// hinting the move is about to turn. +/// +/// ```text +/// long body = |close − open| >= 0.5 * (high − low) (bar1) +/// doji = |close − open| <= 0.1 * (high − low) (bar2) +/// bullish (+1.0): bar1 black, doji body gaps DOWN below it (max(o2,c2) < close1) +/// bearish (−1.0): bar1 white, doji body gaps UP above it (min(o2,c2) > close1) +/// ``` +/// +/// Output is `+1.0` (bullish star, after a black bar) or `−1.0` (bearish star, +/// after a white bar) when the pattern completes, and `0.0` otherwise. The first +/// bar always returns `0.0` because the two-bar window is not yet filled. Doji +/// thresholds follow the geometric house style (fixed half-range body for the +/// long bar, tenth-range body for the doji) rather than TA-Lib's rolling +/// averages. Pattern-shape check only — no trend filter is applied; combine with +/// a trend indicator for actionable signals. +/// +/// # Signed ±1 encoding +/// +/// This detector emits the uniform candlestick sign convention shared across the +/// pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no pattern — so it +/// drops straight into a machine-learning feature matrix where the bullish and +/// bearish variants occupy a single dimension. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Candle, DojiStar, Indicator}; +/// +/// let mut indicator = DojiStar::new(); +/// // Long black bar, then a doji gapping down -> bullish star. +/// indicator.update(Candle::new(20.0, 20.2, 14.8, 15.0, 1.0, 0).unwrap()); +/// let out = indicator +/// .update(Candle::new(13.0, 13.1, 12.9, 13.0, 1.0, 1).unwrap()); +/// assert_eq!(out, Some(1.0)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct DojiStar { + prev: Option, + has_emitted: bool, +} + +impl DojiStar { + /// Construct a new Doji Star detector. + pub const fn new() -> Self { + Self { + prev: None, + has_emitted: false, + } + } +} + +impl Indicator for DojiStar { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let prev = self.prev; + self.prev = Some(candle); + let Some(bar1) = prev else { + return Some(0.0); + }; + let range1 = bar1.high - bar1.low; + let range2 = candle.high - candle.low; + if range1 <= 0.0 || range2 <= 0.0 { + return Some(0.0); + } + let body1 = bar1.close - bar1.open; + if body1.abs() < 0.5 * range1 { + return Some(0.0); + } + if (candle.close - candle.open).abs() > 0.1 * range2 { + return Some(0.0); + } + let doji_top = candle.open.max(candle.close); + let doji_bottom = candle.open.min(candle.close); + // Bullish: long black bar, doji body gaps down below it. + if body1 < 0.0 && doji_top < bar1.close { + return Some(1.0); + } + // Bearish: long white bar, doji body gaps up above it. + if body1 > 0.0 && doji_bottom > bar1.close { + return Some(-1.0); + } + Some(0.0) + } + + fn reset(&mut self) { + self.prev = None; + self.has_emitted = false; + } + + fn warmup_period(&self) -> usize { + 2 + } + + fn is_ready(&self) -> bool { + self.has_emitted + } + + fn name(&self) -> &'static str { + "DojiStar" + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BatchExt; + + fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle { + Candle::new(open, high, low, close, 1.0, ts).unwrap() + } + + #[test] + fn accessors_and_metadata() { + let t = DojiStar::new(); + assert_eq!(t.name(), "DojiStar"); + assert_eq!(t.warmup_period(), 2); + assert!(!t.is_ready()); + } + + #[test] + fn bullish_doji_star_is_plus_one() { + let mut t = DojiStar::new(); + assert_eq!(t.update(c(20.0, 20.2, 14.8, 15.0, 0)), Some(0.0)); + assert_eq!(t.update(c(13.0, 13.1, 12.9, 13.0, 1)), Some(1.0)); + } + + #[test] + fn bearish_doji_star_is_minus_one() { + let mut t = DojiStar::new(); + assert_eq!(t.update(c(15.0, 20.2, 14.8, 20.0, 0)), Some(0.0)); + assert_eq!(t.update(c(22.0, 22.1, 21.9, 22.0, 1)), Some(-1.0)); + } + + #[test] + fn second_bar_not_doji_yields_zero() { + let mut t = DojiStar::new(); + t.update(c(20.0, 20.2, 14.8, 15.0, 0)); + // Wide body, not a doji. + assert_eq!(t.update(c(13.0, 13.2, 11.0, 11.5, 1)), Some(0.0)); + } + + #[test] + fn no_gap_yields_zero() { + let mut t = DojiStar::new(); + t.update(c(20.0, 20.2, 14.8, 15.0, 0)); + // Doji overlaps bar1's body (no gap down). + assert_eq!(t.update(c(16.0, 16.1, 15.9, 16.0, 1)), Some(0.0)); + } + + #[test] + fn short_first_body_yields_zero() { + let mut t = DojiStar::new(); + // First bar body too short to be the "long" leg. + t.update(c(20.0, 24.0, 16.0, 19.5, 0)); + assert_eq!(t.update(c(13.0, 13.1, 12.9, 13.0, 1)), Some(0.0)); + } + + #[test] + fn first_bar_returns_zero() { + let mut t = DojiStar::new(); + assert_eq!(t.update(c(20.0, 20.2, 14.8, 15.0, 0)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + if i % 2 == 0 { + c(base + 5.0, base + 5.2, base - 0.2, base, i) + } else { + c(base - 3.0, base - 2.9, base - 3.1, base - 3.0, i) + } + }) + .collect(); + let mut a = DojiStar::new(); + let mut b = DojiStar::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = DojiStar::new(); + t.update(c(20.0, 20.2, 14.8, 15.0, 0)); + t.update(c(13.0, 13.1, 12.9, 13.0, 1)); + assert!(t.is_ready()); + t.reset(); + assert!(!t.is_ready()); + assert_eq!(t.update(c(20.0, 20.2, 14.8, 15.0, 0)), Some(0.0)); + } +} diff --git a/crates/wickra-core/src/indicators/dragonfly_doji.rs b/crates/wickra-core/src/indicators/dragonfly_doji.rs new file mode 100644 index 00000000..3b37b51e --- /dev/null +++ b/crates/wickra-core/src/indicators/dragonfly_doji.rs @@ -0,0 +1,163 @@ +//! Dragonfly Doji candlestick pattern. + +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Dragonfly Doji — a single-bar bullish reversal. Open, close, and high sit at +/// the top of the bar while a long lower shadow shows price was driven down hard +/// and then bid all the way back to the open — buyers rejecting the lows. +/// +/// ```text +/// range = high − low +/// doji = |close − open| <= 0.1 * range +/// no upper wick = high − max(open, close) <= 0.1 * range +/// long lower = min(open, close) − low >= 0.5 * range +/// ``` +/// +/// Output is `+1.0` when the dragonfly prints and `0.0` otherwise. Dragonfly Doji +/// is a single-direction (bullish-only) shape, so it never emits `−1.0`. Body and +/// shadow thresholds follow the geometric house style (fixed fractions of the bar +/// range) rather than TA-Lib's rolling averages. Pattern-shape check only — no +/// trend filter is applied; combine with a trend indicator for actionable +/// signals. +/// +/// # Signed ±1 encoding +/// +/// This detector emits the uniform candlestick sign convention shared across the +/// pattern family — `+1.0` bullish, `0.0` no pattern — so it drops straight into +/// a machine-learning feature matrix as a single dimension. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Candle, DragonflyDoji, Indicator}; +/// +/// let mut indicator = DragonflyDoji::new(); +/// // Body at the top, long lower shadow. +/// let candle = Candle::new(10.0, 10.05, 6.0, 10.0, 1.0, 0).unwrap(); +/// assert_eq!(indicator.update(candle), Some(1.0)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct DragonflyDoji { + has_emitted: bool, +} + +impl DragonflyDoji { + /// Construct a new Dragonfly Doji detector. + pub const fn new() -> Self { + Self { has_emitted: false } + } +} + +impl Indicator for DragonflyDoji { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let range = candle.high - candle.low; + if range <= 0.0 { + return Some(0.0); + } + if (candle.close - candle.open).abs() > 0.1 * range { + return Some(0.0); + } + let upper = candle.high - candle.open.max(candle.close); + let lower = candle.open.min(candle.close) - candle.low; + if upper <= 0.1 * range && lower >= 0.5 * range { + return Some(1.0); + } + Some(0.0) + } + + fn reset(&mut self) { + self.has_emitted = false; + } + + fn warmup_period(&self) -> usize { + 1 + } + + fn is_ready(&self) -> bool { + self.has_emitted + } + + fn name(&self) -> &'static str { + "DragonflyDoji" + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BatchExt; + + fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle { + Candle::new(open, high, low, close, 1.0, ts).unwrap() + } + + #[test] + fn accessors_and_metadata() { + let t = DragonflyDoji::new(); + assert_eq!(t.name(), "DragonflyDoji"); + assert_eq!(t.warmup_period(), 1); + assert!(!t.is_ready()); + } + + #[test] + fn dragonfly_is_plus_one() { + let mut t = DragonflyDoji::new(); + assert_eq!(t.update(c(10.0, 10.05, 6.0, 10.0, 0)), Some(1.0)); + } + + #[test] + fn upper_shadow_yields_zero() { + let mut t = DragonflyDoji::new(); + // Long upper shadow -> not a dragonfly (this is a gravestone shape). + assert_eq!(t.update(c(10.0, 14.0, 9.95, 10.0, 0)), Some(0.0)); + } + + #[test] + fn short_lower_shadow_yields_zero() { + let mut t = DragonflyDoji::new(); + // Body at the top but the lower shadow is too short. + assert_eq!(t.update(c(10.0, 10.05, 9.6, 10.0, 0)), Some(0.0)); + } + + #[test] + fn non_doji_yields_zero() { + let mut t = DragonflyDoji::new(); + assert_eq!(t.update(c(10.0, 12.0, 6.0, 11.5, 0)), Some(0.0)); + } + + #[test] + fn zero_range_yields_zero() { + let mut t = DragonflyDoji::new(); + assert_eq!(t.update(c(10.0, 10.0, 10.0, 10.0, 0)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + c(base, base + 0.05, base - 4.0, base, i) + }) + .collect(); + let mut a = DragonflyDoji::new(); + let mut b = DragonflyDoji::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = DragonflyDoji::new(); + t.update(c(10.0, 10.05, 6.0, 10.0, 0)); + assert!(t.is_ready()); + t.reset(); + assert!(!t.is_ready()); + } +} diff --git a/crates/wickra-core/src/indicators/gravestone_doji.rs b/crates/wickra-core/src/indicators/gravestone_doji.rs new file mode 100644 index 00000000..11dd4245 --- /dev/null +++ b/crates/wickra-core/src/indicators/gravestone_doji.rs @@ -0,0 +1,163 @@ +//! Gravestone Doji candlestick pattern. + +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Gravestone Doji — a single-bar bearish reversal. Open, close, and low sit at +/// the bottom of the bar while a long upper shadow shows price was pushed up hard +/// and then sold all the way back to the open — sellers rejecting the highs. +/// +/// ```text +/// range = high − low +/// doji = |close − open| <= 0.1 * range +/// no lower wick = min(open, close) − low <= 0.1 * range +/// long upper = high − max(open, close) >= 0.5 * range +/// ``` +/// +/// Output is `−1.0` when the gravestone prints and `0.0` otherwise. Gravestone +/// Doji is a single-direction (bearish-only) shape, so it never emits `+1.0`. +/// Body and shadow thresholds follow the geometric house style (fixed fractions +/// of the bar range) rather than TA-Lib's rolling averages. Pattern-shape check +/// only — no trend filter is applied; combine with a trend indicator for +/// actionable signals. +/// +/// # Signed ±1 encoding +/// +/// This detector emits the uniform candlestick sign convention shared across the +/// pattern family — `−1.0` bearish, `0.0` no pattern — so it drops straight into +/// a machine-learning feature matrix as a single dimension. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Candle, GravestoneDoji, Indicator}; +/// +/// let mut indicator = GravestoneDoji::new(); +/// // Body at the bottom, long upper shadow. +/// let candle = Candle::new(10.0, 14.0, 9.95, 10.0, 1.0, 0).unwrap(); +/// assert_eq!(indicator.update(candle), Some(-1.0)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct GravestoneDoji { + has_emitted: bool, +} + +impl GravestoneDoji { + /// Construct a new Gravestone Doji detector. + pub const fn new() -> Self { + Self { has_emitted: false } + } +} + +impl Indicator for GravestoneDoji { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let range = candle.high - candle.low; + if range <= 0.0 { + return Some(0.0); + } + if (candle.close - candle.open).abs() > 0.1 * range { + return Some(0.0); + } + let upper = candle.high - candle.open.max(candle.close); + let lower = candle.open.min(candle.close) - candle.low; + if lower <= 0.1 * range && upper >= 0.5 * range { + return Some(-1.0); + } + Some(0.0) + } + + fn reset(&mut self) { + self.has_emitted = false; + } + + fn warmup_period(&self) -> usize { + 1 + } + + fn is_ready(&self) -> bool { + self.has_emitted + } + + fn name(&self) -> &'static str { + "GravestoneDoji" + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BatchExt; + + fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle { + Candle::new(open, high, low, close, 1.0, ts).unwrap() + } + + #[test] + fn accessors_and_metadata() { + let t = GravestoneDoji::new(); + assert_eq!(t.name(), "GravestoneDoji"); + assert_eq!(t.warmup_period(), 1); + assert!(!t.is_ready()); + } + + #[test] + fn gravestone_is_minus_one() { + let mut t = GravestoneDoji::new(); + assert_eq!(t.update(c(10.0, 14.0, 9.95, 10.0, 0)), Some(-1.0)); + } + + #[test] + fn lower_shadow_yields_zero() { + let mut t = GravestoneDoji::new(); + // Long lower shadow -> not a gravestone (this is a dragonfly shape). + assert_eq!(t.update(c(10.0, 10.05, 6.0, 10.0, 0)), Some(0.0)); + } + + #[test] + fn short_upper_shadow_yields_zero() { + let mut t = GravestoneDoji::new(); + // Body at the bottom but the upper shadow is too short. + assert_eq!(t.update(c(10.0, 10.4, 9.95, 10.0, 0)), Some(0.0)); + } + + #[test] + fn non_doji_yields_zero() { + let mut t = GravestoneDoji::new(); + assert_eq!(t.update(c(10.0, 14.0, 9.5, 13.5, 0)), Some(0.0)); + } + + #[test] + fn zero_range_yields_zero() { + let mut t = GravestoneDoji::new(); + assert_eq!(t.update(c(10.0, 10.0, 10.0, 10.0, 0)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + c(base, base + 4.0, base - 0.05, base, i) + }) + .collect(); + let mut a = GravestoneDoji::new(); + let mut b = GravestoneDoji::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = GravestoneDoji::new(); + t.update(c(10.0, 14.0, 9.95, 10.0, 0)); + assert!(t.is_ready()); + t.reset(); + assert!(!t.is_ready()); + } +} diff --git a/crates/wickra-core/src/indicators/long_legged_doji.rs b/crates/wickra-core/src/indicators/long_legged_doji.rs new file mode 100644 index 00000000..21ca48d5 --- /dev/null +++ b/crates/wickra-core/src/indicators/long_legged_doji.rs @@ -0,0 +1,157 @@ +//! Long-Legged Doji candlestick pattern. + +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Long-Legged Doji — a single-bar indecision signal. A doji with long shadows on +/// *both* sides: price ranged widely up and down yet closed essentially where it +/// opened, a tug-of-war that often precedes a turn. +/// +/// ```text +/// range = high − low +/// doji = |close − open| <= 0.1 * range +/// long upper = high − max(open, close) >= 0.3 * range +/// long lower = min(open, close) − low >= 0.3 * range +/// ``` +/// +/// Output is `+1.0` when the long-legged doji prints and `0.0` otherwise. This is +/// a non-directional indecision flag — it never emits `−1.0` (use +/// `DragonflyDoji` / `GravestoneDoji` for the directional single-shadow variants). +/// Body and shadow thresholds follow the geometric house style (fixed fractions +/// of the bar range) rather than TA-Lib's rolling averages. Pattern-shape check +/// only — no trend filter is applied; combine with a trend indicator for +/// actionable signals. +/// +/// # Signed ±1 encoding +/// +/// This detector emits the uniform candlestick sign convention shared across the +/// pattern family — `+1.0` detected, `0.0` no pattern — so it drops straight into +/// a machine-learning feature matrix as a single dimension. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Candle, LongLeggedDoji, Indicator}; +/// +/// let mut indicator = LongLeggedDoji::new(); +/// // Tiny body, long shadows on both sides. +/// let candle = Candle::new(10.0, 12.0, 8.0, 10.05, 1.0, 0).unwrap(); +/// assert_eq!(indicator.update(candle), Some(1.0)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct LongLeggedDoji { + has_emitted: bool, +} + +impl LongLeggedDoji { + /// Construct a new Long-Legged Doji detector. + pub const fn new() -> Self { + Self { has_emitted: false } + } +} + +impl Indicator for LongLeggedDoji { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let range = candle.high - candle.low; + if range <= 0.0 { + return Some(0.0); + } + if (candle.close - candle.open).abs() > 0.1 * range { + return Some(0.0); + } + let upper = candle.high - candle.open.max(candle.close); + let lower = candle.open.min(candle.close) - candle.low; + if upper >= 0.3 * range && lower >= 0.3 * range { + return Some(1.0); + } + Some(0.0) + } + + fn reset(&mut self) { + self.has_emitted = false; + } + + fn warmup_period(&self) -> usize { + 1 + } + + fn is_ready(&self) -> bool { + self.has_emitted + } + + fn name(&self) -> &'static str { + "LongLeggedDoji" + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BatchExt; + + fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle { + Candle::new(open, high, low, close, 1.0, ts).unwrap() + } + + #[test] + fn accessors_and_metadata() { + let t = LongLeggedDoji::new(); + assert_eq!(t.name(), "LongLeggedDoji"); + assert_eq!(t.warmup_period(), 1); + assert!(!t.is_ready()); + } + + #[test] + fn long_legged_is_plus_one() { + let mut t = LongLeggedDoji::new(); + assert_eq!(t.update(c(10.0, 12.0, 8.0, 10.05, 0)), Some(1.0)); + } + + #[test] + fn one_sided_shadow_yields_zero() { + let mut t = LongLeggedDoji::new(); + // Dragonfly shape: long lower shadow but no upper -> not long-legged. + assert_eq!(t.update(c(10.0, 10.05, 6.0, 10.0, 0)), Some(0.0)); + } + + #[test] + fn non_doji_yields_zero() { + let mut t = LongLeggedDoji::new(); + assert_eq!(t.update(c(10.0, 12.0, 8.0, 11.5, 0)), Some(0.0)); + } + + #[test] + fn zero_range_yields_zero() { + let mut t = LongLeggedDoji::new(); + assert_eq!(t.update(c(10.0, 10.0, 10.0, 10.0, 0)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + c(base, base + 3.0, base - 3.0, base + 0.05, i) + }) + .collect(); + let mut a = LongLeggedDoji::new(); + let mut b = LongLeggedDoji::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = LongLeggedDoji::new(); + t.update(c(10.0, 12.0, 8.0, 10.05, 0)); + assert!(t.is_ready()); + t.reset(); + assert!(!t.is_ready()); + } +} diff --git a/crates/wickra-core/src/indicators/mod.rs b/crates/wickra-core/src/indicators/mod.rs index 43a9f8b2..d7e68c63 100644 --- a/crates/wickra-core/src/indicators/mod.rs +++ b/crates/wickra-core/src/indicators/mod.rs @@ -63,10 +63,12 @@ mod demark_pivots; mod depth_slope; mod detrended_std_dev; mod doji; +mod doji_star; mod donchian; mod donchian_stop; mod double_bollinger; mod dpo; +mod dragonfly_doji; mod drawdown_duration; mod ease_of_movement; mod effective_spread; @@ -89,6 +91,7 @@ mod funding_rate_mean; mod funding_rate_zscore; mod gain_loss_ratio; mod garman_klass; +mod gravestone_doji; mod hammer; mod hanging_man; mod harami; @@ -122,6 +125,7 @@ mod linreg_angle; mod linreg_channel; mod linreg_slope; mod liquidation_features; +mod long_legged_doji; mod long_short_ratio; mod ma_envelope; mod macd; @@ -168,6 +172,7 @@ mod realized_spread; mod recovery_factor; mod relative_strength_ab; mod renko_trailing_stop; +mod rickshaw_man; mod roc; mod rogers_satchell; mod roofing_filter; @@ -317,10 +322,12 @@ pub use demark_pivots::{DemarkPivots, DemarkPivotsOutput}; pub use depth_slope::DepthSlope; pub use detrended_std_dev::DetrendedStdDev; pub use doji::Doji; +pub use doji_star::DojiStar; pub use donchian::{Donchian, DonchianOutput}; pub use donchian_stop::{DonchianStop, DonchianStopOutput}; pub use double_bollinger::{DoubleBollinger, DoubleBollingerOutput}; pub use dpo::Dpo; +pub use dragonfly_doji::DragonflyDoji; pub use drawdown_duration::DrawdownDuration; pub use ease_of_movement::EaseOfMovement; pub use effective_spread::EffectiveSpread; @@ -343,6 +350,7 @@ pub use funding_rate_mean::FundingRateMean; pub use funding_rate_zscore::FundingRateZScore; pub use gain_loss_ratio::GainLossRatio; pub use garman_klass::GarmanKlassVolatility; +pub use gravestone_doji::GravestoneDoji; pub use hammer::Hammer; pub use hanging_man::HangingMan; pub use harami::Harami; @@ -376,6 +384,7 @@ pub use linreg_angle::LinRegAngle; pub use linreg_channel::{LinRegChannel, LinRegChannelOutput}; pub use linreg_slope::LinRegSlope; pub use liquidation_features::{LiquidationFeatures, LiquidationFeaturesOutput}; +pub use long_legged_doji::LongLeggedDoji; pub use long_short_ratio::LongShortRatio; pub use ma_envelope::{MaEnvelope, MaEnvelopeOutput}; pub use macd::{MacdIndicator, MacdOutput}; @@ -422,6 +431,7 @@ pub use realized_spread::RealizedSpread; pub use recovery_factor::RecoveryFactor; pub use relative_strength_ab::{RelativeStrengthAB, RelativeStrengthOutput}; pub use renko_trailing_stop::RenkoTrailingStop; +pub use rickshaw_man::RickshawMan; pub use roc::Roc; pub use rogers_satchell::RogersSatchellVolatility; pub use roofing_filter::RoofingFilter; @@ -785,6 +795,11 @@ pub const FAMILIES: &[(&str, &[&str])] = &[ "BeltHold", "Breakaway", "Counterattack", + "DojiStar", + "DragonflyDoji", + "GravestoneDoji", + "LongLeggedDoji", + "RickshawMan", ], ), ( @@ -876,6 +891,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, 249, "FAMILIES total drifted from indicator count"); + assert_eq!(total, 254, "FAMILIES total drifted from indicator count"); } } diff --git a/crates/wickra-core/src/indicators/rickshaw_man.rs b/crates/wickra-core/src/indicators/rickshaw_man.rs new file mode 100644 index 00000000..dbfb36b4 --- /dev/null +++ b/crates/wickra-core/src/indicators/rickshaw_man.rs @@ -0,0 +1,167 @@ +//! Rickshaw Man candlestick pattern. + +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Rickshaw Man — a single-bar indecision signal. A long-legged doji whose tiny +/// body sits near the *middle* of a wide range, the most balanced form of +/// indecision: neither side controlled the close and the midpoint pins it. +/// +/// ```text +/// range = high − low +/// doji = |close − open| <= 0.1 * range +/// long upper = high − max(open, close) >= 0.3 * range +/// long lower = min(open, close) − low >= 0.3 * range +/// centred body = body midpoint within the central 40–60 % of the range +/// ``` +/// +/// Output is `+1.0` when the rickshaw man prints and `0.0` otherwise. This is a +/// non-directional indecision flag — it never emits `−1.0`. A rickshaw man is a +/// special case of a long-legged doji (the body additionally sits at the centre), +/// so both detectors may flag the same bar. Body and shadow thresholds follow the +/// geometric house style (fixed fractions of the bar range) rather than TA-Lib's +/// rolling averages. Pattern-shape check only — no trend filter is applied; +/// combine with a trend indicator for actionable signals. +/// +/// # Signed ±1 encoding +/// +/// This detector emits the uniform candlestick sign convention shared across the +/// pattern family — `+1.0` detected, `0.0` no pattern — so it drops straight into +/// a machine-learning feature matrix as a single dimension. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Candle, Indicator, RickshawMan}; +/// +/// let mut indicator = RickshawMan::new(); +/// // Tiny body centred in a wide range, long shadows both sides. +/// let candle = Candle::new(10.0, 12.0, 8.0, 10.0, 1.0, 0).unwrap(); +/// assert_eq!(indicator.update(candle), Some(1.0)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct RickshawMan { + has_emitted: bool, +} + +impl RickshawMan { + /// Construct a new Rickshaw Man detector. + pub const fn new() -> Self { + Self { has_emitted: false } + } +} + +impl Indicator for RickshawMan { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let range = candle.high - candle.low; + if range <= 0.0 { + return Some(0.0); + } + if (candle.close - candle.open).abs() > 0.1 * range { + return Some(0.0); + } + let upper = candle.high - candle.open.max(candle.close); + let lower = candle.open.min(candle.close) - candle.low; + let body_mid = 0.5 * (candle.open + candle.close); + let pos = (body_mid - candle.low) / range; + if upper >= 0.3 * range && lower >= 0.3 * range && (0.4..=0.6).contains(&pos) { + return Some(1.0); + } + Some(0.0) + } + + fn reset(&mut self) { + self.has_emitted = false; + } + + fn warmup_period(&self) -> usize { + 1 + } + + fn is_ready(&self) -> bool { + self.has_emitted + } + + fn name(&self) -> &'static str { + "RickshawMan" + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BatchExt; + + fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle { + Candle::new(open, high, low, close, 1.0, ts).unwrap() + } + + #[test] + fn accessors_and_metadata() { + let t = RickshawMan::new(); + assert_eq!(t.name(), "RickshawMan"); + assert_eq!(t.warmup_period(), 1); + assert!(!t.is_ready()); + } + + #[test] + fn rickshaw_is_plus_one() { + let mut t = RickshawMan::new(); + assert_eq!(t.update(c(10.0, 12.0, 8.0, 10.0, 0)), Some(1.0)); + } + + #[test] + fn off_centre_body_yields_zero() { + let mut t = RickshawMan::new(); + // Long-legged but the body sits near the top, not the middle. + assert_eq!(t.update(c(11.4, 12.0, 8.0, 11.45, 0)), Some(0.0)); + } + + #[test] + fn one_sided_shadow_yields_zero() { + let mut t = RickshawMan::new(); + // Dragonfly shape: no upper shadow -> not a rickshaw man. + assert_eq!(t.update(c(10.0, 10.05, 6.0, 10.0, 0)), Some(0.0)); + } + + #[test] + fn non_doji_yields_zero() { + let mut t = RickshawMan::new(); + assert_eq!(t.update(c(9.0, 12.0, 8.0, 11.0, 0)), Some(0.0)); + } + + #[test] + fn zero_range_yields_zero() { + let mut t = RickshawMan::new(); + assert_eq!(t.update(c(10.0, 10.0, 10.0, 10.0, 0)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + c(base, base + 2.0, base - 2.0, base + 0.05, i) + }) + .collect(); + let mut a = RickshawMan::new(); + let mut b = RickshawMan::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = RickshawMan::new(); + t.update(c(10.0, 12.0, 8.0, 10.0, 0)); + assert!(t.is_ready()); + t.reset(); + assert!(!t.is_ready()); + } +} diff --git a/crates/wickra-core/src/lib.rs b/crates/wickra-core/src/lib.rs index 28a244d6..09a1b692 100644 --- a/crates/wickra-core/src/lib.rs +++ b/crates/wickra-core/src/lib.rs @@ -64,44 +64,45 @@ pub use indicators::{ ClassicPivotsOutput, Cmo, CoefficientOfVariation, Cointegration, CointegrationOutput, ConditionalValueAtRisk, ConnorsRsi, Coppock, Counterattack, CumulativeVolumeDelta, CyberneticCycle, Decycler, DecyclerOscillator, Dema, DemandIndex, DemarkPivots, - DemarkPivotsOutput, DepthSlope, DetrendedStdDev, Doji, Donchian, DonchianOutput, DonchianStop, - DonchianStopOutput, DoubleBollinger, DoubleBollingerOutput, Dpo, DrawdownDuration, - EaseOfMovement, EffectiveSpread, EhlersStochastic, ElderImpulse, Ema, + DemarkPivotsOutput, DepthSlope, DetrendedStdDev, Doji, DojiStar, Donchian, DonchianOutput, + DonchianStop, DonchianStopOutput, DoubleBollinger, DoubleBollingerOutput, Dpo, DragonflyDoji, + DrawdownDuration, EaseOfMovement, EffectiveSpread, EhlersStochastic, ElderImpulse, Ema, EmpiricalModeDecomposition, Engulfing, Evwma, Fama, FibonacciPivots, FibonacciPivotsOutput, FisherTransform, Footprint, FootprintOutput, ForceIndex, FractalChaosBands, FractalChaosBandsOutput, Frama, FundingBasis, FundingRate, FundingRateMean, FundingRateZScore, - GainLossRatio, GarmanKlassVolatility, Hammer, HangingMan, Harami, HeikinAshi, HeikinAshiOutput, - HiLoActivator, HilbertDominantCycle, HistoricalVolatility, Hma, HurstChannel, + GainLossRatio, GarmanKlassVolatility, GravestoneDoji, Hammer, HangingMan, Harami, HeikinAshi, + HeikinAshiOutput, HiLoActivator, HilbertDominantCycle, HistoricalVolatility, Hma, HurstChannel, HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput, IdenticalThreeCrows, Inertia, InformationRatio, InitialBalance, InitialBalanceOutput, InstantaneousTrendline, InverseFisherTransform, InvertedHammer, Jma, Kama, KellyCriterion, Keltner, KeltnerOutput, Kst, KstOutput, Kurtosis, Kvo, KylesLambda, LaguerreRsi, LeadLagCrossCorrelation, LeadLagCrossCorrelationOutput, LinRegAngle, LinRegChannel, LinRegChannelOutput, LinRegSlope, - LinearRegression, LiquidationFeatures, LiquidationFeaturesOutput, LongShortRatio, MaEnvelope, - MaEnvelopeOutput, MacdIndicator, MacdOutput, Mama, MamaOutput, MarketFacilitationIndex, - Marubozu, MassIndex, MaxDrawdown, McGinleyDynamic, MedianAbsoluteDeviation, MedianPrice, Mfi, - Microprice, Mom, MorningEveningStar, Natr, Nvi, OIPriceDivergence, OIWeighted, Obv, OmegaRatio, - OpenInterestDelta, OpeningRange, OpeningRangeOutput, OrderBookImbalanceFull, - OrderBookImbalanceTop1, OrderBookImbalanceTopN, PainIndex, PairSpreadZScore, PairwiseBeta, - ParkinsonVolatility, PearsonCorrelation, PercentB, PercentageTrailingStop, Pgo, - PiercingDarkCloud, Pmo, Ppo, ProfitFactor, Psar, Pvi, QuotedSpread, RSquared, RealizedSpread, - RecoveryFactor, RelativeStrengthAB, RelativeStrengthOutput, RenkoTrailingStop, Roc, - RogersSatchellVolatility, RollingVwap, RoofingFilter, Rsi, Rvi, RviVolatility, Rwi, RwiOutput, - SharpeRatio, ShootingStar, SignedVolume, SineWave, Skewness, Sma, Smi, Smma, SortinoRatio, - SpearmanCorrelation, SpinningTop, StandardError, StandardErrorBands, StandardErrorBandsOutput, - StarcBands, StarcBandsOutput, Stc, StdDev, StepTrailingStop, StochRsi, Stochastic, - StochasticOutput, SuperSmoother, SuperTrend, SuperTrendOutput, TakerBuySellRatio, TdCombo, - TdCountdown, TdDeMarker, TdDifferential, TdLines, TdLinesOutput, TdOpen, TdPressure, - TdRangeProjection, TdRangeProjectionOutput, TdRei, TdRiskLevel, TdRiskLevelOutput, - TdSequential, TdSequentialOutput, TdSetup, Tema, TermStructureBasis, ThreeInside, - ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, Tii, TradeImbalance, - TreynorRatio, Trima, Trix, TrueRange, Tsi, Tsv, TtmSqueeze, TtmSqueezeOutput, Tweezer, - TwoCrows, TypicalPrice, UlcerIndex, UltimateOscillator, UpsideGapTwoCrows, ValueArea, - ValueAreaOutput, ValueAtRisk, Variance, VerticalHorizontalFilter, Vidya, VoltyStop, - VolumeOscillator, VolumePriceTrend, Vortex, VortexOutput, Vwap, VwapStdDevBands, - VwapStdDevBandsOutput, Vwma, Vzo, WaveTrend, WaveTrendOutput, WeightedClose, WilliamsFractals, - WilliamsFractalsOutput, WilliamsR, Wma, WoodiePivots, WoodiePivotsOutput, YangZhangVolatility, - YoyoExit, ZScore, ZeroLagMacd, ZeroLagMacdOutput, ZigZag, ZigZagOutput, Zlema, FAMILIES, T3, + LinearRegression, LiquidationFeatures, LiquidationFeaturesOutput, LongLeggedDoji, + LongShortRatio, MaEnvelope, MaEnvelopeOutput, MacdIndicator, MacdOutput, Mama, MamaOutput, + MarketFacilitationIndex, Marubozu, MassIndex, MaxDrawdown, McGinleyDynamic, + MedianAbsoluteDeviation, MedianPrice, Mfi, Microprice, Mom, MorningEveningStar, Natr, Nvi, + OIPriceDivergence, OIWeighted, Obv, OmegaRatio, OpenInterestDelta, OpeningRange, + OpeningRangeOutput, OrderBookImbalanceFull, OrderBookImbalanceTop1, OrderBookImbalanceTopN, + PainIndex, PairSpreadZScore, PairwiseBeta, ParkinsonVolatility, PearsonCorrelation, PercentB, + PercentageTrailingStop, Pgo, PiercingDarkCloud, Pmo, Ppo, ProfitFactor, Psar, Pvi, + QuotedSpread, RSquared, RealizedSpread, RecoveryFactor, RelativeStrengthAB, + RelativeStrengthOutput, RenkoTrailingStop, RickshawMan, Roc, RogersSatchellVolatility, + RollingVwap, RoofingFilter, Rsi, Rvi, RviVolatility, Rwi, RwiOutput, SharpeRatio, ShootingStar, + SignedVolume, SineWave, Skewness, Sma, Smi, Smma, SortinoRatio, SpearmanCorrelation, + SpinningTop, StandardError, StandardErrorBands, StandardErrorBandsOutput, StarcBands, + StarcBandsOutput, Stc, StdDev, StepTrailingStop, StochRsi, Stochastic, StochasticOutput, + SuperSmoother, SuperTrend, SuperTrendOutput, TakerBuySellRatio, TdCombo, TdCountdown, + TdDeMarker, TdDifferential, TdLines, TdLinesOutput, TdOpen, TdPressure, TdRangeProjection, + TdRangeProjectionOutput, TdRei, TdRiskLevel, TdRiskLevelOutput, TdSequential, + TdSequentialOutput, TdSetup, Tema, TermStructureBasis, ThreeInside, ThreeLineStrike, + ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, Tii, TradeImbalance, TreynorRatio, + Trima, Trix, TrueRange, Tsi, Tsv, TtmSqueeze, TtmSqueezeOutput, Tweezer, TwoCrows, + TypicalPrice, UlcerIndex, UltimateOscillator, UpsideGapTwoCrows, ValueArea, ValueAreaOutput, + ValueAtRisk, Variance, VerticalHorizontalFilter, Vidya, VoltyStop, VolumeOscillator, + VolumePriceTrend, Vortex, VortexOutput, Vwap, VwapStdDevBands, VwapStdDevBandsOutput, Vwma, + Vzo, WaveTrend, WaveTrendOutput, WeightedClose, WilliamsFractals, WilliamsFractalsOutput, + WilliamsR, Wma, WoodiePivots, WoodiePivotsOutput, YangZhangVolatility, YoyoExit, ZScore, + ZeroLagMacd, ZeroLagMacdOutput, ZigZag, ZigZagOutput, Zlema, FAMILIES, T3, }; // `FootprintLevel` is a row element of `FootprintOutput`, re-exported on its own // line so the indicator-count tooling (which scans the braced block above and diff --git a/docs/README.md b/docs/README.md index eea4a319..925f521e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ That includes: [Python](https://docs.wickra.org/Quickstart-Python), [Node](https://docs.wickra.org/Quickstart-Node), and [WASM](https://docs.wickra.org/Quickstart-WASM). -- A per-indicator deep dive for every one of the **254 indicators** across +- A per-indicator deep dive for every one of the **259 indicators** across the sixteen families (Moving Averages, Momentum Oscillators, Trend & Directional, Price Oscillators, Volatility & Bands, Bands & Channels, Trailing Stops, Volume, Price Statistics, Ehlers / Cycle DSP, Pivots & diff --git a/fuzz/fuzz_targets/indicator_update_candle.rs b/fuzz/fuzz_targets/indicator_update_candle.rs index 0abb9bc1..106bd37d 100644 --- a/fuzz/fuzz_targets/indicator_update_candle.rs +++ b/fuzz/fuzz_targets/indicator_update_candle.rs @@ -23,7 +23,7 @@ use libfuzzer_sys::fuzz_target; use wickra_core::{ -AbandonedBaby, AccelerationBands, AcceleratorOscillator, AdOscillator, Adl, AdvanceBlock, Adx, Adxr, Alligator, AnchoredVwap, Aroon, AroonOscillator, Atr, AtrBands, AtrTrailingStop, AwesomeOscillator, AwesomeOscillatorHistogram, BalanceOfPower, BatchExt, BeltHold, Breakaway, Camarilla, Candle, Cci, ChaikinMoneyFlow, ChaikinOscillator, ChaikinVolatility, ChandeKrollStop, ChandelierExit, ChoppinessIndex, ClassicPivots, Counterattack, DemandIndex, DemarkPivots, Doji, Donchian, DonchianStop, EaseOfMovement, Engulfing, Evwma, FibonacciPivots, ForceIndex, FractalChaosBands, GarmanKlassVolatility, Hammer, HangingMan, Harami, HeikinAshi, HiLoActivator, HurstChannel, Ichimoku, IdenticalThreeCrows, Indicator, Inertia, InitialBalance, InvertedHammer, Keltner, Kvo, MarketFacilitationIndex, Marubozu, MassIndex, MedianPrice, Mfi, MorningEveningStar, Natr, Nvi, Obv, OpeningRange, ParkinsonVolatility, Pgo, PiercingDarkCloud, Psar, Pvi, RogersSatchellVolatility, RollingVwap, Rvi, Rwi, ShootingStar, Smi, SpinningTop, StarcBands, Stochastic, SuperTrend, TdCombo, TdCountdown, TdDeMarker, TdDifferential, TdLines, TdOpen, TdPressure, TdRangeProjection, TdRei, TdRiskLevel, TdSequential, TdSetup, ThreeInside, ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, TrueRange, Tsv, TtmSqueeze, Tweezer, TwoCrows, TypicalPrice, UltimateOscillator, UpsideGapTwoCrows, ValueArea, VoltyStop, VolumeOscillator, VolumePriceTrend, Vortex, Vwap, VwapStdDevBands, Vwma, Vzo, WaveTrend, WeightedClose, WilliamsFractals, WilliamsR, WoodiePivots, YangZhangVolatility, YoyoExit, ZigZag +AbandonedBaby, AccelerationBands, AcceleratorOscillator, AdOscillator, Adl, AdvanceBlock, Adx, Adxr, Alligator, AnchoredVwap, Aroon, AroonOscillator, Atr, AtrBands, AtrTrailingStop, AwesomeOscillator, AwesomeOscillatorHistogram, BalanceOfPower, BatchExt, BeltHold, Breakaway, Camarilla, Candle, Cci, ChaikinMoneyFlow, ChaikinOscillator, ChaikinVolatility, ChandeKrollStop, ChandelierExit, ChoppinessIndex, ClassicPivots, Counterattack, DemandIndex, DemarkPivots, Doji, DojiStar, Donchian, DonchianStop, DragonflyDoji, EaseOfMovement, Engulfing, Evwma, FibonacciPivots, ForceIndex, FractalChaosBands, GarmanKlassVolatility, GravestoneDoji, Hammer, HangingMan, Harami, HeikinAshi, HiLoActivator, HurstChannel, Ichimoku, IdenticalThreeCrows, Indicator, Inertia, InitialBalance, InvertedHammer, Keltner, Kvo, LongLeggedDoji, MarketFacilitationIndex, Marubozu, MassIndex, MedianPrice, Mfi, MorningEveningStar, Natr, Nvi, Obv, OpeningRange, ParkinsonVolatility, Pgo, PiercingDarkCloud, Psar, Pvi, RickshawMan, RogersSatchellVolatility, RollingVwap, Rvi, Rwi, ShootingStar, Smi, SpinningTop, StarcBands, Stochastic, SuperTrend, TdCombo, TdCountdown, TdDeMarker, TdDifferential, TdLines, TdOpen, TdPressure, TdRangeProjection, TdRei, TdRiskLevel, TdSequential, TdSetup, ThreeInside, ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, TrueRange, Tsv, TtmSqueeze, Tweezer, TwoCrows, TypicalPrice, UltimateOscillator, UpsideGapTwoCrows, ValueArea, VoltyStop, VolumeOscillator, VolumePriceTrend, Vortex, Vwap, VwapStdDevBands, Vwma, Vzo, WaveTrend, WeightedClose, WilliamsFractals, WilliamsR, WoodiePivots, YangZhangVolatility, YoyoExit, ZigZag }; /// Convert a flat `f64` stream into a `Vec` by chunking it into @@ -278,6 +278,11 @@ fuzz_target!(|data: Vec| { } // --- Candlestick Patterns (family 14) --- + drive(RickshawMan::new, &candles); + drive(LongLeggedDoji::new, &candles); + drive(GravestoneDoji::new, &candles); + drive(DragonflyDoji::new, &candles); + drive(DojiStar::new, &candles); drive(Counterattack::new, &candles); drive(Breakaway::new, &candles); drive(BeltHold::new, &candles);