diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84d78867..65e67adc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -47,6 +47,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Gap Side-by-Side White** — two similar white candles opening side by side after a gap, a continuation; gap up +1, gap down -1 (`CDLGAPSIDESIDEWHITE`).
- **High-Wave** — a small body with very long shadows on both sides, an extreme indecision signal; +1 detection (`CDLHIGHWAVE`).
- **Hikkake** — an inside bar followed by a failed breakout, a trap; bullish +1, bearish -1 (`CDLHIKKAKE`).
+ - **Modified Hikkake** — a close-confirmed Hikkake: an inside bar then a failed breakout closing back inside; bullish +1, bearish -1 (`CDLHIKKAKEMOD`).
+ - **Homing Pigeon** — two black candles, the second a small body inside the first, a bullish reversal; +1 (`CDLHOMINGPIGEON`).
+ - **On-Neck** — a long black candle then a white candle closing at its low (the neckline), a bearish continuation; -1 (`CDLONNECK`).
+ - **In-Neck** — a long black candle then a white candle closing just into its body, a bearish continuation; -1 (`CDLINNECK`).
+ - **Thrusting** — a long black candle then a white candle closing well into but below the midpoint of its body, a bearish continuation; -1 (`CDLTHRUSTING`).
- **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 a5c81199..218104ee 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
[](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 264 indicators; start at the
+ every one of the 269 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
-264 streaming-first indicators across eighteen families. Every one passes the
+269 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, Doji Star, Dragonfly Doji, Gravestone Doji, Long-Legged Doji, Rickshaw Man, Evening Doji Star, Morning Doji Star, Gap Side-by-Side White, High-Wave, Hikkake |
+| 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, Evening Doji Star, Morning Doji Star, Gap Side-by-Side White, High-Wave, Hikkake, Modified Hikkake, Homing Pigeon, On-Neck, In-Neck, Thrusting |
| 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 264 indicators
+│ ├── wickra-core/ core engine + all 269 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 844d572c..7baf975c 100644
--- a/bindings/node/__tests__/indicators.test.js
+++ b/bindings/node/__tests__/indicators.test.js
@@ -255,6 +255,11 @@ const candleScalar = {
GapSideBySideWhite: { make: () => new wickra.GapSideBySideWhite(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
HighWave: { make: () => new wickra.HighWave(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
Hikkake: { make: () => new wickra.Hikkake(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
+ HikkakeModified: { make: () => new wickra.HikkakeModified(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
+ HomingPigeon: { make: () => new wickra.HomingPigeon(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
+ OnNeck: { make: () => new wickra.OnNeck(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
+ InNeck: { make: () => new wickra.InNeck(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
+ Thrusting: { make: () => new wickra.Thrusting(), 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 df226fd0..3fdda389 100644
--- a/bindings/node/index.d.ts
+++ b/bindings/node/index.d.ts
@@ -2390,6 +2390,51 @@ export declare class Hikkake {
isReady(): boolean
warmupPeriod(): number
}
+export type HikkakeModifiedNode = HikkakeModified
+export declare class HikkakeModified {
+ 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 HomingPigeonNode = HomingPigeon
+export declare class HomingPigeon {
+ 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 OnNeckNode = OnNeck
+export declare class OnNeck {
+ 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 InNeckNode = InNeck
+export declare class InNeck {
+ 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 ThrustingNode = Thrusting
+export declare class Thrusting {
+ 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 74ac4a94..95892149 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, DojiStar, DragonflyDoji, GravestoneDoji, LongLeggedDoji, RickshawMan, EveningDojiStar, MorningDojiStar, GapSideBySideWhite, HighWave, Hikkake, 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, EveningDojiStar, MorningDojiStar, GapSideBySideWhite, HighWave, Hikkake, HikkakeModified, HomingPigeon, OnNeck, InNeck, Thrusting, 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
@@ -535,6 +535,11 @@ module.exports.MorningDojiStar = MorningDojiStar
module.exports.GapSideBySideWhite = GapSideBySideWhite
module.exports.HighWave = HighWave
module.exports.Hikkake = Hikkake
+module.exports.HikkakeModified = HikkakeModified
+module.exports.HomingPigeon = HomingPigeon
+module.exports.OnNeck = OnNeck
+module.exports.InNeck = InNeck
+module.exports.Thrusting = Thrusting
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 bd5d89c6..3a91c182 100644
--- a/bindings/node/src/lib.rs
+++ b/bindings/node/src/lib.rs
@@ -8789,6 +8789,11 @@ node_candle_pattern!(
);
node_candle_pattern!(HighWaveNode, wc::HighWave, "HighWave");
node_candle_pattern!(HikkakeNode, wc::Hikkake, "Hikkake");
+node_candle_pattern!(HikkakeModifiedNode, wc::HikkakeModified, "HikkakeModified");
+node_candle_pattern!(HomingPigeonNode, wc::HomingPigeon, "HomingPigeon");
+node_candle_pattern!(OnNeckNode, wc::OnNeck, "OnNeck");
+node_candle_pattern!(InNeckNode, wc::InNeck, "InNeck");
+node_candle_pattern!(ThrustingNode, wc::Thrusting, "Thrusting");
// ============================== Microstructure: Order Book ==============================
//
diff --git a/bindings/python/python/wickra/__init__.py b/bindings/python/python/wickra/__init__.py
index a43a4677..28bd6f54 100644
--- a/bindings/python/python/wickra/__init__.py
+++ b/bindings/python/python/wickra/__init__.py
@@ -260,6 +260,11 @@ from ._wickra import (
GapSideBySideWhite,
HighWave,
Hikkake,
+ HikkakeModified,
+ HomingPigeon,
+ OnNeck,
+ InNeck,
+ Thrusting,
# Microstructure: order book
OrderBookImbalanceTop1,
OrderBookImbalanceTopN,
@@ -547,6 +552,11 @@ __all__ = [
"GapSideBySideWhite",
"HighWave",
"Hikkake",
+ "HikkakeModified",
+ "HomingPigeon",
+ "OnNeck",
+ "InNeck",
+ "Thrusting",
# Microstructure: order book
"OrderBookImbalanceTop1",
"OrderBookImbalanceTopN",
diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs
index 94e3b15a..e89a996b 100644
--- a/bindings/python/src/lib.rs
+++ b/bindings/python/src/lib.rs
@@ -11652,6 +11652,11 @@ candle_pattern_no_param!(
);
candle_pattern_no_param!(PyHighWave, wc::HighWave, "HighWave");
candle_pattern_no_param!(PyHikkake, wc::Hikkake, "Hikkake");
+candle_pattern_no_param!(PyHikkakeModified, wc::HikkakeModified, "HikkakeModified");
+candle_pattern_no_param!(PyHomingPigeon, wc::HomingPigeon, "HomingPigeon");
+candle_pattern_no_param!(PyOnNeck, wc::OnNeck, "OnNeck");
+candle_pattern_no_param!(PyInNeck, wc::InNeck, "InNeck");
+candle_pattern_no_param!(PyThrusting, wc::Thrusting, "Thrusting");
// ============================== Microstructure: Order Book ==============================
//
// Order-book indicators consume a depth snapshot rather than OHLCV. Streaming
@@ -14168,6 +14173,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 8bf9c7bf..52ef4a68 100644
--- a/bindings/python/tests/test_new_indicators.py
+++ b/bindings/python/tests/test_new_indicators.py
@@ -603,6 +603,26 @@ CANDLE_SCALAR = {
lambda: ta.Hikkake(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
+ "HikkakeModified": (
+ lambda: ta.HikkakeModified(),
+ lambda ind, h, l, c, v: ind.batch(c, h, l, c),
+ ),
+ "HomingPigeon": (
+ lambda: ta.HomingPigeon(),
+ lambda ind, h, l, c, v: ind.batch(c, h, l, c),
+ ),
+ "OnNeck": (
+ lambda: ta.OnNeck(),
+ lambda ind, h, l, c, v: ind.batch(c, h, l, c),
+ ),
+ "InNeck": (
+ lambda: ta.InNeck(),
+ lambda ind, h, l, c, v: ind.batch(c, h, l, c),
+ ),
+ "Thrusting": (
+ lambda: ta.Thrusting(),
+ lambda ind, h, l, c, v: ind.batch(c, h, l, c),
+ ),
}
@@ -1784,6 +1804,37 @@ def test_hikkake_reference():
assert t.update((11.0, 13.0, 8.0, 12.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((9.0, 12.0, 6.0, 7.0, 1.0, 2)) == pytest.approx(1.0)
+
+def test_hikkake_modified_reference():
+ t = ta.HikkakeModified()
+ assert t.update((10.0, 15.0, 5.0, 12.0, 1.0, 0)) == pytest.approx(0.0)
+ assert t.update((11.0, 13.0, 8.0, 12.0, 1.0, 1)) == pytest.approx(0.0)
+ assert t.update((9.0, 12.0, 6.0, 9.0, 1.0, 2)) == pytest.approx(1.0)
+
+
+def test_homing_pigeon_reference():
+ t = ta.HomingPigeon()
+ assert t.update((15.0, 15.1, 9.9, 10.0, 1.0, 0)) == pytest.approx(0.0)
+ assert t.update((14.0, 14.1, 10.9, 11.0, 1.0, 1)) == pytest.approx(1.0)
+
+
+def test_on_neck_reference():
+ t = ta.OnNeck()
+ assert t.update((15.0, 15.1, 9.0, 10.0, 1.0, 0)) == pytest.approx(0.0)
+ assert t.update((7.0, 9.1, 6.9, 9.0, 1.0, 1)) == pytest.approx(-1.0)
+
+
+def test_in_neck_reference():
+ t = ta.InNeck()
+ assert t.update((15.0, 15.1, 9.0, 10.0, 1.0, 0)) == pytest.approx(0.0)
+ assert t.update((7.0, 10.3, 6.9, 10.2, 1.0, 1)) == pytest.approx(-1.0)
+
+
+def test_thrusting_reference():
+ t = ta.Thrusting()
+ assert t.update((15.0, 15.1, 9.0, 10.0, 1.0, 0)) == pytest.approx(0.0)
+ assert t.update((7.0, 11.6, 6.9, 11.5, 1.0, 1)) == pytest.approx(-1.0)
+
# --- Lifecycle ------------------------------------------------------------
diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs
index b3626b10..d2fffe77 100644
--- a/bindings/wasm/src/lib.rs
+++ b/bindings/wasm/src/lib.rs
@@ -6366,6 +6366,11 @@ wasm_candle_pattern!(
);
wasm_candle_pattern!(WasmHighWave, wc::HighWave, HighWave);
wasm_candle_pattern!(WasmHikkake, wc::Hikkake, Hikkake);
+wasm_candle_pattern!(WasmHikkakeModified, wc::HikkakeModified, HikkakeModified);
+wasm_candle_pattern!(WasmHomingPigeon, wc::HomingPigeon, HomingPigeon);
+wasm_candle_pattern!(WasmOnNeck, wc::OnNeck, OnNeck);
+wasm_candle_pattern!(WasmInNeck, wc::InNeck, InNeck);
+wasm_candle_pattern!(WasmThrusting, wc::Thrusting, Thrusting);
// ============================== Microstructure: Order Book ==============================
//
diff --git a/crates/wickra-core/src/indicators/hikkake_modified.rs b/crates/wickra-core/src/indicators/hikkake_modified.rs
new file mode 100644
index 00000000..e757800b
--- /dev/null
+++ b/crates/wickra-core/src/indicators/hikkake_modified.rs
@@ -0,0 +1,196 @@
+//! Modified Hikkake candlestick pattern.
+
+use crate::ohlcv::Candle;
+use crate::traits::Indicator;
+
+/// Modified Hikkake — a close-confirmed variant of the [`Hikkake`](crate::Hikkake)
+/// trap. An inside bar is followed by a bar that breaks out *and is immediately
+/// rejected*: it pierces the inside bar's range intrabar but closes back inside,
+/// a stronger signal than the plain breakout setup.
+///
+/// ```text
+/// inside bar : bar2.high < bar1.high && bar2.low > bar1.low
+/// bullish (+1.0): bar3 makes a lower high AND lower low than bar2,
+/// yet closes back above the inside-bar low (close3 > bar2.low)
+/// bearish (−1.0): bar3 makes a higher high AND higher low than bar2,
+/// yet closes back below the inside-bar high (close3 < bar2.high)
+/// ```
+///
+/// Output is `+1.0` (bullish), `−1.0` (bearish), or `0.0` otherwise. The extra
+/// close-recovery condition is what distinguishes it from the plain Hikkake, which
+/// fires on the high/low break alone. The first two bars always return `0.0`
+/// because the three-bar window is not yet filled. 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 as a single dimension.
+///
+/// # Example
+///
+/// ```
+/// use wickra_core::{Candle, HikkakeModified, Indicator};
+///
+/// let mut indicator = HikkakeModified::new();
+/// indicator.update(Candle::new(10.0, 15.0, 5.0, 12.0, 1.0, 0).unwrap());
+/// indicator.update(Candle::new(11.0, 13.0, 8.0, 12.0, 1.0, 1).unwrap());
+/// let out = indicator
+/// .update(Candle::new(9.0, 12.0, 6.0, 9.0, 1.0, 2).unwrap());
+/// assert_eq!(out, Some(1.0));
+/// ```
+#[derive(Debug, Clone, Default)]
+pub struct HikkakeModified {
+ prev: Option,
+ prev_prev: Option,
+ has_emitted: bool,
+}
+
+impl HikkakeModified {
+ /// Construct a new Modified Hikkake detector.
+ pub const fn new() -> Self {
+ Self {
+ prev: None,
+ prev_prev: None,
+ has_emitted: false,
+ }
+ }
+}
+
+impl Indicator for HikkakeModified {
+ type Input = Candle;
+ type Output = f64;
+
+ fn update(&mut self, candle: Candle) -> Option {
+ self.has_emitted = true;
+ let bar1 = self.prev_prev;
+ let bar2 = self.prev;
+ self.prev_prev = self.prev;
+ self.prev = Some(candle);
+ let (Some(bar1), Some(bar2)) = (bar1, bar2) else {
+ return Some(0.0);
+ };
+ if !(bar2.high < bar1.high && bar2.low > bar1.low) {
+ return Some(0.0);
+ }
+ // Bullish: false downside break that closes back above the inside-bar low.
+ if candle.high < bar2.high && candle.low < bar2.low && candle.close > bar2.low {
+ return Some(1.0);
+ }
+ // Bearish: false upside break that closes back below the inside-bar high.
+ if candle.high > bar2.high && candle.low > bar2.low && candle.close < bar2.high {
+ return Some(-1.0);
+ }
+ Some(0.0)
+ }
+
+ fn reset(&mut self) {
+ self.prev = None;
+ self.prev_prev = None;
+ self.has_emitted = false;
+ }
+
+ fn warmup_period(&self) -> usize {
+ 3
+ }
+
+ fn is_ready(&self) -> bool {
+ self.has_emitted
+ }
+
+ fn name(&self) -> &'static str {
+ "HikkakeModified"
+ }
+}
+
+#[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 = HikkakeModified::new();
+ assert_eq!(t.name(), "HikkakeModified");
+ assert_eq!(t.warmup_period(), 3);
+ assert!(!t.is_ready());
+ }
+
+ #[test]
+ fn bullish_modified_hikkake_is_plus_one() {
+ let mut t = HikkakeModified::new();
+ assert_eq!(t.update(c(10.0, 15.0, 5.0, 12.0, 0)), Some(0.0));
+ assert_eq!(t.update(c(11.0, 13.0, 8.0, 12.0, 1)), Some(0.0));
+ assert_eq!(t.update(c(9.0, 12.0, 6.0, 9.0, 2)), Some(1.0));
+ }
+
+ #[test]
+ fn bearish_modified_hikkake_is_minus_one() {
+ let mut t = HikkakeModified::new();
+ assert_eq!(t.update(c(10.0, 15.0, 5.0, 12.0, 0)), Some(0.0));
+ assert_eq!(t.update(c(11.0, 13.0, 8.0, 12.0, 1)), Some(0.0));
+ assert_eq!(t.update(c(13.0, 14.0, 9.0, 10.0, 2)), Some(-1.0));
+ }
+
+ #[test]
+ fn break_without_close_recovery_yields_zero() {
+ let mut t = HikkakeModified::new();
+ t.update(c(10.0, 15.0, 5.0, 12.0, 0));
+ t.update(c(11.0, 13.0, 8.0, 12.0, 1));
+ // Lower high and lower low, but closes below the inside-bar low -> plain
+ // Hikkake break, not the close-confirmed modified version.
+ assert_eq!(t.update(c(9.0, 12.0, 6.0, 7.0, 2)), Some(0.0));
+ }
+
+ #[test]
+ fn not_inside_bar_yields_zero() {
+ let mut t = HikkakeModified::new();
+ t.update(c(10.0, 15.0, 5.0, 12.0, 0));
+ t.update(c(11.0, 16.0, 8.0, 12.0, 1));
+ assert_eq!(t.update(c(9.0, 12.0, 6.0, 9.0, 2)), Some(0.0));
+ }
+
+ #[test]
+ fn first_two_bars_return_zero() {
+ let mut t = HikkakeModified::new();
+ assert_eq!(t.update(c(10.0, 15.0, 5.0, 12.0, 0)), Some(0.0));
+ assert_eq!(t.update(c(11.0, 13.0, 8.0, 12.0, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn batch_equals_streaming() {
+ let candles: Vec = (0..40)
+ .map(|i| {
+ let base = 100.0 + i as f64;
+ match i % 3 {
+ 0 => c(base, base + 6.0, base - 6.0, base, i),
+ 1 => c(base, base + 2.0, base - 2.0, base, i),
+ _ => c(base, base + 1.0, base - 5.0, base, i),
+ }
+ })
+ .collect();
+ let mut a = HikkakeModified::new();
+ let mut b = HikkakeModified::new();
+ assert_eq!(
+ a.batch(&candles),
+ candles.iter().map(|x| b.update(*x)).collect::>()
+ );
+ }
+
+ #[test]
+ fn reset_clears_state() {
+ let mut t = HikkakeModified::new();
+ t.update(c(10.0, 15.0, 5.0, 12.0, 0));
+ t.update(c(11.0, 13.0, 8.0, 12.0, 1));
+ t.update(c(9.0, 12.0, 6.0, 9.0, 2));
+ assert!(t.is_ready());
+ t.reset();
+ assert!(!t.is_ready());
+ assert_eq!(t.update(c(10.0, 15.0, 5.0, 12.0, 0)), Some(0.0));
+ }
+}
diff --git a/crates/wickra-core/src/indicators/homing_pigeon.rs b/crates/wickra-core/src/indicators/homing_pigeon.rs
new file mode 100644
index 00000000..2d231372
--- /dev/null
+++ b/crates/wickra-core/src/indicators/homing_pigeon.rs
@@ -0,0 +1,169 @@
+//! Homing Pigeon candlestick pattern.
+
+use crate::ohlcv::Candle;
+use crate::traits::Indicator;
+
+/// Homing Pigeon — a 2-bar bullish reversal. Two black candles in a decline, the
+/// second a small body sitting entirely inside the first body (a same-colour
+/// harami). The shrinking range signals selling pressure is fading.
+///
+/// ```text
+/// bar1 black (close < open)
+/// bar2 black & its body sits inside bar1's body
+/// (open2 <= open1 && close2 >= close1)
+/// bar2 body is smaller than bar1's
+/// ```
+///
+/// Output is `+1.0` when the pattern completes and `0.0` otherwise. Homing Pigeon
+/// is a single-direction (bullish-only) reversal, so it never emits `−1.0`. The
+/// first bar always returns `0.0` because the two-bar window is not yet filled.
+/// 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, HomingPigeon, Indicator};
+///
+/// let mut indicator = HomingPigeon::new();
+/// indicator.update(Candle::new(15.0, 15.1, 9.9, 10.0, 1.0, 0).unwrap());
+/// let out = indicator
+/// .update(Candle::new(14.0, 14.1, 10.9, 11.0, 1.0, 1).unwrap());
+/// assert_eq!(out, Some(1.0));
+/// ```
+#[derive(Debug, Clone, Default)]
+pub struct HomingPigeon {
+ prev: Option,
+ has_emitted: bool,
+}
+
+impl HomingPigeon {
+ /// Construct a new Homing Pigeon detector.
+ pub const fn new() -> Self {
+ Self {
+ prev: None,
+ has_emitted: false,
+ }
+ }
+}
+
+impl Indicator for HomingPigeon {
+ 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);
+ };
+ // Both bars black, bar2's body inside bar1's body and smaller.
+ if bar1.close < bar1.open
+ && candle.close < candle.open
+ && candle.open <= bar1.open
+ && candle.close >= bar1.close
+ && (candle.open - candle.close) < (bar1.open - 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 {
+ "HomingPigeon"
+ }
+}
+
+#[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 = HomingPigeon::new();
+ assert_eq!(t.name(), "HomingPigeon");
+ assert_eq!(t.warmup_period(), 2);
+ assert!(!t.is_ready());
+ }
+
+ #[test]
+ fn homing_pigeon_is_plus_one() {
+ let mut t = HomingPigeon::new();
+ assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
+ assert_eq!(t.update(c(14.0, 14.1, 10.9, 11.0, 1)), Some(1.0));
+ }
+
+ #[test]
+ fn second_bar_white_yields_zero() {
+ let mut t = HomingPigeon::new();
+ t.update(c(15.0, 15.1, 9.9, 10.0, 0));
+ // bar2 white -> not a homing pigeon.
+ assert_eq!(t.update(c(11.0, 14.1, 10.9, 14.0, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn second_body_not_inside_yields_zero() {
+ let mut t = HomingPigeon::new();
+ t.update(c(15.0, 15.1, 9.9, 10.0, 0));
+ // bar2 opens above bar1's open -> body not contained.
+ assert_eq!(t.update(c(16.0, 16.1, 10.9, 11.0, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn first_bar_returns_zero() {
+ let mut t = HomingPigeon::new();
+ assert_eq!(t.update(c(15.0, 15.1, 9.9, 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 + 5.0, base + 5.1, base - 0.1, base, i)
+ })
+ .collect();
+ let mut a = HomingPigeon::new();
+ let mut b = HomingPigeon::new();
+ assert_eq!(
+ a.batch(&candles),
+ candles.iter().map(|x| b.update(*x)).collect::>()
+ );
+ }
+
+ #[test]
+ fn reset_clears_state() {
+ let mut t = HomingPigeon::new();
+ t.update(c(15.0, 15.1, 9.9, 10.0, 0));
+ t.update(c(14.0, 14.1, 10.9, 11.0, 1));
+ assert!(t.is_ready());
+ t.reset();
+ assert!(!t.is_ready());
+ assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
+ }
+}
diff --git a/crates/wickra-core/src/indicators/in_neck.rs b/crates/wickra-core/src/indicators/in_neck.rs
new file mode 100644
index 00000000..16edc2c7
--- /dev/null
+++ b/crates/wickra-core/src/indicators/in_neck.rs
@@ -0,0 +1,183 @@
+//! In-Neck candlestick pattern.
+
+use crate::ohlcv::Candle;
+use crate::traits::Indicator;
+
+/// In-Neck — a 2-bar bearish continuation, slightly stronger than On-Neck. A long
+/// black candle in a decline is followed by a white candle that opens below the
+/// black bar's low and closes just barely *into* the black body, around its close
+/// level. The shallow recovery still favours the sellers.
+///
+/// ```text
+/// long body = |close − open| >= 0.5 * (high − low)
+/// bar1 black & long
+/// bar2 white, opens below bar1's low (open2 < low1)
+/// bar2 closes just into bar1's body (close1 <= close2 <= close1 + 0.1 · body1)
+/// ```
+///
+/// Output is `−1.0` when the pattern completes and `0.0` otherwise. In-Neck is a
+/// single-direction (bearish-only) continuation, so it never emits `+1.0`. The
+/// first bar always returns `0.0` because the two-bar window is not yet filled.
+/// Body and neckline thresholds follow the geometric house style 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, InNeck, Indicator};
+///
+/// let mut indicator = InNeck::new();
+/// indicator.update(Candle::new(15.0, 15.1, 9.0, 10.0, 1.0, 0).unwrap());
+/// let out = indicator
+/// .update(Candle::new(7.0, 10.3, 6.9, 10.2, 1.0, 1).unwrap());
+/// assert_eq!(out, Some(-1.0));
+/// ```
+#[derive(Debug, Clone, Default)]
+pub struct InNeck {
+ prev: Option,
+ has_emitted: bool,
+}
+
+impl InNeck {
+ /// Construct a new In-Neck detector.
+ pub const fn new() -> Self {
+ Self {
+ prev: None,
+ has_emitted: false,
+ }
+ }
+}
+
+impl Indicator for InNeck {
+ 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;
+ if range1 <= 0.0 {
+ return Some(0.0);
+ }
+ let body1 = bar1.open - bar1.close;
+ if bar1.close < bar1.open
+ && body1 >= 0.5 * range1
+ && candle.close > candle.open
+ && candle.open < bar1.low
+ && candle.close >= bar1.close
+ && candle.close <= bar1.close + 0.1 * body1
+ {
+ 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 {
+ "InNeck"
+ }
+}
+
+#[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 = InNeck::new();
+ assert_eq!(t.name(), "InNeck");
+ assert_eq!(t.warmup_period(), 2);
+ assert!(!t.is_ready());
+ }
+
+ #[test]
+ fn in_neck_is_minus_one() {
+ let mut t = InNeck::new();
+ assert_eq!(t.update(c(15.0, 15.1, 9.0, 10.0, 0)), Some(0.0));
+ assert_eq!(t.update(c(7.0, 10.3, 6.9, 10.2, 1)), Some(-1.0));
+ }
+
+ #[test]
+ fn close_at_low_yields_zero() {
+ let mut t = InNeck::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ // Closes at the prior low, not into the body -> on-neck, not in-neck.
+ assert_eq!(t.update(c(7.0, 9.1, 6.9, 9.0, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn close_past_neck_yields_zero() {
+ let mut t = InNeck::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ // Closes well into the body -> thrusting, not in-neck.
+ assert_eq!(t.update(c(7.0, 11.6, 6.9, 11.5, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn second_bar_black_yields_zero() {
+ let mut t = InNeck::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ assert_eq!(t.update(c(10.4, 10.5, 6.9, 10.1, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn first_bar_returns_zero() {
+ let mut t = InNeck::new();
+ assert_eq!(t.update(c(15.0, 15.1, 9.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 + 5.0, base + 5.1, base - 1.0, base, i)
+ })
+ .collect();
+ let mut a = InNeck::new();
+ let mut b = InNeck::new();
+ assert_eq!(
+ a.batch(&candles),
+ candles.iter().map(|x| b.update(*x)).collect::>()
+ );
+ }
+
+ #[test]
+ fn reset_clears_state() {
+ let mut t = InNeck::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ t.update(c(7.0, 10.3, 6.9, 10.2, 1));
+ assert!(t.is_ready());
+ t.reset();
+ assert!(!t.is_ready());
+ assert_eq!(t.update(c(15.0, 15.1, 9.0, 10.0, 0)), Some(0.0));
+ }
+}
diff --git a/crates/wickra-core/src/indicators/mod.rs b/crates/wickra-core/src/indicators/mod.rs
index 7bac8a64..335d3238 100644
--- a/crates/wickra-core/src/indicators/mod.rs
+++ b/crates/wickra-core/src/indicators/mod.rs
@@ -100,14 +100,17 @@ mod harami;
mod heikin_ashi;
mod high_wave;
mod hikkake;
+mod hikkake_modified;
mod hilbert_dominant_cycle;
mod hilo_activator;
mod historical_volatility;
mod hma;
+mod homing_pigeon;
mod hurst_channel;
mod hurst_exponent;
mod ichimoku;
mod identical_three_crows;
+mod in_neck;
mod inertia;
mod information_ratio;
mod initial_balance;
@@ -156,6 +159,7 @@ mod oi_delta;
mod oi_price_divergence;
mod oi_weighted;
mod omega_ratio;
+mod on_neck;
mod opening_range;
mod pain_index;
mod pair_spread_zscore;
@@ -227,6 +231,7 @@ mod three_line_strike;
mod three_outside;
mod three_soldiers_or_crows;
mod three_stars_in_south;
+mod thrusting;
mod tii;
mod trade_imbalance;
mod treynor_ratio;
@@ -364,14 +369,17 @@ pub use harami::Harami;
pub use heikin_ashi::{HeikinAshi, HeikinAshiOutput};
pub use high_wave::HighWave;
pub use hikkake::Hikkake;
+pub use hikkake_modified::HikkakeModified;
pub use hilbert_dominant_cycle::HilbertDominantCycle;
pub use hilo_activator::HiLoActivator;
pub use historical_volatility::HistoricalVolatility;
pub use hma::Hma;
+pub use homing_pigeon::HomingPigeon;
pub use hurst_channel::{HurstChannel, HurstChannelOutput};
pub use hurst_exponent::HurstExponent;
pub use ichimoku::{Ichimoku, IchimokuOutput};
pub use identical_three_crows::IdenticalThreeCrows;
+pub use in_neck::InNeck;
pub use inertia::Inertia;
pub use information_ratio::InformationRatio;
pub use initial_balance::{InitialBalance, InitialBalanceOutput};
@@ -420,6 +428,7 @@ pub use oi_delta::OpenInterestDelta;
pub use oi_price_divergence::OIPriceDivergence;
pub use oi_weighted::OIWeighted;
pub use omega_ratio::OmegaRatio;
+pub use on_neck::OnNeck;
pub use opening_range::{OpeningRange, OpeningRangeOutput};
pub use pain_index::PainIndex;
pub use pair_spread_zscore::PairSpreadZScore;
@@ -491,6 +500,7 @@ pub use three_line_strike::ThreeLineStrike;
pub use three_outside::ThreeOutside;
pub use three_soldiers_or_crows::ThreeSoldiersOrCrows;
pub use three_stars_in_south::ThreeStarsInSouth;
+pub use thrusting::Thrusting;
pub use tii::Tii;
pub use trade_imbalance::TradeImbalance;
pub use treynor_ratio::TreynorRatio;
@@ -815,6 +825,11 @@ pub const FAMILIES: &[(&str, &[&str])] = &[
"GapSideBySideWhite",
"HighWave",
"Hikkake",
+ "HikkakeModified",
+ "HomingPigeon",
+ "OnNeck",
+ "InNeck",
+ "Thrusting",
],
),
(
@@ -906,6 +921,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, 259, "FAMILIES total drifted from indicator count");
+ assert_eq!(total, 264, "FAMILIES total drifted from indicator count");
}
}
diff --git a/crates/wickra-core/src/indicators/on_neck.rs b/crates/wickra-core/src/indicators/on_neck.rs
new file mode 100644
index 00000000..2de38168
--- /dev/null
+++ b/crates/wickra-core/src/indicators/on_neck.rs
@@ -0,0 +1,181 @@
+//! On-Neck candlestick pattern.
+
+use crate::ohlcv::Candle;
+use crate::traits::Indicator;
+
+/// On-Neck — a 2-bar bearish continuation. In a decline a long black candle is
+/// followed by a white candle that opens below the black bar's low yet rallies
+/// only as far as the black bar's *low* (the "neckline"). The feeble bounce shows
+/// sellers remain in control.
+///
+/// ```text
+/// long body = |close − open| >= 0.5 * (high − low)
+/// bar1 black & long
+/// bar2 white, opens below bar1's low (open2 < low1)
+/// bar2 closes at bar1's low (the neckline) (|close2 − low1| <= 0.05 · range1)
+/// ```
+///
+/// Output is `−1.0` when the pattern completes and `0.0` otherwise. On-Neck is a
+/// single-direction (bearish-only) continuation, so it never emits `+1.0`. The
+/// first bar always returns `0.0` because the two-bar window is not yet filled.
+/// Body and neckline thresholds follow the geometric house style 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, Indicator, OnNeck};
+///
+/// let mut indicator = OnNeck::new();
+/// indicator.update(Candle::new(15.0, 15.1, 9.0, 10.0, 1.0, 0).unwrap());
+/// let out = indicator
+/// .update(Candle::new(7.0, 9.1, 6.9, 9.0, 1.0, 1).unwrap());
+/// assert_eq!(out, Some(-1.0));
+/// ```
+#[derive(Debug, Clone, Default)]
+pub struct OnNeck {
+ prev: Option,
+ has_emitted: bool,
+}
+
+impl OnNeck {
+ /// Construct a new On-Neck detector.
+ pub const fn new() -> Self {
+ Self {
+ prev: None,
+ has_emitted: false,
+ }
+ }
+}
+
+impl Indicator for OnNeck {
+ 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;
+ if range1 <= 0.0 {
+ return Some(0.0);
+ }
+ if bar1.close < bar1.open
+ && (bar1.open - bar1.close) >= 0.5 * range1
+ && candle.close > candle.open
+ && candle.open < bar1.low
+ && (candle.close - bar1.low).abs() <= 0.05 * range1
+ {
+ 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 {
+ "OnNeck"
+ }
+}
+
+#[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 = OnNeck::new();
+ assert_eq!(t.name(), "OnNeck");
+ assert_eq!(t.warmup_period(), 2);
+ assert!(!t.is_ready());
+ }
+
+ #[test]
+ fn on_neck_is_minus_one() {
+ let mut t = OnNeck::new();
+ assert_eq!(t.update(c(15.0, 15.1, 9.0, 10.0, 0)), Some(0.0));
+ assert_eq!(t.update(c(7.0, 9.1, 6.9, 9.0, 1)), Some(-1.0));
+ }
+
+ #[test]
+ fn close_into_body_yields_zero() {
+ let mut t = OnNeck::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ // Closes at the prior close, not the low -> in-neck, not on-neck.
+ assert_eq!(t.update(c(7.0, 10.2, 6.9, 10.1, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn second_bar_black_yields_zero() {
+ let mut t = OnNeck::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ assert_eq!(t.update(c(9.5, 9.6, 6.9, 9.0, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn opens_above_low_yields_zero() {
+ let mut t = OnNeck::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ // Opens above bar1's low.
+ assert_eq!(t.update(c(9.5, 10.1, 9.4, 10.0, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn first_bar_returns_zero() {
+ let mut t = OnNeck::new();
+ assert_eq!(t.update(c(15.0, 15.1, 9.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 + 5.0, base + 5.1, base - 1.0, base, i)
+ })
+ .collect();
+ let mut a = OnNeck::new();
+ let mut b = OnNeck::new();
+ assert_eq!(
+ a.batch(&candles),
+ candles.iter().map(|x| b.update(*x)).collect::>()
+ );
+ }
+
+ #[test]
+ fn reset_clears_state() {
+ let mut t = OnNeck::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ t.update(c(7.0, 9.1, 6.9, 9.0, 1));
+ assert!(t.is_ready());
+ t.reset();
+ assert!(!t.is_ready());
+ assert_eq!(t.update(c(15.0, 15.1, 9.0, 10.0, 0)), Some(0.0));
+ }
+}
diff --git a/crates/wickra-core/src/indicators/thrusting.rs b/crates/wickra-core/src/indicators/thrusting.rs
new file mode 100644
index 00000000..dfc3afd3
--- /dev/null
+++ b/crates/wickra-core/src/indicators/thrusting.rs
@@ -0,0 +1,186 @@
+//! Thrusting candlestick pattern.
+
+use crate::ohlcv::Candle;
+use crate::traits::Indicator;
+
+/// Thrusting — a 2-bar bearish continuation, deeper than In-Neck but short of a
+/// piercing reversal. A long black candle in a decline is followed by a white
+/// candle that opens below the black bar's low and closes well into the black
+/// body — but still below its midpoint, so the bounce is not yet a reversal.
+///
+/// ```text
+/// long body = |close − open| >= 0.5 * (high − low)
+/// bar1 black & long
+/// bar2 white, opens below bar1's low (open2 < low1)
+/// bar2 closes above the in-neck zone but below the body midpoint
+/// (close1 + 0.1·body1 < close2 < midpoint(open1, close1))
+/// ```
+///
+/// Output is `−1.0` when the pattern completes and `0.0` otherwise. Thrusting is a
+/// single-direction (bearish-only) continuation, so it never emits `+1.0`. A close
+/// at or above the midpoint would be a piercing pattern instead. The first bar
+/// always returns `0.0` because the two-bar window is not yet filled. Body and
+/// neckline thresholds follow the geometric house style 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, Indicator, Thrusting};
+///
+/// let mut indicator = Thrusting::new();
+/// indicator.update(Candle::new(15.0, 15.1, 9.0, 10.0, 1.0, 0).unwrap());
+/// let out = indicator
+/// .update(Candle::new(7.0, 11.6, 6.9, 11.5, 1.0, 1).unwrap());
+/// assert_eq!(out, Some(-1.0));
+/// ```
+#[derive(Debug, Clone, Default)]
+pub struct Thrusting {
+ prev: Option,
+ has_emitted: bool,
+}
+
+impl Thrusting {
+ /// Construct a new Thrusting detector.
+ pub const fn new() -> Self {
+ Self {
+ prev: None,
+ has_emitted: false,
+ }
+ }
+}
+
+impl Indicator for Thrusting {
+ 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;
+ if range1 <= 0.0 {
+ return Some(0.0);
+ }
+ let body1 = bar1.open - bar1.close;
+ let mid1 = f64::midpoint(bar1.open, bar1.close);
+ if bar1.close < bar1.open
+ && body1 >= 0.5 * range1
+ && candle.close > candle.open
+ && candle.open < bar1.low
+ && candle.close > bar1.close + 0.1 * body1
+ && candle.close < mid1
+ {
+ 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 {
+ "Thrusting"
+ }
+}
+
+#[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 = Thrusting::new();
+ assert_eq!(t.name(), "Thrusting");
+ assert_eq!(t.warmup_period(), 2);
+ assert!(!t.is_ready());
+ }
+
+ #[test]
+ fn thrusting_is_minus_one() {
+ let mut t = Thrusting::new();
+ assert_eq!(t.update(c(15.0, 15.1, 9.0, 10.0, 0)), Some(0.0));
+ assert_eq!(t.update(c(7.0, 11.6, 6.9, 11.5, 1)), Some(-1.0));
+ }
+
+ #[test]
+ fn shallow_close_yields_zero() {
+ let mut t = Thrusting::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ // Closes barely into the body -> in-neck, not thrusting.
+ assert_eq!(t.update(c(7.0, 10.3, 6.9, 10.2, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn close_past_midpoint_yields_zero() {
+ let mut t = Thrusting::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ // Closes above the midpoint -> piercing, not thrusting.
+ assert_eq!(t.update(c(7.0, 13.1, 6.9, 13.0, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn second_bar_black_yields_zero() {
+ let mut t = Thrusting::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ assert_eq!(t.update(c(12.0, 12.1, 6.9, 11.5, 1)), Some(0.0));
+ }
+
+ #[test]
+ fn first_bar_returns_zero() {
+ let mut t = Thrusting::new();
+ assert_eq!(t.update(c(15.0, 15.1, 9.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 + 5.0, base + 5.1, base - 1.0, base, i)
+ })
+ .collect();
+ let mut a = Thrusting::new();
+ let mut b = Thrusting::new();
+ assert_eq!(
+ a.batch(&candles),
+ candles.iter().map(|x| b.update(*x)).collect::>()
+ );
+ }
+
+ #[test]
+ fn reset_clears_state() {
+ let mut t = Thrusting::new();
+ t.update(c(15.0, 15.1, 9.0, 10.0, 0));
+ t.update(c(7.0, 11.6, 6.9, 11.5, 1));
+ assert!(t.is_ready());
+ t.reset();
+ assert!(!t.is_ready());
+ assert_eq!(t.update(c(15.0, 15.1, 9.0, 10.0, 0)), Some(0.0));
+ }
+}
diff --git a/crates/wickra-core/src/lib.rs b/crates/wickra-core/src/lib.rs
index 28fb4cc9..8359b6fd 100644
--- a/crates/wickra-core/src/lib.rs
+++ b/crates/wickra-core/src/lib.rs
@@ -72,17 +72,18 @@ pub use indicators::{
FractalChaosBands, FractalChaosBandsOutput, Frama, FundingBasis, FundingRate, FundingRateMean,
FundingRateZScore, GainLossRatio, GapSideBySideWhite, GarmanKlassVolatility, GravestoneDoji,
Hammer, HangingMan, Harami, HeikinAshi, HeikinAshiOutput, HiLoActivator, HighWave, Hikkake,
- 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, LongLeggedDoji, LongShortRatio, MaEnvelope,
- MaEnvelopeOutput, MacdIndicator, MacdOutput, Mama, MamaOutput, MarketFacilitationIndex,
- Marubozu, MassIndex, MaxDrawdown, McGinleyDynamic, MedianAbsoluteDeviation, MedianPrice, Mfi,
- Microprice, Mom, MorningDojiStar, MorningEveningStar, Natr, Nvi, OIPriceDivergence, OIWeighted,
- Obv, OmegaRatio, OpenInterestDelta, OpeningRange, OpeningRangeOutput, OrderBookImbalanceFull,
+ HikkakeModified, HilbertDominantCycle, HistoricalVolatility, Hma, HomingPigeon, HurstChannel,
+ HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput, IdenticalThreeCrows, InNeck,
+ 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, LongLeggedDoji,
+ LongShortRatio, MaEnvelope, MaEnvelopeOutput, MacdIndicator, MacdOutput, Mama, MamaOutput,
+ MarketFacilitationIndex, Marubozu, MassIndex, MaxDrawdown, McGinleyDynamic,
+ MedianAbsoluteDeviation, MedianPrice, Mfi, Microprice, Mom, MorningDojiStar,
+ MorningEveningStar, Natr, Nvi, OIPriceDivergence, OIWeighted, Obv, OmegaRatio, OnNeck,
+ OpenInterestDelta, OpeningRange, OpeningRangeOutput, OrderBookImbalanceFull,
OrderBookImbalanceTop1, OrderBookImbalanceTopN, PainIndex, PairSpreadZScore, PairwiseBeta,
ParkinsonVolatility, PearsonCorrelation, PercentB, PercentageTrailingStop, Pgo,
PiercingDarkCloud, Pmo, Ppo, ProfitFactor, Psar, Pvi, QuotedSpread, RSquared, RealizedSpread,
@@ -95,14 +96,15 @@ pub use indicators::{
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,
+ ThreeInside, ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, Thrusting,
+ 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 d5e8e0bb..3527fd45 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 **264 indicators** across
+- A per-indicator deep dive for every one of the **269 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 e92e41f4..f6c4b22f 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, DojiStar, Donchian, DonchianStop, DragonflyDoji, EaseOfMovement, Engulfing, EveningDojiStar, Evwma, FibonacciPivots, ForceIndex, FractalChaosBands, GapSideBySideWhite, GarmanKlassVolatility, GravestoneDoji, Hammer, HangingMan, Harami, HeikinAshi, HiLoActivator, HighWave, Hikkake, HurstChannel, Ichimoku, IdenticalThreeCrows, Indicator, Inertia, InitialBalance, InvertedHammer, Keltner, Kvo, LongLeggedDoji, MarketFacilitationIndex, Marubozu, MassIndex, MedianPrice, Mfi, MorningDojiStar, 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
+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, EveningDojiStar, Evwma, FibonacciPivots, ForceIndex, FractalChaosBands, GapSideBySideWhite, GarmanKlassVolatility, GravestoneDoji, Hammer, HangingMan, Harami, HeikinAshi, HiLoActivator, HighWave, Hikkake, HikkakeModified, HomingPigeon, HurstChannel, Ichimoku, IdenticalThreeCrows, InNeck, Indicator, Inertia, InitialBalance, InvertedHammer, Keltner, Kvo, LongLeggedDoji, MarketFacilitationIndex, Marubozu, MassIndex, MedianPrice, Mfi, MorningDojiStar, MorningEveningStar, Natr, Nvi, Obv, OnNeck, 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, Thrusting, 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(Thrusting::new, &candles);
+ drive(InNeck::new, &candles);
+ drive(OnNeck::new, &candles);
+ drive(HomingPigeon::new, &candles);
+ drive(HikkakeModified::new, &candles);
drive(Hikkake::new, &candles);
drive(HighWave::new, &candles);
drive(GapSideBySideWhite::new, &candles);