diff --git a/CHANGELOG.md b/CHANGELOG.md index d7963197..e4bf0db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **TA-Lib candlestick patterns (part 1).** New candlestick pattern detectors + matching TA-Lib `CDL*`, emitting the family's signed `+1 / 0 / −1` convention + over OHLCV candles in Rust, Python, Node and WASM: + - **Two Crows** — a three-bar bearish reversal (`CDL2CROWS`): a long white + candle, a black candle whose body gaps up, then a black candle that opens + inside the second's body and closes inside the first's. + - **Upside Gap Two Crows** — a three-bar bearish reversal + (`CDLUPSIDEGAP2CROWS`): two black candles gap up over a long white candle, + the second engulfing the first crow yet still closing above the white body, + leaving the upside gap open. + - **Identical Three Crows** — a three-bar bearish reversal + (`CDLIDENTICAL3CROWS`): three red candles with steadily lower closes, each + opening at the prior candle's close so the bodies stack in an identical + staircase. + - **Three Line Strike** — a four-bar pattern (`CDL3LINESTRIKE`): a + three-candle advance or decline struck by a fourth opposite-colour candle + that engulfs the entire run; bullish `+1`, bearish `−1`. + - **Three Stars in the South** — a rare three-bar bullish reversal + (`CDL3STARSINSOUTH`): three shrinking red candles each carving a higher low + and contracting toward a tiny black marubozu as selling exhausts. - **Derivatives family — funding & open interest (part 1).** A new family of indicators that consume a perpetual / futures tick (`DerivativesTick`, bundling funding rate, mark / index / futures price, open interest, diff --git a/README.md b/README.md index 296fce8b..4c41fa88 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

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

[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml) @@ -47,7 +47,7 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**: [Node](https://docs.wickra.org/Quickstart-Node), [WASM](https://docs.wickra.org/Quickstart-WASM). - **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for - every one of the 244 indicators; start at the + every one of the 249 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 -244 streaming-first indicators across eighteen families. Every one passes the +249 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 | +| 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 | | 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 244 indicators +│ ├── wickra-core/ core engine + all 249 indicators │ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/ │ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds ├── bindings/ diff --git a/bindings/node/__tests__/indicators.test.js b/bindings/node/__tests__/indicators.test.js index 39f29fcd..4eda07ae 100644 --- a/bindings/node/__tests__/indicators.test.js +++ b/bindings/node/__tests__/indicators.test.js @@ -235,6 +235,11 @@ const candleScalar = { SpinningTop: { make: () => new wickra.SpinningTop(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, ThreeInside: { make: () => new wickra.ThreeInside(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, ThreeOutside: { make: () => new wickra.ThreeOutside(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + TwoCrows: { make: () => new wickra.TwoCrows(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + UpsideGapTwoCrows: { make: () => new wickra.UpsideGapTwoCrows(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + IdenticalThreeCrows: { make: () => new wickra.IdenticalThreeCrows(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + ThreeLineStrike: { make: () => new wickra.ThreeLineStrike(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, + ThreeStarsInSouth: { make: () => new wickra.ThreeStarsInSouth(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, }; for (const [name, d] of Object.entries(candleScalar)) { diff --git a/bindings/node/index.d.ts b/bindings/node/index.d.ts index 9077e13d..234ba29b 100644 --- a/bindings/node/index.d.ts +++ b/bindings/node/index.d.ts @@ -2210,6 +2210,51 @@ export declare class ThreeOutside { isReady(): boolean warmupPeriod(): number } +export type TwoCrowsNode = TwoCrows +export declare class TwoCrows { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} +export type UpsideGapTwoCrowsNode = UpsideGapTwoCrows +export declare class UpsideGapTwoCrows { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} +export type IdenticalThreeCrowsNode = IdenticalThreeCrows +export declare class IdenticalThreeCrows { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} +export type ThreeLineStrikeNode = ThreeLineStrike +export declare class ThreeLineStrike { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} +export type ThreeStarsInSouthNode = ThreeStarsInSouth +export declare class ThreeStarsInSouth { + constructor() + update(open: number, high: number, low: number, close: number): number | null + batch(open: Array, high: Array, low: Array, close: Array): Array + reset(): void + isReady(): boolean + warmupPeriod(): number +} export type OrderBookImbalanceTop1Node = OrderBookImbalanceTop1 export declare class OrderBookImbalanceTop1 { constructor() diff --git a/bindings/node/index.js b/bindings/node/index.js index e1c892e1..eee2f986 100644 --- a/bindings/node/index.js +++ b/bindings/node/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, McGinleyDynamic, FRAMA, SuperSmoother, FisherTransform, Decycler, CenterOfGravity, CyberneticCycle, InstantaneousTrendline, EhlersStochastic, RVIVolatility, Variance, CoefficientOfVariation, Skewness, Kurtosis, StandardError, DetrendedStdDev, RSquared, MedianAbsoluteDeviation, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration, RelativeStrengthAB, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, ADXR, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, Inertia, ConnorsRSI, LaguerreRSI, SMI, KST, PGO, RVI, AwesomeOscillatorHistogram, STC, ElderImpulse, ZeroLagMACD, CFO, APO, KAMA, EVWMA, Alligator, JMA, VIDYA, ALMA, T3, TSI, PMO, TII, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, NVI, PVI, VolumeOscillator, KVO, WilliamsAD, AnchoredVWAP, DemandIndex, TSV, VZO, MarketFacilitationIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, HiLoActivator, VoltyStop, YoyoExit, DonchianStop, PercentageTrailingStop, StepTrailingStop, RenkoTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, YangZhangVolatility, RogersSatchellVolatility, GarmanKlassVolatility, ParkinsonVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, WaveTrend, RWI, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA, MaEnvelope, AccelerationBands, StarcBands, AtrBands, HurstChannel, LinRegChannel, StandardErrorBands, DoubleBollinger, TtmSqueeze, FractalChaosBands, VwapStdDevBands, ClassicPivots, FibonacciPivots, Camarilla, WoodiePivots, DemarkPivots, WilliamsFractals, ZigZag, TDSetup, TDSequential, TDDeMarker, TDREI, TDPressure, TDCombo, TDCountdown, TDLines, TDRangeProjection, TDDifferential, TDOpen, TDRiskLevel, InverseFisherTransform, DecyclerOscillator, RoofingFilter, EmpiricalModeDecomposition, HilbertDominantCycle, AdaptiveCycle, SineWave, MAMA, FAMA, Ichimoku, HeikinAshi, ValueArea, InitialBalance, OpeningRange, Doji, Hammer, InvertedHammer, HangingMan, ShootingStar, Engulfing, Harami, MorningEveningStar, ThreeSoldiersOrCrows, PiercingDarkCloud, Marubozu, Tweezer, SpinningTop, ThreeInside, ThreeOutside, 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, 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 @@ -515,6 +515,11 @@ module.exports.Tweezer = Tweezer module.exports.SpinningTop = SpinningTop module.exports.ThreeInside = ThreeInside module.exports.ThreeOutside = ThreeOutside +module.exports.TwoCrows = TwoCrows +module.exports.UpsideGapTwoCrows = UpsideGapTwoCrows +module.exports.IdenticalThreeCrows = IdenticalThreeCrows +module.exports.ThreeLineStrike = ThreeLineStrike +module.exports.ThreeStarsInSouth = ThreeStarsInSouth module.exports.OrderBookImbalanceTop1 = OrderBookImbalanceTop1 module.exports.OrderBookImbalanceFull = OrderBookImbalanceFull module.exports.Microprice = Microprice diff --git a/bindings/node/src/lib.rs b/bindings/node/src/lib.rs index 4d65e58e..e5e9c605 100644 --- a/bindings/node/src/lib.rs +++ b/bindings/node/src/lib.rs @@ -8753,6 +8753,23 @@ node_candle_pattern!(TweezerNode, wc::Tweezer, "Tweezer"); node_candle_pattern!(SpinningTopNode, wc::SpinningTop, "SpinningTop"); node_candle_pattern!(ThreeInsideNode, wc::ThreeInside, "ThreeInside"); node_candle_pattern!(ThreeOutsideNode, wc::ThreeOutside, "ThreeOutside"); +node_candle_pattern!(TwoCrowsNode, wc::TwoCrows, "TwoCrows"); +node_candle_pattern!( + UpsideGapTwoCrowsNode, + wc::UpsideGapTwoCrows, + "UpsideGapTwoCrows" +); +node_candle_pattern!( + IdenticalThreeCrowsNode, + wc::IdenticalThreeCrows, + "IdenticalThreeCrows" +); +node_candle_pattern!(ThreeLineStrikeNode, wc::ThreeLineStrike, "ThreeLineStrike"); +node_candle_pattern!( + ThreeStarsInSouthNode, + wc::ThreeStarsInSouth, + "ThreeStarsInSouth" +); // ============================== Microstructure: Order Book ============================== // diff --git a/bindings/python/python/wickra/__init__.py b/bindings/python/python/wickra/__init__.py index 207be12d..d5106e60 100644 --- a/bindings/python/python/wickra/__init__.py +++ b/bindings/python/python/wickra/__init__.py @@ -240,6 +240,11 @@ from ._wickra import ( SpinningTop, ThreeInside, ThreeOutside, + TwoCrows, + UpsideGapTwoCrows, + IdenticalThreeCrows, + ThreeLineStrike, + ThreeStarsInSouth, # Microstructure: order book OrderBookImbalanceTop1, OrderBookImbalanceTopN, @@ -507,6 +512,11 @@ __all__ = [ "SpinningTop", "ThreeInside", "ThreeOutside", + "TwoCrows", + "UpsideGapTwoCrows", + "IdenticalThreeCrows", + "ThreeLineStrike", + "ThreeStarsInSouth", # Microstructure: order book "OrderBookImbalanceTop1", "OrderBookImbalanceTopN", diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 0aebb4ff..efb86e0a 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -11615,6 +11615,23 @@ candle_pattern_no_param!(PyTweezer, wc::Tweezer, "Tweezer"); candle_pattern_no_param!(PySpinningTop, wc::SpinningTop, "SpinningTop"); candle_pattern_no_param!(PyThreeInside, wc::ThreeInside, "ThreeInside"); candle_pattern_no_param!(PyThreeOutside, wc::ThreeOutside, "ThreeOutside"); +candle_pattern_no_param!(PyTwoCrows, wc::TwoCrows, "TwoCrows"); +candle_pattern_no_param!( + PyUpsideGapTwoCrows, + wc::UpsideGapTwoCrows, + "UpsideGapTwoCrows" +); +candle_pattern_no_param!( + PyIdenticalThreeCrows, + wc::IdenticalThreeCrows, + "IdenticalThreeCrows" +); +candle_pattern_no_param!(PyThreeLineStrike, wc::ThreeLineStrike, "ThreeLineStrike"); +candle_pattern_no_param!( + PyThreeStarsInSouth, + wc::ThreeStarsInSouth, + "ThreeStarsInSouth" +); // ============================== Microstructure: Order Book ============================== // @@ -14112,6 +14129,11 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; // Microstructure: order book. m.add_class::()?; m.add_class::()?; diff --git a/bindings/python/tests/test_new_indicators.py b/bindings/python/tests/test_new_indicators.py index 754f267d..e47995b9 100644 --- a/bindings/python/tests/test_new_indicators.py +++ b/bindings/python/tests/test_new_indicators.py @@ -523,6 +523,26 @@ CANDLE_SCALAR = { lambda: ta.ThreeOutside(), lambda ind, h, l, c, v: ind.batch(c, h, l, c), ), + "TwoCrows": ( + lambda: ta.TwoCrows(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), + "UpsideGapTwoCrows": ( + lambda: ta.UpsideGapTwoCrows(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), + "IdenticalThreeCrows": ( + lambda: ta.IdenticalThreeCrows(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), + "ThreeLineStrike": ( + lambda: ta.ThreeLineStrike(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), + "ThreeStarsInSouth": ( + lambda: ta.ThreeStarsInSouth(), + lambda ind, h, l, c, v: ind.batch(c, h, l, c), + ), } @@ -1851,6 +1871,42 @@ def test_three_outside_reference(): assert t.update((11.5, 13.0, 11.4, 12.5, 1.0, 2)) == pytest.approx(1.0) +def test_two_crows_reference(): + t = ta.TwoCrows() + assert t.update((10.0, 12.2, 9.9, 12.0, 1.0, 0)) == pytest.approx(0.0) + assert t.update((14.0, 14.2, 12.9, 13.0, 1.0, 1)) == pytest.approx(0.0) + assert t.update((13.5, 13.6, 10.9, 11.0, 1.0, 2)) == pytest.approx(-1.0) + + +def test_upside_gap_two_crows_reference(): + t = ta.UpsideGapTwoCrows() + assert t.update((10.0, 12.2, 9.9, 12.0, 1.0, 0)) == pytest.approx(0.0) + assert t.update((14.0, 14.2, 12.9, 13.0, 1.0, 1)) == pytest.approx(0.0) + assert t.update((15.0, 15.2, 12.4, 12.5, 1.0, 2)) == pytest.approx(-1.0) + + +def test_identical_three_crows_reference(): + t = ta.IdenticalThreeCrows() + assert t.update((13.0, 13.1, 11.9, 12.0, 1.0, 0)) == pytest.approx(0.0) + assert t.update((12.0, 12.1, 10.9, 11.0, 1.0, 1)) == pytest.approx(0.0) + assert t.update((11.0, 11.1, 9.9, 10.0, 1.0, 2)) == pytest.approx(-1.0) + + +def test_three_line_strike_reference(): + t = ta.ThreeLineStrike() + assert t.update((10.0, 11.1, 9.9, 11.0, 1.0, 0)) == pytest.approx(0.0) + assert t.update((10.5, 12.1, 10.4, 12.0, 1.0, 1)) == pytest.approx(0.0) + assert t.update((11.5, 13.1, 11.4, 13.0, 1.0, 2)) == pytest.approx(0.0) + assert t.update((13.5, 13.6, 9.4, 9.5, 1.0, 3)) == pytest.approx(1.0) + + +def test_three_stars_in_south_reference(): + t = ta.ThreeStarsInSouth() + assert t.update((20.0, 20.1, 8.0, 15.0, 1.0, 0)) == pytest.approx(0.0) + assert t.update((18.0, 18.1, 12.0, 16.0, 1.0, 1)) == pytest.approx(0.0) + assert t.update((15.0, 15.0, 14.0, 14.0, 1.0, 2)) == pytest.approx(1.0) + + # --- Lifecycle ------------------------------------------------------------ diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 3f4d2305..6f0429fe 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -6330,6 +6330,23 @@ wasm_candle_pattern!(WasmTweezer, wc::Tweezer, Tweezer); wasm_candle_pattern!(WasmSpinningTop, wc::SpinningTop, SpinningTop); wasm_candle_pattern!(WasmThreeInside, wc::ThreeInside, ThreeInside); wasm_candle_pattern!(WasmThreeOutside, wc::ThreeOutside, ThreeOutside); +wasm_candle_pattern!(WasmTwoCrows, wc::TwoCrows, TwoCrows); +wasm_candle_pattern!( + WasmUpsideGapTwoCrows, + wc::UpsideGapTwoCrows, + UpsideGapTwoCrows +); +wasm_candle_pattern!( + WasmIdenticalThreeCrows, + wc::IdenticalThreeCrows, + IdenticalThreeCrows +); +wasm_candle_pattern!(WasmThreeLineStrike, wc::ThreeLineStrike, ThreeLineStrike); +wasm_candle_pattern!( + WasmThreeStarsInSouth, + wc::ThreeStarsInSouth, + ThreeStarsInSouth +); // ============================== Microstructure: Order Book ============================== // diff --git a/crates/wickra-core/src/indicators/identical_three_crows.rs b/crates/wickra-core/src/indicators/identical_three_crows.rs new file mode 100644 index 00000000..22ed3737 --- /dev/null +++ b/crates/wickra-core/src/indicators/identical_three_crows.rs @@ -0,0 +1,230 @@ +//! Identical Three Crows candlestick pattern. + +use crate::error::{Error, Result}; +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Identical Three Crows — a 3-bar bearish reversal: three consecutive red +/// candles with steadily lower closes where each candle opens at (or very near) +/// the prior candle's close, so the bodies stack in an identical staircase. +/// +/// ```text +/// tol_n = tolerance * max(|open|, |prev.close|) +/// all three red (close < open) +/// declining closes (bar2.close < bar1.close, bar3.close < bar2.close) +/// bar2 opens at bar1's close (|bar2.open − bar1.close| <= tol_2) +/// bar3 opens at bar2's close (|bar3.open − bar2.close| <= tol_3) +/// ``` +/// +/// Output is `−1.0` when the pattern completes and `0.0` otherwise. Identical +/// Three Crows is a single-direction (bearish-only) pattern, so it never emits +/// `+1.0`. The first two bars always return `0.0` because the three-bar window +/// is not yet filled. `tolerance` defaults to `0.001` (10 bps relative) and must +/// lie in `[0, 1)`. 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, IdenticalThreeCrows, Indicator}; +/// +/// let mut indicator = IdenticalThreeCrows::new(); +/// indicator.update(Candle::new(13.0, 13.1, 11.9, 12.0, 1.0, 0).unwrap()); +/// indicator.update(Candle::new(12.0, 12.1, 10.9, 11.0, 1.0, 1).unwrap()); +/// let out = indicator +/// .update(Candle::new(11.0, 11.1, 9.9, 10.0, 1.0, 2).unwrap()); +/// assert_eq!(out, Some(-1.0)); +/// ``` +#[derive(Debug, Clone)] +pub struct IdenticalThreeCrows { + tolerance: f64, + prev: Option, + prev_prev: Option, + has_emitted: bool, +} + +impl Default for IdenticalThreeCrows { + fn default() -> Self { + Self::new() + } +} + +impl IdenticalThreeCrows { + /// Construct a detector with the default relative tolerance (1e-3). + pub const fn new() -> Self { + Self { + tolerance: 0.001, + prev: None, + prev_prev: None, + has_emitted: false, + } + } + + /// Construct a detector with a custom relative tolerance. + /// + /// `tolerance` must lie in `[0, 1)`. + pub fn with_tolerance(tolerance: f64) -> Result { + if !(0.0..1.0).contains(&tolerance) { + return Err(Error::InvalidPeriod { + message: "identical three crows tolerance must lie in [0, 1)", + }); + } + Ok(Self { + tolerance, + prev: None, + prev_prev: None, + has_emitted: false, + }) + } + + /// Configured relative tolerance. + pub fn tolerance(&self) -> f64 { + self.tolerance + } +} + +impl Indicator for IdenticalThreeCrows { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let pp = self.prev_prev; + let p = self.prev; + self.prev_prev = self.prev; + self.prev = Some(candle); + let (Some(bar1), Some(bar2)) = (pp, p) else { + return Some(0.0); + }; + let tol2 = self.tolerance * bar2.open.abs().max(bar1.close.abs()); + let tol3 = self.tolerance * candle.open.abs().max(bar2.close.abs()); + if bar1.close < bar1.open + && bar2.close < bar2.open + && candle.close < candle.open + && bar2.close < bar1.close + && candle.close < bar2.close + && (bar2.open - bar1.close).abs() <= tol2 + && (candle.open - bar2.close).abs() <= tol3 + { + 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 { + "IdenticalThreeCrows" + } +} + +#[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_tolerance() { + assert!(IdenticalThreeCrows::with_tolerance(-0.01).is_err()); + assert!(IdenticalThreeCrows::with_tolerance(1.0).is_err()); + } + + #[test] + fn accepts_valid_tolerance() { + let t = IdenticalThreeCrows::with_tolerance(0.0).unwrap(); + assert!((t.tolerance() - 0.0).abs() < 1e-12); + } + + #[test] + fn accessors_and_metadata() { + let t = IdenticalThreeCrows::default(); + assert_eq!(t.name(), "IdenticalThreeCrows"); + assert_eq!(t.warmup_period(), 3); + assert!(!t.is_ready()); + assert!((t.tolerance() - 0.001).abs() < 1e-12); + } + + #[test] + fn identical_three_crows_is_minus_one() { + let mut t = IdenticalThreeCrows::new(); + // Three red candles, each opening at the prior close, declining. + assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), Some(0.0)); + assert_eq!(t.update(c(12.0, 12.1, 10.9, 11.0, 1)), Some(0.0)); + assert_eq!(t.update(c(11.0, 11.1, 9.9, 10.0, 2)), Some(-1.0)); + } + + #[test] + fn non_identical_opens_yield_zero() { + let mut t = IdenticalThreeCrows::new(); + t.update(c(13.0, 13.1, 11.9, 12.0, 0)); + t.update(c(12.0, 12.1, 10.9, 11.0, 1)); + // bar3 opens at 10.0, far from bar2's close (11.0) -> not identical. + assert_eq!(t.update(c(10.0, 10.1, 8.9, 9.0, 2)), Some(0.0)); + } + + #[test] + fn rising_close_yields_zero() { + let mut t = IdenticalThreeCrows::new(); + t.update(c(13.0, 13.1, 11.9, 12.0, 0)); + t.update(c(12.0, 12.1, 10.9, 11.0, 1)); + // bar3 is green -> not three crows. + assert_eq!(t.update(c(11.0, 12.2, 10.9, 12.0, 2)), Some(0.0)); + } + + #[test] + fn first_two_bars_return_zero() { + let mut t = IdenticalThreeCrows::new(); + assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), Some(0.0)); + assert_eq!(t.update(c(12.0, 12.1, 10.9, 11.0, 1)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 - i as f64; + c(base, base + 0.1, base - 1.1, base - 1.0, i) + }) + .collect(); + let mut a = IdenticalThreeCrows::new(); + let mut b = IdenticalThreeCrows::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = IdenticalThreeCrows::new(); + t.update(c(13.0, 13.1, 11.9, 12.0, 0)); + t.update(c(12.0, 12.1, 10.9, 11.0, 1)); + t.update(c(11.0, 11.1, 9.9, 10.0, 2)); + assert!(t.is_ready()); + t.reset(); + assert!(!t.is_ready()); + assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), Some(0.0)); + } +} diff --git a/crates/wickra-core/src/indicators/mod.rs b/crates/wickra-core/src/indicators/mod.rs index 58fe26d7..589d2b81 100644 --- a/crates/wickra-core/src/indicators/mod.rs +++ b/crates/wickra-core/src/indicators/mod.rs @@ -95,6 +95,7 @@ mod hma; mod hurst_channel; mod hurst_exponent; mod ichimoku; +mod identical_three_crows; mod inertia; mod information_ratio; mod initial_balance; @@ -207,8 +208,10 @@ mod td_setup; mod tema; mod term_structure_basis; mod three_inside; +mod three_line_strike; mod three_outside; mod three_soldiers_or_crows; +mod three_stars_in_south; mod tii; mod trade_imbalance; mod treynor_ratio; @@ -219,9 +222,11 @@ mod tsi; mod tsv; mod ttm_squeeze; mod tweezer; +mod two_crows; mod typical_price; mod ulcer_index; mod ultimate_oscillator; +mod upside_gap_two_crows; mod value_area; mod value_at_risk; mod variance; @@ -339,6 +344,7 @@ pub use hma::Hma; pub use hurst_channel::{HurstChannel, HurstChannelOutput}; pub use hurst_exponent::HurstExponent; pub use ichimoku::{Ichimoku, IchimokuOutput}; +pub use identical_three_crows::IdenticalThreeCrows; pub use inertia::Inertia; pub use information_ratio::InformationRatio; pub use initial_balance::{InitialBalance, InitialBalanceOutput}; @@ -451,8 +457,10 @@ pub use td_setup::TdSetup; pub use tema::Tema; pub use term_structure_basis::TermStructureBasis; pub use three_inside::ThreeInside; +pub use three_line_strike::ThreeLineStrike; pub use three_outside::ThreeOutside; pub use three_soldiers_or_crows::ThreeSoldiersOrCrows; +pub use three_stars_in_south::ThreeStarsInSouth; pub use tii::Tii; pub use trade_imbalance::TradeImbalance; pub use treynor_ratio::TreynorRatio; @@ -463,9 +471,11 @@ pub use tsi::Tsi; pub use tsv::Tsv; pub use ttm_squeeze::{TtmSqueeze, TtmSqueezeOutput}; pub use tweezer::Tweezer; +pub use two_crows::TwoCrows; pub use typical_price::TypicalPrice; pub use ulcer_index::UlcerIndex; pub use ultimate_oscillator::UltimateOscillator; +pub use upside_gap_two_crows::UpsideGapTwoCrows; pub use value_area::{ValueArea, ValueAreaOutput}; pub use value_at_risk::ValueAtRisk; pub use variance::Variance; @@ -755,6 +765,11 @@ pub const FAMILIES: &[(&str, &[&str])] = &[ "SpinningTop", "ThreeInside", "ThreeOutside", + "TwoCrows", + "UpsideGapTwoCrows", + "IdenticalThreeCrows", + "ThreeLineStrike", + "ThreeStarsInSouth", ], ), ( @@ -846,6 +861,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, 239, "FAMILIES total drifted from indicator count"); + assert_eq!(total, 244, "FAMILIES total drifted from indicator count"); } } diff --git a/crates/wickra-core/src/indicators/three_line_strike.rs b/crates/wickra-core/src/indicators/three_line_strike.rs new file mode 100644 index 00000000..bd28f0dc --- /dev/null +++ b/crates/wickra-core/src/indicators/three_line_strike.rs @@ -0,0 +1,215 @@ +//! Three Line Strike candlestick pattern. + +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Three Line Strike — a 4-bar pattern: three candles marching in one direction +/// (a three-soldiers / three-crows advance) followed by a fourth candle of the +/// opposite colour that opens beyond the third candle and closes back past the +/// first candle's open, "striking" through the whole run. +/// +/// **Bullish** (`+1.0`): +/// ```text +/// bar1..bar3 green, each opening inside the prior body and closing higher +/// bar4 red & opens above bar3's close & closes below bar1's open +/// ``` +/// +/// **Bearish** (`−1.0`): the mirror — three falling red candles struck by a +/// green bar4 that opens below bar3's close and closes above bar1's open. +/// +/// Output is `0.0` otherwise. The first three bars always return `0.0` because +/// the four-bar window is not yet filled. Pattern-shape check only — no trend +/// filter is applied; combine with a trend indicator for actionable signals. +/// +/// # Signed ±1 encoding +/// +/// This detector emits the uniform candlestick sign convention shared across the +/// pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no pattern — so it +/// drops straight into a machine-learning feature matrix where the bullish and +/// bearish variants occupy a single dimension. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Candle, Indicator, ThreeLineStrike}; +/// +/// let mut indicator = ThreeLineStrike::new(); +/// indicator.update(Candle::new(10.0, 11.1, 9.9, 11.0, 1.0, 0).unwrap()); +/// indicator.update(Candle::new(10.5, 12.1, 10.4, 12.0, 1.0, 1).unwrap()); +/// indicator.update(Candle::new(11.5, 13.1, 11.4, 13.0, 1.0, 2).unwrap()); +/// let out = indicator +/// .update(Candle::new(13.5, 13.6, 9.4, 9.5, 1.0, 3).unwrap()); +/// assert_eq!(out, Some(1.0)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct ThreeLineStrike { + c1: Option, + c2: Option, + c3: Option, + has_emitted: bool, +} + +impl ThreeLineStrike { + /// Construct a new Three Line Strike detector. + pub const fn new() -> Self { + Self { + c1: None, + c2: None, + c3: None, + has_emitted: false, + } + } +} + +impl Indicator for ThreeLineStrike { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let bar1 = self.c1; + let bar2 = self.c2; + let bar3 = self.c3; + self.c1 = self.c2; + self.c2 = self.c3; + self.c3 = Some(candle); + let (Some(bar1), Some(bar2), Some(bar3)) = (bar1, bar2, bar3) else { + return Some(0.0); + }; + // Bullish: three rising green candles struck by a red bar4. + if bar1.close > bar1.open + && bar2.close > bar2.open + && bar3.close > bar3.open + && bar2.open >= bar1.open + && bar2.open <= bar1.close + && bar2.close > bar1.close + && bar3.open >= bar2.open + && bar3.open <= bar2.close + && bar3.close > bar2.close + && candle.close < candle.open + && candle.open > bar3.close + && candle.close < bar1.open + { + return Some(1.0); + } + // Bearish: three falling red candles struck by a green bar4. + if bar1.close < bar1.open + && bar2.close < bar2.open + && bar3.close < bar3.open + && bar2.open <= bar1.open + && bar2.open >= bar1.close + && bar2.close < bar1.close + && bar3.open <= bar2.open + && bar3.open >= bar2.close + && bar3.close < bar2.close + && candle.close > candle.open + && candle.open < bar3.close + && candle.close > bar1.open + { + return Some(-1.0); + } + Some(0.0) + } + + fn reset(&mut self) { + self.c1 = None; + self.c2 = None; + self.c3 = None; + self.has_emitted = false; + } + + fn warmup_period(&self) -> usize { + 4 + } + + fn is_ready(&self) -> bool { + self.has_emitted + } + + fn name(&self) -> &'static str { + "ThreeLineStrike" + } +} + +#[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 = ThreeLineStrike::new(); + assert_eq!(t.name(), "ThreeLineStrike"); + assert_eq!(t.warmup_period(), 4); + assert!(!t.is_ready()); + } + + #[test] + fn bullish_three_line_strike_is_plus_one() { + let mut t = ThreeLineStrike::new(); + assert_eq!(t.update(c(10.0, 11.1, 9.9, 11.0, 0)), Some(0.0)); + assert_eq!(t.update(c(10.5, 12.1, 10.4, 12.0, 1)), Some(0.0)); + assert_eq!(t.update(c(11.5, 13.1, 11.4, 13.0, 2)), Some(0.0)); + assert_eq!(t.update(c(13.5, 13.6, 9.4, 9.5, 3)), Some(1.0)); + } + + #[test] + fn bearish_three_line_strike_is_minus_one() { + let mut t = ThreeLineStrike::new(); + assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), Some(0.0)); + assert_eq!(t.update(c(12.5, 12.6, 10.9, 11.0, 1)), Some(0.0)); + assert_eq!(t.update(c(11.5, 11.6, 9.9, 10.0, 2)), Some(0.0)); + assert_eq!(t.update(c(9.5, 13.6, 9.4, 13.5, 3)), Some(-1.0)); + } + + #[test] + fn strike_not_clearing_first_open_yields_zero() { + let mut t = ThreeLineStrike::new(); + t.update(c(10.0, 11.1, 9.9, 11.0, 0)); + t.update(c(10.5, 12.1, 10.4, 12.0, 1)); + t.update(c(11.5, 13.1, 11.4, 13.0, 2)); + // bar4 closes 10.5, above bar1's open (10.0) -> does not strike through. + assert_eq!(t.update(c(13.5, 13.6, 10.4, 10.5, 3)), Some(0.0)); + } + + #[test] + fn first_three_bars_return_zero() { + let mut t = ThreeLineStrike::new(); + assert_eq!(t.update(c(10.0, 11.1, 9.9, 11.0, 0)), Some(0.0)); + assert_eq!(t.update(c(10.5, 12.1, 10.4, 12.0, 1)), Some(0.0)); + assert_eq!(t.update(c(11.5, 13.1, 11.4, 13.0, 2)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + c(base, base + 1.5, base - 0.2, base + 1.0, i) + }) + .collect(); + let mut a = ThreeLineStrike::new(); + let mut b = ThreeLineStrike::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = ThreeLineStrike::new(); + t.update(c(10.0, 11.1, 9.9, 11.0, 0)); + t.update(c(10.5, 12.1, 10.4, 12.0, 1)); + t.update(c(11.5, 13.1, 11.4, 13.0, 2)); + t.update(c(13.5, 13.6, 9.4, 9.5, 3)); + 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)); + } +} diff --git a/crates/wickra-core/src/indicators/three_stars_in_south.rs b/crates/wickra-core/src/indicators/three_stars_in_south.rs new file mode 100644 index 00000000..a022c6c8 --- /dev/null +++ b/crates/wickra-core/src/indicators/three_stars_in_south.rs @@ -0,0 +1,239 @@ +//! Three Stars in the South candlestick pattern. + +use crate::error::{Error, Result}; +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Three Stars in the South — a rare 3-bar bullish reversal: three shrinking red +/// candles where each session carves out a higher low and contracts toward a +/// tiny black marubozu, signalling exhausted selling at the bottom of a decline. +/// +/// ```text +/// tol = tolerance * max(|bar3.high|, |bar3.low|) +/// all three red (close < open) +/// bar1 long lower shadow (bar1.close − bar1.low) >= (bar1.open − bar1.close) +/// bar2 opens inside bar1's body, higher low, smaller body, closes above bar1.close +/// bar3 small black marubozu (upper & lower shadow <= tol) inside bar2's range +/// ``` +/// +/// Output is `+1.0` when the pattern completes and `0.0` otherwise. Three Stars +/// in the South is a single-direction (bullish-only) pattern, so it never emits +/// `−1.0`. The first two bars always return `0.0` because the three-bar window +/// is not yet filled. `tolerance` defaults to `0.001` (10 bps relative) and must +/// lie in `[0, 1)`. 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, ThreeStarsInSouth}; +/// +/// let mut indicator = ThreeStarsInSouth::new(); +/// indicator.update(Candle::new(20.0, 20.1, 8.0, 15.0, 1.0, 0).unwrap()); +/// indicator.update(Candle::new(18.0, 18.1, 12.0, 16.0, 1.0, 1).unwrap()); +/// let out = indicator +/// .update(Candle::new(15.0, 15.0, 14.0, 14.0, 1.0, 2).unwrap()); +/// assert_eq!(out, Some(1.0)); +/// ``` +#[derive(Debug, Clone)] +pub struct ThreeStarsInSouth { + tolerance: f64, + prev: Option, + prev_prev: Option, + has_emitted: bool, +} + +impl Default for ThreeStarsInSouth { + fn default() -> Self { + Self::new() + } +} + +impl ThreeStarsInSouth { + /// Construct a detector with the default relative tolerance (1e-3). + pub const fn new() -> Self { + Self { + tolerance: 0.001, + prev: None, + prev_prev: None, + has_emitted: false, + } + } + + /// Construct a detector with a custom relative tolerance. + /// + /// `tolerance` must lie in `[0, 1)`. + pub fn with_tolerance(tolerance: f64) -> Result { + if !(0.0..1.0).contains(&tolerance) { + return Err(Error::InvalidPeriod { + message: "three stars in the south tolerance must lie in [0, 1)", + }); + } + Ok(Self { + tolerance, + prev: None, + prev_prev: None, + has_emitted: false, + }) + } + + /// Configured relative tolerance. + pub fn tolerance(&self) -> f64 { + self.tolerance + } +} + +impl Indicator for ThreeStarsInSouth { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let pp = self.prev_prev; + let p = self.prev; + self.prev_prev = self.prev; + self.prev = Some(candle); + let (Some(bar1), Some(bar2)) = (pp, p) else { + return Some(0.0); + }; + let tol = self.tolerance * candle.high.abs().max(candle.low.abs()); + let bar1_body = bar1.open - bar1.close; + let bar1_lower_shadow = bar1.close - bar1.low; + let bar2_body = bar2.open - bar2.close; + let bar3_upper_shadow = candle.high - candle.open; + let bar3_lower_shadow = candle.close - candle.low; + if bar1.close < bar1.open + && bar2.close < bar2.open + && candle.close < candle.open + && bar1_lower_shadow >= bar1_body + && bar2.open <= bar1.open + && bar2.open >= bar1.close + && bar2.low > bar1.low + && bar2.close > bar1.close + && bar2_body < bar1_body + && bar3_upper_shadow <= tol + && bar3_lower_shadow <= tol + && 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 { + "ThreeStarsInSouth" + } +} + +#[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_tolerance() { + assert!(ThreeStarsInSouth::with_tolerance(-0.01).is_err()); + assert!(ThreeStarsInSouth::with_tolerance(1.0).is_err()); + } + + #[test] + fn accepts_valid_tolerance() { + let t = ThreeStarsInSouth::with_tolerance(0.0).unwrap(); + assert!((t.tolerance() - 0.0).abs() < 1e-12); + } + + #[test] + fn accessors_and_metadata() { + let t = ThreeStarsInSouth::default(); + assert_eq!(t.name(), "ThreeStarsInSouth"); + assert_eq!(t.warmup_period(), 3); + assert!(!t.is_ready()); + assert!((t.tolerance() - 0.001).abs() < 1e-12); + } + + #[test] + fn three_stars_in_south_is_plus_one() { + let mut t = ThreeStarsInSouth::new(); + assert_eq!(t.update(c(20.0, 20.1, 8.0, 15.0, 0)), Some(0.0)); + assert_eq!(t.update(c(18.0, 18.1, 12.0, 16.0, 1)), Some(0.0)); + assert_eq!(t.update(c(15.0, 15.0, 14.0, 14.0, 2)), Some(1.0)); + } + + #[test] + fn third_with_shadow_yields_zero() { + let mut t = ThreeStarsInSouth::new(); + t.update(c(20.0, 20.1, 8.0, 15.0, 0)); + t.update(c(18.0, 18.1, 12.0, 16.0, 1)); + // bar3 has a long lower shadow -> not a marubozu. + assert_eq!(t.update(c(15.0, 15.0, 12.5, 14.0, 2)), Some(0.0)); + } + + #[test] + fn lower_low_yields_zero() { + let mut t = ThreeStarsInSouth::new(); + t.update(c(20.0, 20.1, 8.0, 15.0, 0)); + // bar2 dips below bar1's low -> no higher low. + assert_eq!(t.update(c(18.0, 18.1, 7.0, 16.0, 1)), Some(0.0)); + assert_eq!(t.update(c(15.0, 15.0, 14.0, 14.0, 2)), Some(0.0)); + } + + #[test] + fn first_two_bars_return_zero() { + let mut t = ThreeStarsInSouth::new(); + assert_eq!(t.update(c(20.0, 20.1, 8.0, 15.0, 0)), Some(0.0)); + assert_eq!(t.update(c(18.0, 18.1, 12.0, 16.0, 1)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + c(base + 2.0, base + 2.1, base - 3.0, base, i) + }) + .collect(); + let mut a = ThreeStarsInSouth::new(); + let mut b = ThreeStarsInSouth::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = ThreeStarsInSouth::new(); + t.update(c(20.0, 20.1, 8.0, 15.0, 0)); + t.update(c(18.0, 18.1, 12.0, 16.0, 1)); + t.update(c(15.0, 15.0, 14.0, 14.0, 2)); + assert!(t.is_ready()); + t.reset(); + assert!(!t.is_ready()); + assert_eq!(t.update(c(20.0, 20.1, 8.0, 15.0, 0)), Some(0.0)); + } +} diff --git a/crates/wickra-core/src/indicators/two_crows.rs b/crates/wickra-core/src/indicators/two_crows.rs new file mode 100644 index 00000000..c45afc48 --- /dev/null +++ b/crates/wickra-core/src/indicators/two_crows.rs @@ -0,0 +1,186 @@ +//! Two Crows candlestick pattern. + +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Two Crows — a 3-bar bearish reversal pattern that appears after an advance. +/// +/// ```text +/// bar1 green (long white) +/// bar2 red & its body gaps up above bar1's body (bar2.close > bar1.close) +/// bar3 red & opens inside bar2's body (bar2.close < bar3.open < bar2.open) +/// & closes inside bar1's body (bar1.open < bar3.close < bar1.close) +/// ``` +/// +/// Output is `−1.0` when the pattern completes and `0.0` otherwise. Two Crows is +/// a single-direction (bearish-only) pattern, so it never emits `+1.0`. The +/// first two bars always return `0.0` because the three-bar window is not yet +/// filled. Pattern-shape check only — no trend filter is applied; combine with a +/// trend indicator for actionable signals. +/// +/// # Signed ±1 encoding +/// +/// This detector emits the uniform candlestick sign convention shared across the +/// pattern family — `−1.0` bearish, `0.0` no pattern — so it drops straight into +/// a machine-learning feature matrix as a single dimension. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Candle, Indicator, TwoCrows}; +/// +/// let mut indicator = TwoCrows::new(); +/// indicator.update(Candle::new(10.0, 12.2, 9.9, 12.0, 1.0, 0).unwrap()); +/// indicator.update(Candle::new(14.0, 14.2, 12.9, 13.0, 1.0, 1).unwrap()); +/// let out = indicator +/// .update(Candle::new(13.5, 13.6, 10.9, 11.0, 1.0, 2).unwrap()); +/// assert_eq!(out, Some(-1.0)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct TwoCrows { + prev: Option, + prev_prev: Option, + has_emitted: bool, +} + +impl TwoCrows { + /// Construct a new Two Crows detector. + pub const fn new() -> Self { + Self { + prev: None, + prev_prev: None, + has_emitted: false, + } + } +} + +impl Indicator for TwoCrows { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let pp = self.prev_prev; + let p = self.prev; + self.prev_prev = self.prev; + self.prev = Some(candle); + let (Some(bar1), Some(bar2)) = (pp, p) else { + return Some(0.0); + }; + if bar1.close > bar1.open + && bar2.close < bar2.open + && bar2.close > bar1.close + && candle.close < candle.open + && candle.open < bar2.open + && candle.open > bar2.close + && candle.close > bar1.open + && candle.close < bar1.close + { + 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 { + "TwoCrows" + } +} + +#[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 = TwoCrows::new(); + assert_eq!(t.name(), "TwoCrows"); + assert_eq!(t.warmup_period(), 3); + assert!(!t.is_ready()); + } + + #[test] + fn two_crows_is_minus_one() { + let mut t = TwoCrows::new(); + // bar1 green 10->12; bar2 red 14->13 (body above bar1); bar3 red + // opens 13.5 (inside [13,14]) and closes 11 (inside [10,12]). + assert_eq!(t.update(c(10.0, 12.2, 9.9, 12.0, 0)), Some(0.0)); + assert_eq!(t.update(c(14.0, 14.2, 12.9, 13.0, 1)), Some(0.0)); + assert_eq!(t.update(c(13.5, 13.6, 10.9, 11.0, 2)), Some(-1.0)); + } + + #[test] + fn no_gap_up_yields_zero() { + let mut t = TwoCrows::new(); + // bar2 red but its body does not gap above bar1's body. + t.update(c(10.0, 12.2, 9.9, 12.0, 0)); + t.update(c(11.5, 12.0, 10.4, 11.0, 1)); + assert_eq!(t.update(c(11.0, 11.2, 9.9, 10.5, 2)), Some(0.0)); + } + + #[test] + fn third_close_below_first_body_yields_zero() { + let mut t = TwoCrows::new(); + t.update(c(10.0, 12.2, 9.9, 12.0, 0)); + t.update(c(14.0, 14.2, 12.9, 13.0, 1)); + // bar3 closes 9.5, below bar1's body low (10) -> not Two Crows. + assert_eq!(t.update(c(13.5, 13.6, 9.4, 9.5, 2)), Some(0.0)); + } + + #[test] + fn first_two_bars_return_zero() { + let mut t = TwoCrows::new(); + assert_eq!(t.update(c(10.0, 12.2, 9.9, 12.0, 0)), Some(0.0)); + assert_eq!(t.update(c(14.0, 14.2, 12.9, 13.0, 1)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + if i % 3 == 0 { + c(base, base + 0.5, base - 1.0, base + 0.4, i) + } else { + c(base + 1.5, base + 1.7, base - 0.2, base + 0.6, i) + } + }) + .collect(); + let mut a = TwoCrows::new(); + let mut b = TwoCrows::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut t = TwoCrows::new(); + t.update(c(10.0, 12.2, 9.9, 12.0, 0)); + t.update(c(14.0, 14.2, 12.9, 13.0, 1)); + t.update(c(13.5, 13.6, 10.9, 11.0, 2)); + assert!(t.is_ready()); + t.reset(); + assert!(!t.is_ready()); + assert_eq!(t.update(c(10.0, 12.2, 9.9, 12.0, 0)), Some(0.0)); + } +} diff --git a/crates/wickra-core/src/indicators/upside_gap_two_crows.rs b/crates/wickra-core/src/indicators/upside_gap_two_crows.rs new file mode 100644 index 00000000..423a8482 --- /dev/null +++ b/crates/wickra-core/src/indicators/upside_gap_two_crows.rs @@ -0,0 +1,189 @@ +//! Upside Gap Two Crows candlestick pattern. + +use crate::ohlcv::Candle; +use crate::traits::Indicator; + +/// Upside Gap Two Crows — a 3-bar bearish reversal that appears after an +/// advance. Two black candles gap up above a long white candle; the second +/// black candle engulfs the first crow yet still closes above the white body, +/// leaving the upside gap open. +/// +/// ```text +/// bar1 green (long white) +/// bar2 red & its body gaps up above bar1's body (bar2.close > bar1.close) +/// bar3 red & opens above bar2's open (bar3.open > bar2.open) +/// & closes below bar2's close (bar3.close < bar2.close) +/// & closes above bar1's close (bar3.close > bar1.close) +/// ``` +/// +/// Output is `−1.0` when the pattern completes and `0.0` otherwise. Upside Gap +/// Two Crows is a single-direction (bearish-only) pattern, so it never emits +/// `+1.0`. The first two bars always return `0.0` because the three-bar window +/// is not yet filled. Pattern-shape check only — no trend filter is applied; +/// combine with a trend indicator for actionable signals. +/// +/// # Signed ±1 encoding +/// +/// This detector emits the uniform candlestick sign convention shared across the +/// pattern family — `−1.0` bearish, `0.0` no pattern — so it drops straight into +/// a machine-learning feature matrix as a single dimension. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Candle, Indicator, UpsideGapTwoCrows}; +/// +/// let mut indicator = UpsideGapTwoCrows::new(); +/// indicator.update(Candle::new(10.0, 12.2, 9.9, 12.0, 1.0, 0).unwrap()); +/// indicator.update(Candle::new(14.0, 14.2, 12.9, 13.0, 1.0, 1).unwrap()); +/// let out = indicator +/// .update(Candle::new(15.0, 15.2, 12.4, 12.5, 1.0, 2).unwrap()); +/// assert_eq!(out, Some(-1.0)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct UpsideGapTwoCrows { + prev: Option, + prev_prev: Option, + has_emitted: bool, +} + +impl UpsideGapTwoCrows { + /// Construct a new Upside Gap Two Crows detector. + pub const fn new() -> Self { + Self { + prev: None, + prev_prev: None, + has_emitted: false, + } + } +} + +impl Indicator for UpsideGapTwoCrows { + type Input = Candle; + type Output = f64; + + fn update(&mut self, candle: Candle) -> Option { + self.has_emitted = true; + let pp = self.prev_prev; + let p = self.prev; + self.prev_prev = self.prev; + self.prev = Some(candle); + let (Some(bar1), Some(bar2)) = (pp, p) else { + return Some(0.0); + }; + if bar1.close > bar1.open + && bar2.close < bar2.open + && bar2.close > bar1.close + && candle.close < candle.open + && candle.open > bar2.open + && candle.close < bar2.close + && candle.close > bar1.close + { + 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 { + "UpsideGapTwoCrows" + } +} + +#[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 u = UpsideGapTwoCrows::new(); + assert_eq!(u.name(), "UpsideGapTwoCrows"); + assert_eq!(u.warmup_period(), 3); + assert!(!u.is_ready()); + } + + #[test] + fn upside_gap_two_crows_is_minus_one() { + let mut u = UpsideGapTwoCrows::new(); + // bar1 green 10->12; bar2 red 14->13 gapping up; bar3 red opens 15 + // (above bar2 open) and closes 12.5 (below bar2 close, above bar1 close). + assert_eq!(u.update(c(10.0, 12.2, 9.9, 12.0, 0)), Some(0.0)); + assert_eq!(u.update(c(14.0, 14.2, 12.9, 13.0, 1)), Some(0.0)); + assert_eq!(u.update(c(15.0, 15.2, 12.4, 12.5, 2)), Some(-1.0)); + } + + #[test] + fn closing_the_first_gap_yields_zero() { + let mut u = UpsideGapTwoCrows::new(); + u.update(c(10.0, 12.2, 9.9, 12.0, 0)); + u.update(c(14.0, 14.2, 12.9, 13.0, 1)); + // bar3 closes 11.5, below bar1's close (12) -> gap closed, not the pattern. + assert_eq!(u.update(c(15.0, 15.2, 11.4, 11.5, 2)), Some(0.0)); + } + + #[test] + fn no_gap_up_yields_zero() { + let mut u = UpsideGapTwoCrows::new(); + u.update(c(10.0, 12.2, 9.9, 12.0, 0)); + // bar2's body does not gap above bar1's body. + u.update(c(11.5, 12.0, 10.4, 11.0, 1)); + assert_eq!(u.update(c(12.0, 12.2, 10.9, 11.5, 2)), Some(0.0)); + } + + #[test] + fn first_two_bars_return_zero() { + let mut u = UpsideGapTwoCrows::new(); + assert_eq!(u.update(c(10.0, 12.2, 9.9, 12.0, 0)), Some(0.0)); + assert_eq!(u.update(c(14.0, 14.2, 12.9, 13.0, 1)), Some(0.0)); + } + + #[test] + fn batch_equals_streaming() { + let candles: Vec = (0..40) + .map(|i| { + let base = 100.0 + i as f64; + if i % 3 == 0 { + c(base, base + 0.5, base - 1.0, base + 0.4, i) + } else { + c(base + 1.5, base + 1.7, base - 0.2, base + 0.6, i) + } + }) + .collect(); + let mut a = UpsideGapTwoCrows::new(); + let mut b = UpsideGapTwoCrows::new(); + assert_eq!( + a.batch(&candles), + candles.iter().map(|x| b.update(*x)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut u = UpsideGapTwoCrows::new(); + u.update(c(10.0, 12.2, 9.9, 12.0, 0)); + u.update(c(14.0, 14.2, 12.9, 13.0, 1)); + u.update(c(15.0, 15.2, 12.4, 12.5, 2)); + assert!(u.is_ready()); + u.reset(); + assert!(!u.is_ready()); + assert_eq!(u.update(c(10.0, 12.2, 9.9, 12.0, 0)), Some(0.0)); + } +} diff --git a/crates/wickra-core/src/lib.rs b/crates/wickra-core/src/lib.rs index c067abc6..0a9b3edc 100644 --- a/crates/wickra-core/src/lib.rs +++ b/crates/wickra-core/src/lib.rs @@ -65,15 +65,15 @@ pub use indicators::{ FractalChaosBandsOutput, Frama, FundingBasis, FundingRate, FundingRateMean, FundingRateZScore, GainLossRatio, GarmanKlassVolatility, Hammer, HangingMan, Harami, HeikinAshi, HeikinAshiOutput, HiLoActivator, HilbertDominantCycle, HistoricalVolatility, Hma, HurstChannel, - HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput, Inertia, InformationRatio, - InitialBalance, InitialBalanceOutput, InstantaneousTrendline, InverseFisherTransform, - InvertedHammer, Jma, Kama, KellyCriterion, Keltner, KeltnerOutput, Kst, KstOutput, Kurtosis, - Kvo, KylesLambda, LaguerreRsi, LeadLagCrossCorrelation, LeadLagCrossCorrelationOutput, - LinRegAngle, LinRegChannel, LinRegChannelOutput, LinRegSlope, LinearRegression, - LiquidationFeatures, LiquidationFeaturesOutput, LongShortRatio, MaEnvelope, MaEnvelopeOutput, - MacdIndicator, MacdOutput, Mama, MamaOutput, MarketFacilitationIndex, Marubozu, MassIndex, - MaxDrawdown, McGinleyDynamic, MedianAbsoluteDeviation, MedianPrice, Mfi, Microprice, Mom, - MorningEveningStar, Natr, Nvi, OIPriceDivergence, OIWeighted, Obv, OmegaRatio, + HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput, IdenticalThreeCrows, Inertia, + InformationRatio, InitialBalance, InitialBalanceOutput, InstantaneousTrendline, + InverseFisherTransform, InvertedHammer, Jma, Kama, KellyCriterion, Keltner, KeltnerOutput, Kst, + KstOutput, Kurtosis, Kvo, KylesLambda, LaguerreRsi, LeadLagCrossCorrelation, + LeadLagCrossCorrelationOutput, LinRegAngle, LinRegChannel, LinRegChannelOutput, LinRegSlope, + LinearRegression, LiquidationFeatures, LiquidationFeaturesOutput, LongShortRatio, MaEnvelope, + MaEnvelopeOutput, MacdIndicator, MacdOutput, Mama, MamaOutput, MarketFacilitationIndex, + Marubozu, MassIndex, MaxDrawdown, McGinleyDynamic, MedianAbsoluteDeviation, MedianPrice, Mfi, + Microprice, Mom, MorningEveningStar, Natr, Nvi, OIPriceDivergence, OIWeighted, Obv, OmegaRatio, OpenInterestDelta, OpeningRange, OpeningRangeOutput, OrderBookImbalanceFull, OrderBookImbalanceTop1, OrderBookImbalanceTopN, PainIndex, PairSpreadZScore, PairwiseBeta, ParkinsonVolatility, PearsonCorrelation, PercentB, PercentageTrailingStop, Pgo, @@ -86,9 +86,10 @@ pub use indicators::{ StochasticOutput, SuperSmoother, SuperTrend, SuperTrendOutput, TakerBuySellRatio, TdCombo, TdCountdown, TdDeMarker, TdDifferential, TdLines, TdLinesOutput, TdOpen, TdPressure, TdRangeProjection, TdRangeProjectionOutput, TdRei, TdRiskLevel, TdRiskLevelOutput, - TdSequential, TdSequentialOutput, TdSetup, Tema, TermStructureBasis, ThreeInside, ThreeOutside, - ThreeSoldiersOrCrows, Tii, TradeImbalance, TreynorRatio, Trima, Trix, TrueRange, Tsi, Tsv, - TtmSqueeze, TtmSqueezeOutput, Tweezer, TypicalPrice, UlcerIndex, UltimateOscillator, ValueArea, + 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, diff --git a/fuzz/fuzz_targets/indicator_update_candle.rs b/fuzz/fuzz_targets/indicator_update_candle.rs index 38b1b76b..039fb314 100644 --- a/fuzz/fuzz_targets/indicator_update_candle.rs +++ b/fuzz/fuzz_targets/indicator_update_candle.rs @@ -29,14 +29,17 @@ use wickra_core::{ ChaikinOscillator, ChaikinVolatility, ChandeKrollStop, ChandelierExit, ChoppinessIndex, ClassicPivots, DemandIndex, DemarkPivots, Doji, Donchian, DonchianStop, EaseOfMovement, Engulfing, Evwma, FibonacciPivots, ForceIndex, FractalChaosBands, GarmanKlassVolatility, - Hammer, HangingMan, Harami, HeikinAshi, HiLoActivator, HurstChannel, Ichimoku, Indicator, + Hammer, HangingMan, Harami, HeikinAshi, HiLoActivator, HurstChannel, Ichimoku, + IdenticalThreeCrows, Indicator, Inertia, InitialBalance, InvertedHammer, Keltner, Kvo, MarketFacilitationIndex, Marubozu, MassIndex, MedianPrice, Mfi, MorningEveningStar, Natr, Nvi, Obv, OpeningRange, ParkinsonVolatility, Pgo, PiercingDarkCloud, Psar, Pvi, RogersSatchellVolatility, RollingVwap, Rvi, Rwi, ShootingStar, Smi, SpinningTop, StarcBands, Stochastic, SuperTrend, TdCombo, TdCountdown, TdDeMarker, TdDifferential, TdLines, TdOpen, TdPressure, TdRangeProjection, TdRei, - TdRiskLevel, TdSequential, TdSetup, ThreeInside, ThreeOutside, ThreeSoldiersOrCrows, TrueRange, - Tsv, TtmSqueeze, Tweezer, TypicalPrice, UltimateOscillator, ValueArea, VoltyStop, + 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, @@ -304,10 +307,15 @@ fuzz_target!(|data: Vec| { drive(Harami::new, &candles); drive(MorningEveningStar::new, &candles); drive(ThreeSoldiersOrCrows::new, &candles); + drive(ThreeStarsInSouth::new, &candles); drive(PiercingDarkCloud::new, &candles); drive(Marubozu::new, &candles); drive(Tweezer::new, &candles); drive(SpinningTop::new, &candles); drive(ThreeInside::new, &candles); + drive(ThreeLineStrike::new, &candles); drive(ThreeOutside::new, &candles); + drive(TwoCrows::new, &candles); + drive(UpsideGapTwoCrows::new, &candles); + drive(IdenticalThreeCrows::new, &candles); });