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:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
[](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)) {
|
||||
|
||||
Vendored
+45
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ==============================
|
||||
//
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 ------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 &
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user