feat: TA-Lib candlestick patterns — doji-star/gap/high-wave/hikkake (part 4 of 9) (#135)

* feat: add doji-star, gap, high-wave and hikkake candlestick patterns

Five patterns, all `Input = Candle`, `Output = f64`:

- Evening Doji Star (CDLEVENINGDOJISTAR) — bearish top reversal: long white bar,
  a doji gapping up, then a black bar closing deep into the first body; -1
  (penetration configurable, default 0.3).
- Morning Doji Star (CDLMORNINGDOJISTAR) — bullish bottom reversal mirror; +1.
- Gap Side-by-Side White (CDLGAPSIDESIDEWHITE) — two similar white candles
  opening side by side after a gap, a continuation; gap up +1, gap down -1.
- High-Wave (CDLHIGHWAVE) — a small body with very long shadows on both sides,
  an extreme indecision flag; +1 on detection.
- Hikkake (CDLHIKKAKE) — an inside bar followed by a failed breakout (a trap);
  bullish +1, bearish -1.

Counter 259 -> 264 (mod-count == lib counted block; FAMILIES total 254 -> 259).

* chore: sync indicator count to 264

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
This commit is contained in:
kingchenc
2026-06-02 16:54:47 +02:00
committed by GitHub
parent 244d754707
commit d43bc9ddf3
19 changed files with 1280 additions and 45 deletions
+5
View File
@@ -42,6 +42,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **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`).
- **Evening Doji Star** — a bearish top reversal: long white bar, a doji gapping up, then a black bar closing deep into the first body; -1 (`CDLEVENINGDOJISTAR`).
- **Morning Doji Star** — a bullish bottom reversal: long black bar, a doji gapping down, then a white bar closing deep into the first body; +1 (`CDLMORNINGDOJISTAR`).
- **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`).
- **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,
+5 -5
View File
@@ -1,5 +1,5 @@
<p align="center">
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=259" alt="Wickra — streaming-first technical indicators" width="100%"></a>
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=264" alt="Wickra — streaming-first technical indicators" width="100%"></a>
</p>
[![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 259 indicators; start at the
every one of the 264 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
259 streaming-first indicators across eighteen families. Every one passes the
264 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 |
| 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 |
| 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 259 indicators
│ ├── wickra-core/ core engine + all 264 indicators
│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/
│ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds
├── bindings/
@@ -250,6 +250,11 @@ const candleScalar = {
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) },
EveningDojiStar: { make: () => new wickra.EveningDojiStar(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
MorningDojiStar: { make: () => new wickra.MorningDojiStar(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
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) },
};
for (const [name, d] of Object.entries(candleScalar)) {
+45
View File
@@ -2345,6 +2345,51 @@ export declare class RickshawMan {
isReady(): boolean
warmupPeriod(): number
}
export type EveningDojiStarNode = EveningDojiStar
export declare class EveningDojiStar {
constructor()
update(open: number, high: number, low: number, close: number): number | null
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type MorningDojiStarNode = MorningDojiStar
export declare class MorningDojiStar {
constructor()
update(open: number, high: number, low: number, close: number): number | null
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type GapSideBySideWhiteNode = GapSideBySideWhite
export declare class GapSideBySideWhite {
constructor()
update(open: number, high: number, low: number, close: number): number | null
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type HighWaveNode = HighWave
export declare class HighWave {
constructor()
update(open: number, high: number, low: number, close: number): number | null
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type HikkakeNode = Hikkake
export declare class Hikkake {
constructor()
update(open: number, high: number, low: number, close: number): number | null
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type OrderBookImbalanceTop1Node = OrderBookImbalanceTop1
export declare class OrderBookImbalanceTop1 {
constructor()
+6 -1
View File
@@ -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, 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, 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
@@ -530,6 +530,11 @@ module.exports.DragonflyDoji = DragonflyDoji
module.exports.GravestoneDoji = GravestoneDoji
module.exports.LongLeggedDoji = LongLeggedDoji
module.exports.RickshawMan = RickshawMan
module.exports.EveningDojiStar = EveningDojiStar
module.exports.MorningDojiStar = MorningDojiStar
module.exports.GapSideBySideWhite = GapSideBySideWhite
module.exports.HighWave = HighWave
module.exports.Hikkake = Hikkake
module.exports.OrderBookImbalanceTop1 = OrderBookImbalanceTop1
module.exports.OrderBookImbalanceFull = OrderBookImbalanceFull
module.exports.Microprice = Microprice
+9
View File
@@ -8780,6 +8780,15 @@ 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");
node_candle_pattern!(EveningDojiStarNode, wc::EveningDojiStar, "EveningDojiStar");
node_candle_pattern!(MorningDojiStarNode, wc::MorningDojiStar, "MorningDojiStar");
node_candle_pattern!(
GapSideBySideWhiteNode,
wc::GapSideBySideWhite,
"GapSideBySideWhite"
);
node_candle_pattern!(HighWaveNode, wc::HighWave, "HighWave");
node_candle_pattern!(HikkakeNode, wc::Hikkake, "Hikkake");
// ============================== Microstructure: Order Book ==============================
//
+10
View File
@@ -255,6 +255,11 @@ from ._wickra import (
GravestoneDoji,
LongLeggedDoji,
RickshawMan,
EveningDojiStar,
MorningDojiStar,
GapSideBySideWhite,
HighWave,
Hikkake,
# Microstructure: order book
OrderBookImbalanceTop1,
OrderBookImbalanceTopN,
@@ -537,6 +542,11 @@ __all__ = [
"GravestoneDoji",
"LongLeggedDoji",
"RickshawMan",
"EveningDojiStar",
"MorningDojiStar",
"GapSideBySideWhite",
"HighWave",
"Hikkake",
# Microstructure: order book
"OrderBookImbalanceTop1",
"OrderBookImbalanceTopN",
+14
View File
@@ -11643,6 +11643,15 @@ 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");
candle_pattern_no_param!(PyEveningDojiStar, wc::EveningDojiStar, "EveningDojiStar");
candle_pattern_no_param!(PyMorningDojiStar, wc::MorningDojiStar, "MorningDojiStar");
candle_pattern_no_param!(
PyGapSideBySideWhite,
wc::GapSideBySideWhite,
"GapSideBySideWhite"
);
candle_pattern_no_param!(PyHighWave, wc::HighWave, "HighWave");
candle_pattern_no_param!(PyHikkake, wc::Hikkake, "Hikkake");
// ============================== Microstructure: Order Book ==============================
//
// Order-book indicators consume a depth snapshot rather than OHLCV. Streaming
@@ -14154,6 +14163,11 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyGravestoneDoji>()?;
m.add_class::<PyLongLeggedDoji>()?;
m.add_class::<PyRickshawMan>()?;
m.add_class::<PyEveningDojiStar>()?;
m.add_class::<PyMorningDojiStar>()?;
m.add_class::<PyGapSideBySideWhite>()?;
m.add_class::<PyHighWave>()?;
m.add_class::<PyHikkake>()?;
// Microstructure: order book.
m.add_class::<PyOrderBookImbalanceTop1>()?;
m.add_class::<PyOrderBookImbalanceTopN>()?;
@@ -583,6 +583,26 @@ CANDLE_SCALAR = {
lambda: ta.RickshawMan(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"EveningDojiStar": (
lambda: ta.EveningDojiStar(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"MorningDojiStar": (
lambda: ta.MorningDojiStar(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"GapSideBySideWhite": (
lambda: ta.GapSideBySideWhite(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"HighWave": (
lambda: ta.HighWave(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"Hikkake": (
lambda: ta.Hikkake(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
}
@@ -1731,6 +1751,39 @@ 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)
def test_evening_doji_star_reference():
t = ta.EveningDojiStar()
assert t.update((10.0, 15.1, 9.9, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((17.0, 17.1, 16.9, 17.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((16.0, 16.1, 11.9, 12.0, 1.0, 2)) == pytest.approx(-1.0)
def test_morning_doji_star_reference():
t = ta.MorningDojiStar()
assert t.update((15.0, 15.1, 9.9, 10.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((8.0, 8.1, 7.9, 8.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((9.0, 13.1, 8.9, 13.0, 1.0, 2)) == pytest.approx(1.0)
def test_gap_side_by_side_white_reference():
t = ta.GapSideBySideWhite()
assert t.update((10.0, 11.1, 9.9, 11.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((13.0, 14.1, 12.9, 14.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((13.0, 14.1, 12.9, 14.0, 1.0, 2)) == pytest.approx(1.0)
def test_high_wave_reference():
t = ta.HighWave()
assert t.update((10.0, 12.0, 8.0, 10.3, 1.0, 0)) == pytest.approx(1.0)
def test_hikkake_reference():
t = ta.Hikkake()
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, 7.0, 1.0, 2)) == pytest.approx(1.0)
# --- Lifecycle ------------------------------------------------------------
+9
View File
@@ -6357,6 +6357,15 @@ 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);
wasm_candle_pattern!(WasmEveningDojiStar, wc::EveningDojiStar, EveningDojiStar);
wasm_candle_pattern!(WasmMorningDojiStar, wc::MorningDojiStar, MorningDojiStar);
wasm_candle_pattern!(
WasmGapSideBySideWhite,
wc::GapSideBySideWhite,
GapSideBySideWhite
);
wasm_candle_pattern!(WasmHighWave, wc::HighWave, HighWave);
wasm_candle_pattern!(WasmHikkake, wc::Hikkake, Hikkake);
// ============================== Microstructure: Order Book ==============================
//
@@ -0,0 +1,241 @@
//! Evening Doji Star candlestick pattern.
use crate::error::{Error, Result};
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Evening Doji Star — a 3-bar bearish top reversal. A long white bar extends the
/// advance, a doji gaps up above it (the star of indecision), then a black bar
/// gaps back down and closes deep into the first body, confirming the turn.
///
/// ```text
/// long body = |close open| >= 0.5 * (high low)
/// doji = |close open| <= 0.1 * (high low)
/// bar1 white & long
/// bar2 doji, body gaps UP above bar1 body (min(o2,c2) > close1)
/// bar3 black, body gaps DOWN below the doji (max(o3,c3) < min(o2,c2))
/// bar3 closes deep into bar1 body (close3 < close1 penetration·body1)
/// ```
///
/// Output is `1.0` when the pattern completes and `0.0` otherwise. Evening Doji
/// Star is a single-direction (bearish-only) reversal, so it never emits `+1.0`.
/// The first two bars always return `0.0` because the three-bar window is not yet
/// filled. `penetration` is how far into the first body the third bar must close;
/// it defaults to `0.3` (TA-Lib's `CDLEVENINGDOJISTAR` default) and must lie in
/// `[0, 1)`. Body and doji 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, EveningDojiStar, Indicator};
///
/// let mut indicator = EveningDojiStar::new();
/// indicator.update(Candle::new(10.0, 15.1, 9.9, 15.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(17.0, 17.1, 16.9, 17.0, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(16.0, 16.1, 11.9, 12.0, 1.0, 2).unwrap());
/// assert_eq!(out, Some(-1.0));
/// ```
#[derive(Debug, Clone)]
pub struct EveningDojiStar {
penetration: f64,
prev: Option<Candle>,
prev_prev: Option<Candle>,
has_emitted: bool,
}
impl Default for EveningDojiStar {
fn default() -> Self {
Self::new()
}
}
impl EveningDojiStar {
/// Construct an Evening Doji Star detector with the default 0.3 penetration.
pub const fn new() -> Self {
Self {
penetration: 0.3,
prev: None,
prev_prev: None,
has_emitted: false,
}
}
/// Construct an Evening Doji Star detector with a custom penetration fraction.
///
/// `penetration` must lie in `[0, 1)`.
pub fn with_penetration(penetration: f64) -> Result<Self> {
if !(0.0..1.0).contains(&penetration) {
return Err(Error::InvalidPeriod {
message: "evening doji star penetration must lie in [0, 1)",
});
}
Ok(Self {
penetration,
prev: None,
prev_prev: None,
has_emitted: false,
})
}
/// Configured penetration fraction.
pub fn penetration(&self) -> f64 {
self.penetration
}
}
impl Indicator for EveningDojiStar {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
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);
};
let range1 = bar1.high - bar1.low;
let range2 = bar2.high - bar2.low;
if range1 <= 0.0 || range2 <= 0.0 {
return Some(0.0);
}
let body1 = bar1.close - bar1.open;
if body1 < 0.5 * range1 {
return Some(0.0); // bar1 must be a long white body
}
if (bar2.close - bar2.open).abs() > 0.1 * range2 {
return Some(0.0); // bar2 must be a doji
}
let star_bottom = bar2.open.min(bar2.close);
let bar3_top = candle.open.max(candle.close);
if star_bottom > bar1.close
&& candle.close < candle.open
&& bar3_top < star_bottom
&& candle.close < bar1.close - self.penetration * body1
{
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 {
"EveningDojiStar"
}
}
#[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 rejects_invalid_penetration() {
assert!(EveningDojiStar::with_penetration(-0.01).is_err());
assert!(EveningDojiStar::with_penetration(1.0).is_err());
}
#[test]
fn accepts_valid_penetration() {
let t = EveningDojiStar::with_penetration(0.5).unwrap();
assert!((t.penetration() - 0.5).abs() < 1e-12);
}
#[test]
fn accessors_and_metadata() {
let t = EveningDojiStar::default();
assert_eq!(t.name(), "EveningDojiStar");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
assert!((t.penetration() - 0.3).abs() < 1e-12);
}
#[test]
fn evening_doji_star_is_minus_one() {
let mut t = EveningDojiStar::new();
assert_eq!(t.update(c(10.0, 15.1, 9.9, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(17.0, 17.1, 16.9, 17.0, 1)), Some(0.0));
assert_eq!(t.update(c(16.0, 16.1, 11.9, 12.0, 2)), Some(-1.0));
}
#[test]
fn middle_not_doji_yields_zero() {
let mut t = EveningDojiStar::new();
t.update(c(10.0, 15.1, 9.9, 15.0, 0));
// Wide-bodied star, not a doji.
t.update(c(16.0, 18.1, 15.9, 18.0, 1));
assert_eq!(t.update(c(16.0, 16.1, 11.9, 12.0, 2)), Some(0.0));
}
#[test]
fn shallow_close_yields_zero() {
let mut t = EveningDojiStar::new();
t.update(c(10.0, 15.1, 9.9, 15.0, 0));
t.update(c(17.0, 17.1, 16.9, 17.0, 1));
// bar3 black but closes at 14.0 -> only 1.0 into the 5.0 body (< 0.3·5).
assert_eq!(t.update(c(16.0, 16.1, 13.9, 14.0, 2)), Some(0.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = EveningDojiStar::new();
assert_eq!(t.update(c(10.0, 15.1, 9.9, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(17.0, 17.1, 16.9, 17.0, 1)), Some(0.0));
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = 100.0 + i as f64;
c(base, base + 5.2, base - 0.1, base + 5.0, i)
})
.collect();
let mut a = EveningDojiStar::new();
let mut b = EveningDojiStar::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = EveningDojiStar::new();
t.update(c(10.0, 15.1, 9.9, 15.0, 0));
t.update(c(17.0, 17.1, 16.9, 17.0, 1));
t.update(c(16.0, 16.1, 11.9, 12.0, 2));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(10.0, 15.1, 9.9, 15.0, 0)), Some(0.0));
}
}
@@ -0,0 +1,220 @@
//! Gap Side-by-Side White Lines candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Gap Side-by-Side White Lines — a 3-bar continuation. After a gap away from the
/// first bar, two white candles of similar size open at roughly the same level
/// (side by side) and hold the gap open, signalling the trend resumes in the gap
/// direction.
///
/// ```text
/// bar2, bar3 both white
/// bar2 body gaps away from bar1 body (up or down)
/// bar3 opens beside bar2 (|open3 open2| <= 0.1 · range2)
/// bar3 body is similar in size to bar2 (neither more than twice the other)
/// gap up -> +1.0 (bullish continuation)
/// gap down -> 1.0 (bearish continuation — "downside" gap side-by-side white)
/// ```
///
/// Output is `+1.0` (gap up) or `1.0` (gap down) when the pattern completes and
/// `0.0` otherwise. The first two bars always return `0.0` because the three-bar
/// window is not yet filled. Open-equality and body-similarity 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` bullish, `1.0` bearish, `0.0` no pattern — so it
/// drops straight into a machine-learning feature matrix where the two gap
/// directions occupy a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, GapSideBySideWhite, Indicator};
///
/// let mut indicator = GapSideBySideWhite::new();
/// indicator.update(Candle::new(10.0, 11.1, 9.9, 11.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(13.0, 14.1, 12.9, 14.0, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(13.0, 14.1, 12.9, 14.0, 1.0, 2).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct GapSideBySideWhite {
prev: Option<Candle>,
prev_prev: Option<Candle>,
has_emitted: bool,
}
impl GapSideBySideWhite {
/// Construct a new Gap Side-by-Side White Lines detector.
pub const fn new() -> Self {
Self {
prev: None,
prev_prev: None,
has_emitted: false,
}
}
}
impl Indicator for GapSideBySideWhite {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
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);
};
let range2 = bar2.high - bar2.low;
if range2 <= 0.0 {
return Some(0.0);
}
// Both of the side-by-side bars must be white.
if bar2.close <= bar2.open || candle.close <= candle.open {
return Some(0.0);
}
// Side by side: opens level and bodies of comparable size.
if (candle.open - bar2.open).abs() > 0.1 * range2 {
return Some(0.0);
}
let body2 = bar2.close - bar2.open;
let body3 = candle.close - candle.open;
if body2 > 2.0 * body3 || body3 > 2.0 * body2 {
return Some(0.0);
}
let bar1_top = bar1.open.max(bar1.close);
let bar1_bottom = bar1.open.min(bar1.close);
let bar2_bottom = bar2.open.min(bar2.close);
let bar2_top = bar2.open.max(bar2.close);
if bar2_bottom > bar1_top {
return Some(1.0); // gap up -> bullish continuation
}
if bar2_top < bar1_bottom {
return Some(-1.0); // gap down -> bearish continuation
}
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 {
"GapSideBySideWhite"
}
}
#[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 = GapSideBySideWhite::new();
assert_eq!(t.name(), "GapSideBySideWhite");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
}
#[test]
fn gap_up_is_plus_one() {
let mut t = GapSideBySideWhite::new();
assert_eq!(t.update(c(10.0, 11.1, 9.9, 11.0, 0)), Some(0.0));
assert_eq!(t.update(c(13.0, 14.1, 12.9, 14.0, 1)), Some(0.0));
assert_eq!(t.update(c(13.0, 14.1, 12.9, 14.0, 2)), Some(1.0));
}
#[test]
fn gap_down_is_minus_one() {
let mut t = GapSideBySideWhite::new();
assert_eq!(t.update(c(14.0, 14.1, 12.9, 13.0, 0)), Some(0.0));
assert_eq!(t.update(c(10.0, 11.1, 9.9, 11.0, 1)), Some(0.0));
assert_eq!(t.update(c(10.0, 11.1, 9.9, 11.0, 2)), Some(-1.0));
}
#[test]
fn second_bar_black_yields_zero() {
let mut t = GapSideBySideWhite::new();
t.update(c(10.0, 11.1, 9.9, 11.0, 0));
// bar3 is black -> not two white lines.
t.update(c(13.0, 14.1, 12.9, 14.0, 1));
assert_eq!(t.update(c(14.0, 14.1, 12.9, 13.0, 2)), Some(0.0));
}
#[test]
fn not_side_by_side_yields_zero() {
let mut t = GapSideBySideWhite::new();
t.update(c(10.0, 11.1, 9.9, 11.0, 0));
t.update(c(13.0, 14.1, 12.9, 14.0, 1));
// bar3 opens far from bar2's open -> not side by side.
assert_eq!(t.update(c(16.0, 17.1, 15.9, 17.0, 2)), Some(0.0));
}
#[test]
fn no_gap_yields_zero() {
let mut t = GapSideBySideWhite::new();
t.update(c(10.0, 13.1, 9.9, 13.0, 0));
// bar2 overlaps bar1 (no gap).
t.update(c(12.0, 13.1, 11.9, 13.0, 1));
assert_eq!(t.update(c(12.0, 13.1, 11.9, 13.0, 2)), Some(0.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = GapSideBySideWhite::new();
assert_eq!(t.update(c(10.0, 11.1, 9.9, 11.0, 0)), Some(0.0));
assert_eq!(t.update(c(13.0, 14.1, 12.9, 14.0, 1)), Some(0.0));
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = 100.0 + i as f64 * 3.0;
c(base, base + 1.1, base - 0.1, base + 1.0, i)
})
.collect();
let mut a = GapSideBySideWhite::new();
let mut b = GapSideBySideWhite::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = GapSideBySideWhite::new();
t.update(c(10.0, 11.1, 9.9, 11.0, 0));
t.update(c(13.0, 14.1, 12.9, 14.0, 1));
t.update(c(13.0, 14.1, 12.9, 14.0, 2));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(10.0, 11.1, 9.9, 11.0, 0)), Some(0.0));
}
}
@@ -0,0 +1,160 @@
//! High-Wave candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// High-Wave — a single-bar extreme-indecision signal. A small body with very
/// long shadows on *both* sides: price swung far up and far down yet finished
/// near the open, a sign that trend conviction has evaporated.
///
/// ```text
/// range = high low
/// long upper = high max(open, close) >= 0.4 * range
/// long lower = min(open, close) low >= 0.4 * range
/// ```
///
/// The two long-shadow conditions force the body below `0.2 * range`, so no
/// separate body test is needed. Output is `+1.0` when the high-wave prints and
/// `0.0` otherwise — a non-directional indecision flag, it never emits `1.0`.
/// Shadow 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` detected, `0.0` no pattern — so it drops straight into
/// a machine-learning feature matrix as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, HighWave, Indicator};
///
/// let mut indicator = HighWave::new();
/// // Small body, long shadows both sides.
/// let candle = Candle::new(10.0, 12.0, 8.0, 10.3, 1.0, 0).unwrap();
/// assert_eq!(indicator.update(candle), Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct HighWave {
has_emitted: bool,
}
impl HighWave {
/// Construct a new High-Wave detector.
pub const fn new() -> Self {
Self { has_emitted: false }
}
}
impl Indicator for HighWave {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
self.has_emitted = true;
let range = candle.high - candle.low;
if range <= 0.0 {
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.4 * range && lower >= 0.4 * 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 {
"HighWave"
}
}
#[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 = HighWave::new();
assert_eq!(t.name(), "HighWave");
assert_eq!(t.warmup_period(), 1);
assert!(!t.is_ready());
}
#[test]
fn high_wave_is_plus_one() {
let mut t = HighWave::new();
assert_eq!(t.update(c(10.0, 12.0, 8.0, 10.3, 0)), Some(1.0));
}
#[test]
fn short_upper_shadow_yields_zero() {
let mut t = HighWave::new();
// Long lower shadow but short upper -> not a high-wave.
assert_eq!(t.update(c(11.5, 12.0, 8.0, 11.7, 0)), Some(0.0));
}
#[test]
fn short_lower_shadow_yields_zero() {
let mut t = HighWave::new();
// Long upper shadow but short lower -> not a high-wave.
assert_eq!(t.update(c(8.3, 12.0, 8.0, 8.5, 0)), Some(0.0));
}
#[test]
fn big_body_yields_zero() {
let mut t = HighWave::new();
// A large body cannot leave both shadows long.
assert_eq!(t.update(c(8.5, 12.0, 8.0, 11.5, 0)), Some(0.0));
}
#[test]
fn zero_range_yields_zero() {
let mut t = HighWave::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<Candle> = (0..40)
.map(|i| {
let base = 100.0 + i as f64;
c(base, base + 3.0, base - 3.0, base + 0.2, i)
})
.collect();
let mut a = HighWave::new();
let mut b = HighWave::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = HighWave::new();
t.update(c(10.0, 12.0, 8.0, 10.3, 0));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
}
}
@@ -0,0 +1,198 @@
//! Hikkake candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Hikkake — a 3-bar trap. An inside bar (bar2 fully contained by bar1) sets up a
/// breakout that immediately fails on bar3, trapping breakout traders and pointing
/// the opposite way.
///
/// ```text
/// inside bar : bar2.high < bar1.high && bar2.low > bar1.low
/// bullish (+1.0): bar3 makes a LOWER high AND LOWER low than bar2
/// (a false downside break -> expect a move up)
/// bearish (1.0): bar3 makes a HIGHER high AND HIGHER low than bar2
/// (a false upside break -> expect a move down)
/// ```
///
/// Output is `+1.0` (bullish setup), `1.0` (bearish setup), or `0.0` otherwise.
/// The detector fires when the three-bar setup completes on bar3; it does not
/// separately flag the optional later confirmation bar. The first two bars always
/// return `0.0` because the 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 where the bullish and
/// bearish setups occupy a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Hikkake, Indicator};
///
/// let mut indicator = Hikkake::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, 7.0, 1.0, 2).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct Hikkake {
prev: Option<Candle>,
prev_prev: Option<Candle>,
has_emitted: bool,
}
impl Hikkake {
/// Construct a new Hikkake detector.
pub const fn new() -> Self {
Self {
prev: None,
prev_prev: None,
has_emitted: false,
}
}
}
impl Indicator for Hikkake {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
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);
};
// bar2 must be an inside bar of bar1.
if !(bar2.high < bar1.high && bar2.low > bar1.low) {
return Some(0.0);
}
// Bullish: bar3 breaks below the inside bar (lower high and lower low).
if candle.high < bar2.high && candle.low < bar2.low {
return Some(1.0);
}
// Bearish: bar3 breaks above the inside bar (higher high and higher low).
if candle.high > bar2.high && candle.low > bar2.low {
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 {
"Hikkake"
}
}
#[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 = Hikkake::new();
assert_eq!(t.name(), "Hikkake");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
}
#[test]
fn bullish_hikkake_is_plus_one() {
let mut t = Hikkake::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, 7.0, 2)), Some(1.0));
}
#[test]
fn bearish_hikkake_is_minus_one() {
let mut t = Hikkake::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(12.0, 14.0, 9.0, 13.0, 2)), Some(-1.0));
}
#[test]
fn not_inside_bar_yields_zero() {
let mut t = Hikkake::new();
t.update(c(10.0, 15.0, 5.0, 12.0, 0));
// bar2 is not contained by bar1 (higher high).
t.update(c(11.0, 16.0, 8.0, 12.0, 1));
assert_eq!(t.update(c(9.0, 12.0, 6.0, 7.0, 2)), Some(0.0));
}
#[test]
fn outside_bar3_yields_zero() {
let mut t = Hikkake::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));
// bar3 engulfs bar2 (higher high and lower low) -> neither direction.
assert_eq!(t.update(c(11.0, 14.0, 7.0, 9.0, 2)), Some(0.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = Hikkake::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<Candle> = (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 - 4.0, i),
}
})
.collect();
let mut a = Hikkake::new();
let mut b = Hikkake::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = Hikkake::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, 7.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));
}
}
+16 -1
View File
@@ -77,6 +77,7 @@ mod elder_impulse;
mod ema;
mod empirical_mode_decomposition;
mod engulfing;
mod evening_doji_star;
mod evwma;
mod fama;
mod fibonacci_pivots;
@@ -90,12 +91,15 @@ mod funding_rate;
mod funding_rate_mean;
mod funding_rate_zscore;
mod gain_loss_ratio;
mod gap_side_by_side_white;
mod garman_klass;
mod gravestone_doji;
mod hammer;
mod hanging_man;
mod harami;
mod heikin_ashi;
mod high_wave;
mod hikkake;
mod hilbert_dominant_cycle;
mod hilo_activator;
mod historical_volatility;
@@ -140,6 +144,7 @@ mod median_price;
mod mfi;
mod microprice;
mod mom;
mod morning_doji_star;
mod morning_evening_star;
mod natr;
mod nvi;
@@ -336,6 +341,7 @@ pub use elder_impulse::ElderImpulse;
pub use ema::Ema;
pub use empirical_mode_decomposition::EmpiricalModeDecomposition;
pub use engulfing::Engulfing;
pub use evening_doji_star::EveningDojiStar;
pub use evwma::Evwma;
pub use fama::Fama;
pub use fibonacci_pivots::{FibonacciPivots, FibonacciPivotsOutput};
@@ -349,12 +355,15 @@ pub use funding_rate::FundingRate;
pub use funding_rate_mean::FundingRateMean;
pub use funding_rate_zscore::FundingRateZScore;
pub use gain_loss_ratio::GainLossRatio;
pub use gap_side_by_side_white::GapSideBySideWhite;
pub use garman_klass::GarmanKlassVolatility;
pub use gravestone_doji::GravestoneDoji;
pub use hammer::Hammer;
pub use hanging_man::HangingMan;
pub use harami::Harami;
pub use heikin_ashi::{HeikinAshi, HeikinAshiOutput};
pub use high_wave::HighWave;
pub use hikkake::Hikkake;
pub use hilbert_dominant_cycle::HilbertDominantCycle;
pub use hilo_activator::HiLoActivator;
pub use historical_volatility::HistoricalVolatility;
@@ -399,6 +408,7 @@ pub use median_price::MedianPrice;
pub use mfi::Mfi;
pub use microprice::Microprice;
pub use mom::Mom;
pub use morning_doji_star::MorningDojiStar;
pub use morning_evening_star::MorningEveningStar;
pub use natr::Natr;
pub use nvi::Nvi;
@@ -800,6 +810,11 @@ pub const FAMILIES: &[(&str, &[&str])] = &[
"GravestoneDoji",
"LongLeggedDoji",
"RickshawMan",
"EveningDojiStar",
"MorningDojiStar",
"GapSideBySideWhite",
"HighWave",
"Hikkake",
],
),
(
@@ -891,6 +906,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, 254, "FAMILIES total drifted from indicator count");
assert_eq!(total, 259, "FAMILIES total drifted from indicator count");
}
}
@@ -0,0 +1,241 @@
//! Morning Doji Star candlestick pattern.
use crate::error::{Error, Result};
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Morning Doji Star — a 3-bar bullish bottom reversal. A long black bar extends
/// the decline, a doji gaps down below it (the star of indecision), then a white
/// bar gaps back up and closes deep into the first body, confirming the turn.
///
/// ```text
/// long body = |close open| >= 0.5 * (high low)
/// doji = |close open| <= 0.1 * (high low)
/// bar1 black & long
/// bar2 doji, body gaps DOWN below bar1 body (max(o2,c2) < close1)
/// bar3 white, body gaps UP above the doji (min(o3,c3) > max(o2,c2))
/// bar3 closes deep into bar1 body (close3 > close1 + penetration·body1)
/// ```
///
/// Output is `+1.0` when the pattern completes and `0.0` otherwise. Morning Doji
/// Star is a single-direction (bullish-only) reversal, so it never emits `1.0`.
/// The first two bars always return `0.0` because the three-bar window is not yet
/// filled. `penetration` is how far into the first body the third bar must close;
/// it defaults to `0.3` (TA-Lib's `CDLMORNINGDOJISTAR` default) and must lie in
/// `[0, 1)`. Body and doji 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` bullish, `0.0` no pattern — so it drops straight into
/// a machine-learning feature matrix as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, MorningDojiStar};
///
/// let mut indicator = MorningDojiStar::new();
/// indicator.update(Candle::new(15.0, 15.1, 9.9, 10.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(8.0, 8.1, 7.9, 8.0, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(9.0, 13.1, 8.9, 13.0, 1.0, 2).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone)]
pub struct MorningDojiStar {
penetration: f64,
prev: Option<Candle>,
prev_prev: Option<Candle>,
has_emitted: bool,
}
impl Default for MorningDojiStar {
fn default() -> Self {
Self::new()
}
}
impl MorningDojiStar {
/// Construct a Morning Doji Star detector with the default 0.3 penetration.
pub const fn new() -> Self {
Self {
penetration: 0.3,
prev: None,
prev_prev: None,
has_emitted: false,
}
}
/// Construct a Morning Doji Star detector with a custom penetration fraction.
///
/// `penetration` must lie in `[0, 1)`.
pub fn with_penetration(penetration: f64) -> Result<Self> {
if !(0.0..1.0).contains(&penetration) {
return Err(Error::InvalidPeriod {
message: "morning doji star penetration must lie in [0, 1)",
});
}
Ok(Self {
penetration,
prev: None,
prev_prev: None,
has_emitted: false,
})
}
/// Configured penetration fraction.
pub fn penetration(&self) -> f64 {
self.penetration
}
}
impl Indicator for MorningDojiStar {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
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);
};
let range1 = bar1.high - bar1.low;
let range2 = bar2.high - bar2.low;
if range1 <= 0.0 || range2 <= 0.0 {
return Some(0.0);
}
let body1 = bar1.open - bar1.close;
if body1 < 0.5 * range1 {
return Some(0.0); // bar1 must be a long black body
}
if (bar2.close - bar2.open).abs() > 0.1 * range2 {
return Some(0.0); // bar2 must be a doji
}
let star_top = bar2.open.max(bar2.close);
let bar3_bottom = candle.open.min(candle.close);
if star_top < bar1.close
&& candle.close > candle.open
&& bar3_bottom > star_top
&& candle.close > bar1.close + self.penetration * body1
{
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 {
"MorningDojiStar"
}
}
#[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 rejects_invalid_penetration() {
assert!(MorningDojiStar::with_penetration(-0.01).is_err());
assert!(MorningDojiStar::with_penetration(1.0).is_err());
}
#[test]
fn accepts_valid_penetration() {
let t = MorningDojiStar::with_penetration(0.5).unwrap();
assert!((t.penetration() - 0.5).abs() < 1e-12);
}
#[test]
fn accessors_and_metadata() {
let t = MorningDojiStar::default();
assert_eq!(t.name(), "MorningDojiStar");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
assert!((t.penetration() - 0.3).abs() < 1e-12);
}
#[test]
fn morning_doji_star_is_plus_one() {
let mut t = MorningDojiStar::new();
assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
assert_eq!(t.update(c(8.0, 8.1, 7.9, 8.0, 1)), Some(0.0));
assert_eq!(t.update(c(9.0, 13.1, 8.9, 13.0, 2)), Some(1.0));
}
#[test]
fn middle_not_doji_yields_zero() {
let mut t = MorningDojiStar::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
// Wide-bodied star, not a doji.
t.update(c(8.0, 10.1, 7.9, 10.0, 1));
assert_eq!(t.update(c(9.0, 13.1, 8.9, 13.0, 2)), Some(0.0));
}
#[test]
fn shallow_close_yields_zero() {
let mut t = MorningDojiStar::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
t.update(c(8.0, 8.1, 7.9, 8.0, 1));
// bar3 white but closes at 11.0 -> only 1.0 into the 5.0 body (< 0.3·5).
assert_eq!(t.update(c(9.0, 11.1, 8.9, 11.0, 2)), Some(0.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = MorningDojiStar::new();
assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
assert_eq!(t.update(c(8.0, 8.1, 7.9, 8.0, 1)), Some(0.0));
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (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 = MorningDojiStar::new();
let mut b = MorningDojiStar::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = MorningDojiStar::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
t.update(c(8.0, 8.1, 7.9, 8.0, 1));
t.update(c(9.0, 13.1, 8.9, 13.0, 2));
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));
}
}
+36 -36
View File
@@ -67,42 +67,42 @@ pub use indicators::{
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, 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, 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,
EmpiricalModeDecomposition, Engulfing, EveningDojiStar, Evwma, Fama, FibonacciPivots,
FibonacciPivotsOutput, FisherTransform, Footprint, FootprintOutput, ForceIndex,
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,
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
+1 -1
View File
@@ -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 **259 indicators** across
- A per-indicator deep dive for every one of the **264 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 &
+6 -1
View File
@@ -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, 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
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
};
/// Convert a flat `f64` stream into a `Vec<Candle>` by chunking it into
@@ -278,6 +278,11 @@ fuzz_target!(|data: Vec<f64>| {
}
// --- Candlestick Patterns (family 14) ---
drive(Hikkake::new, &candles);
drive(HighWave::new, &candles);
drive(GapSideBySideWhite::new, &candles);
drive(MorningDojiStar::new, &candles);
drive(EveningDojiStar::new, &candles);
drive(RickshawMan::new, &candles);
drive(LongLeggedDoji::new, &candles);
drive(GravestoneDoji::new, &candles);