feat: TA-Lib candlestick patterns — gap-three-methods/stalled/stick-sandwich/takuri (part 8 of 9) (#140)

Adds five TA-Lib candlestick patterns, each a streaming `Indicator<Input = Candle, Output = f64>` emitting the family's uniform `±1.0 / 0.0` sign convention, fully wired across the Rust core, Python / Node / WASM bindings, fuzz target and reference tests.

- **Upside Gap Three Methods** (`CDLXSIDEGAP3METHODS`) — a 3-bar bullish continuation: two white candles gap up, then a black candle opens within the second body and closes within the first; bullish +1.
- **Downside Gap Three Methods** (`CDLXSIDEGAP3METHODS`) — the bearish mirror: two black candles gap down, then a white candle opens within the second body and closes within the first; bearish -1.
- **Stalled Pattern** (`CDLSTALLEDPATTERN`) — a 3-bar bearish reversal warning: two long white candles then a small white candle riding the shoulder, signalling the rally is stalling; bearish -1.
- **Stick Sandwich** (`CDLSTICKSANDWICH`) — a 3-bar bullish reversal: two black candles closing at the same level sandwich a white candle, marking a support floor; bullish +1.
- **Takuri** (`CDLTAKURI`) — a single-bar bullish reversal, a strict Dragonfly Doji with a negligible upper shadow and very long lower shadow; bullish +1.

Body and shadow thresholds follow the geometric house style (fixed fractions of the bar range) rather than TA-Lib's rolling averages. Upside / Downside Gap Three Methods share the `CDLXSIDEGAP3METHODS` code, so the second carries a manual CHANGELOG entry (as with Rising / Falling Three Methods).

Counter 279 → 284 (mod-count == lib counted block; FAMILIES total 274 → 279).

Stacked on #139 (`feat/cdl-lines`); base retargets to `main` once the predecessor merges.
This commit is contained in:
kingchenc
2026-06-02 17:24:42 +02:00
committed by GitHub
parent c2c85c7ecf
commit 4d0bc08efd
19 changed files with 1262 additions and 36 deletions
+5
View File
@@ -62,6 +62,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Short Line** — a single short-bodied candle with short shadows; bullish +1 (white) or bearish -1 (black) by colour (`CDLSHORTLINE`).
- **Rising Three Methods** — a 5-bar bullish continuation: a long white candle, three small pullback bars holding within its range, then a white breakout to new highs; bullish +1 (`CDLRISEFALL3METHODS`).
- **Falling Three Methods** — the bearish mirror of rising three methods: a long black candle, three small bars holding within its range, then a black breakdown to new lows; bearish -1 (`CDLRISEFALL3METHODS`).
- **Upside Gap Three Methods** — a 3-bar bullish continuation: two white candles gap up, then a black candle opens within the second body and closes within the first; bullish +1 (`CDLXSIDEGAP3METHODS`).
- **Downside Gap Three Methods** — the bearish mirror of upside gap three methods: two black candles gap down, then a white candle opens within the second body and closes within the first; bearish -1 (`CDLXSIDEGAP3METHODS`).
- **Stalled Pattern** — a 3-bar bearish reversal warning: two long white candles then a small white candle riding the shoulder, signalling the rally is stalling; bearish -1 (`CDLSTALLEDPATTERN`).
- **Stick Sandwich** — a 3-bar bullish reversal: two black candles closing at the same level sandwich a white candle, marking a support floor; bullish +1 (`CDLSTICKSANDWICH`).
- **Takuri** — a single-bar bullish reversal, a strict Dragonfly Doji with a negligible upper shadow and very long lower shadow; bullish +1 (`CDLTAKURI`).
- **Derivatives family — funding & open interest (part 1).** A new family of
indicators that consume a perpetual / futures tick (`DerivativesTick`,
bundling funding rate, mark / index / futures price, open interest,
+5 -5
View File
@@ -1,5 +1,5 @@
<p align="center">
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=279" 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=284" alt="Wickra — streaming-first technical indicators" width="100%"></a>
</p>
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
@@ -47,7 +47,7 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
[Node](https://docs.wickra.org/Quickstart-Node),
[WASM](https://docs.wickra.org/Quickstart-WASM).
- **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for
every one of the 279 indicators; start at the
every one of the 284 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
279 streaming-first indicators across eighteen families. Every one passes the
284 streaming-first indicators across eighteen families. Every one passes the
`batch == streaming` equivalence test, reference-value tests, and reset
semantics tests. Each has a per-indicator deep dive (formula, parameters,
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
@@ -155,7 +155,7 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
| Pivots & S/R | Classic Pivots, Fibonacci Pivots, Camarilla, Woodie Pivots, DeMark Pivots, Williams Fractals, ZigZag |
| DeMark | TD Setup, TD Sequential, TD DeMarker, TD REI, TD Pressure, TD Combo, TD Countdown, TD Lines, TD Range Projection, TD Differential, TD Open, TD Risk Level |
| Ichimoku & Charts | Ichimoku Kinko Hyo (Tenkan, Kijun, Senkou A/B, Chikou), Heikin-Ashi |
| Candlestick Patterns | Doji, Hammer, Inverted Hammer, Hanging Man, Shooting Star, Engulfing, Harami, Morning/Evening Star, Three White Soldiers/Black Crows, Piercing Line/Dark Cloud Cover, Marubozu, Tweezer, Spinning Top, Three Inside Up/Down, Three Outside Up/Down, Two Crows, Upside Gap Two Crows, Identical Three Crows, Three Line Strike, Three Stars in the South, Abandoned Baby, Advance Block, Belt-hold, Breakaway, Counterattack, Doji Star, Dragonfly Doji, Gravestone Doji, Long-Legged Doji, Rickshaw Man, Evening Doji Star, Morning Doji Star, Gap Side-by-Side White, High-Wave, Hikkake, Modified Hikkake, Homing Pigeon, On-Neck, In-Neck, Thrusting, Separating Lines, Kicking, Kicking by Length, Ladder Bottom, Mat Hold, Matching Low, Long Line, Short Line, Rising Three Methods, Falling Three Methods |
| Candlestick Patterns | Doji, Hammer, Inverted Hammer, Hanging Man, Shooting Star, Engulfing, Harami, Morning/Evening Star, Three White Soldiers/Black Crows, Piercing Line/Dark Cloud Cover, Marubozu, Tweezer, Spinning Top, Three Inside Up/Down, Three Outside Up/Down, Two Crows, Upside Gap Two Crows, Identical Three Crows, Three Line Strike, Three Stars in the South, Abandoned Baby, Advance Block, Belt-hold, Breakaway, Counterattack, Doji Star, Dragonfly Doji, Gravestone Doji, Long-Legged Doji, Rickshaw Man, Evening Doji Star, Morning Doji Star, Gap Side-by-Side White, High-Wave, Hikkake, Modified Hikkake, Homing Pigeon, On-Neck, In-Neck, Thrusting, Separating Lines, Kicking, Kicking by Length, Ladder Bottom, Mat Hold, Matching Low, Long Line, Short Line, Rising Three Methods, Falling Three Methods, Upside Gap Three Methods, Downside Gap Three Methods, Stalled Pattern, Stick Sandwich, Takuri |
| 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 279 indicators
│ ├── wickra-core/ core engine + all 284 indicators
│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/
│ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds
├── bindings/
@@ -270,6 +270,11 @@ const candleScalar = {
ShortLine: { make: () => new wickra.ShortLine(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
RisingThreeMethods: { make: () => new wickra.RisingThreeMethods(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
FallingThreeMethods: { make: () => new wickra.FallingThreeMethods(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
UpsideGapThreeMethods: { make: () => new wickra.UpsideGapThreeMethods(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
DownsideGapThreeMethods: { make: () => new wickra.DownsideGapThreeMethods(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
StalledPattern: { make: () => new wickra.StalledPattern(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
StickSandwich: { make: () => new wickra.StickSandwich(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
Takuri: { make: () => new wickra.Takuri(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
};
for (const [name, d] of Object.entries(candleScalar)) {
+45
View File
@@ -2525,6 +2525,51 @@ export declare class FallingThreeMethods {
isReady(): boolean
warmupPeriod(): number
}
export type UpsideGapThreeMethodsNode = UpsideGapThreeMethods
export declare class UpsideGapThreeMethods {
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 DownsideGapThreeMethodsNode = DownsideGapThreeMethods
export declare class DownsideGapThreeMethods {
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 StalledPatternNode = StalledPattern
export declare class StalledPattern {
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 StickSandwichNode = StickSandwich
export declare class StickSandwich {
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 TakuriNode = Takuri
export declare class Takuri {
constructor()
update(open: number, high: number, low: number, close: number): number | null
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type OrderBookImbalanceTop1Node = OrderBookImbalanceTop1
export declare class OrderBookImbalanceTop1 {
constructor()
+6 -1
View File
@@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, McGinleyDynamic, FRAMA, SuperSmoother, FisherTransform, Decycler, CenterOfGravity, CyberneticCycle, InstantaneousTrendline, EhlersStochastic, RVIVolatility, Variance, CoefficientOfVariation, Skewness, Kurtosis, StandardError, DetrendedStdDev, RSquared, MedianAbsoluteDeviation, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration, RelativeStrengthAB, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, ADXR, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, Inertia, ConnorsRSI, LaguerreRSI, SMI, KST, PGO, RVI, AwesomeOscillatorHistogram, STC, ElderImpulse, ZeroLagMACD, CFO, APO, KAMA, EVWMA, Alligator, JMA, VIDYA, ALMA, T3, TSI, PMO, TII, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, NVI, PVI, VolumeOscillator, KVO, WilliamsAD, AnchoredVWAP, DemandIndex, TSV, VZO, MarketFacilitationIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, HiLoActivator, VoltyStop, YoyoExit, DonchianStop, PercentageTrailingStop, StepTrailingStop, RenkoTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, YangZhangVolatility, RogersSatchellVolatility, GarmanKlassVolatility, ParkinsonVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, WaveTrend, RWI, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA, MaEnvelope, AccelerationBands, StarcBands, AtrBands, HurstChannel, LinRegChannel, StandardErrorBands, DoubleBollinger, TtmSqueeze, FractalChaosBands, VwapStdDevBands, ClassicPivots, FibonacciPivots, Camarilla, WoodiePivots, DemarkPivots, WilliamsFractals, ZigZag, TDSetup, TDSequential, TDDeMarker, TDREI, TDPressure, TDCombo, TDCountdown, TDLines, TDRangeProjection, TDDifferential, TDOpen, TDRiskLevel, InverseFisherTransform, DecyclerOscillator, RoofingFilter, EmpiricalModeDecomposition, HilbertDominantCycle, AdaptiveCycle, SineWave, MAMA, FAMA, Ichimoku, HeikinAshi, ValueArea, InitialBalance, OpeningRange, Doji, Hammer, InvertedHammer, HangingMan, ShootingStar, Engulfing, Harami, MorningEveningStar, ThreeSoldiersOrCrows, PiercingDarkCloud, Marubozu, Tweezer, SpinningTop, ThreeInside, ThreeOutside, TwoCrows, UpsideGapTwoCrows, IdenticalThreeCrows, ThreeLineStrike, ThreeStarsInSouth, AbandonedBaby, AdvanceBlock, BeltHold, Breakaway, Counterattack, DojiStar, DragonflyDoji, GravestoneDoji, LongLeggedDoji, RickshawMan, EveningDojiStar, MorningDojiStar, GapSideBySideWhite, HighWave, Hikkake, HikkakeModified, HomingPigeon, OnNeck, InNeck, Thrusting, SeparatingLines, Kicking, KickingByLength, LadderBottom, MatHold, MatchingLow, LongLine, ShortLine, RisingThreeMethods, FallingThreeMethods, OrderBookImbalanceTop1, OrderBookImbalanceFull, Microprice, QuotedSpread, DepthSlope, OrderBookImbalanceTopN, SignedVolume, CumulativeVolumeDelta, TradeImbalance, EffectiveSpread, RealizedSpread, KylesLambda, Footprint, FundingRate, FundingRateMean, FundingRateZScore, FundingBasis, OpenInterestDelta, OIPriceDivergence, OIWeighted, LongShortRatio, TakerBuySellRatio, LiquidationFeatures, TermStructureBasis, CalendarSpread, SharpeRatio, SortinoRatio, CalmarRatio, OmegaRatio, MaxDrawdown, AverageDrawdown, DrawdownDuration, PainIndex, ValueAtRisk, ConditionalValueAtRisk, ProfitFactor, GainLossRatio, RecoveryFactor, KellyCriterion, TreynorRatio, InformationRatio, Alpha } = nativeBinding
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, McGinleyDynamic, FRAMA, SuperSmoother, FisherTransform, Decycler, CenterOfGravity, CyberneticCycle, InstantaneousTrendline, EhlersStochastic, RVIVolatility, Variance, CoefficientOfVariation, Skewness, Kurtosis, StandardError, DetrendedStdDev, RSquared, MedianAbsoluteDeviation, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration, RelativeStrengthAB, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, ADXR, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, Inertia, ConnorsRSI, LaguerreRSI, SMI, KST, PGO, RVI, AwesomeOscillatorHistogram, STC, ElderImpulse, ZeroLagMACD, CFO, APO, KAMA, EVWMA, Alligator, JMA, VIDYA, ALMA, T3, TSI, PMO, TII, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, NVI, PVI, VolumeOscillator, KVO, WilliamsAD, AnchoredVWAP, DemandIndex, TSV, VZO, MarketFacilitationIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, HiLoActivator, VoltyStop, YoyoExit, DonchianStop, PercentageTrailingStop, StepTrailingStop, RenkoTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, YangZhangVolatility, RogersSatchellVolatility, GarmanKlassVolatility, ParkinsonVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, WaveTrend, RWI, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA, MaEnvelope, AccelerationBands, StarcBands, AtrBands, HurstChannel, LinRegChannel, StandardErrorBands, DoubleBollinger, TtmSqueeze, FractalChaosBands, VwapStdDevBands, ClassicPivots, FibonacciPivots, Camarilla, WoodiePivots, DemarkPivots, WilliamsFractals, ZigZag, TDSetup, TDSequential, TDDeMarker, TDREI, TDPressure, TDCombo, TDCountdown, TDLines, TDRangeProjection, TDDifferential, TDOpen, TDRiskLevel, InverseFisherTransform, DecyclerOscillator, RoofingFilter, EmpiricalModeDecomposition, HilbertDominantCycle, AdaptiveCycle, SineWave, MAMA, FAMA, Ichimoku, HeikinAshi, ValueArea, InitialBalance, OpeningRange, Doji, Hammer, InvertedHammer, HangingMan, ShootingStar, Engulfing, Harami, MorningEveningStar, ThreeSoldiersOrCrows, PiercingDarkCloud, Marubozu, Tweezer, SpinningTop, ThreeInside, ThreeOutside, TwoCrows, UpsideGapTwoCrows, IdenticalThreeCrows, ThreeLineStrike, ThreeStarsInSouth, AbandonedBaby, AdvanceBlock, BeltHold, Breakaway, Counterattack, DojiStar, DragonflyDoji, GravestoneDoji, LongLeggedDoji, RickshawMan, EveningDojiStar, MorningDojiStar, GapSideBySideWhite, HighWave, Hikkake, HikkakeModified, HomingPigeon, OnNeck, InNeck, Thrusting, SeparatingLines, Kicking, KickingByLength, LadderBottom, MatHold, MatchingLow, LongLine, ShortLine, RisingThreeMethods, FallingThreeMethods, UpsideGapThreeMethods, DownsideGapThreeMethods, StalledPattern, StickSandwich, Takuri, 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
@@ -550,6 +550,11 @@ module.exports.LongLine = LongLine
module.exports.ShortLine = ShortLine
module.exports.RisingThreeMethods = RisingThreeMethods
module.exports.FallingThreeMethods = FallingThreeMethods
module.exports.UpsideGapThreeMethods = UpsideGapThreeMethods
module.exports.DownsideGapThreeMethods = DownsideGapThreeMethods
module.exports.StalledPattern = StalledPattern
module.exports.StickSandwich = StickSandwich
module.exports.Takuri = Takuri
module.exports.OrderBookImbalanceTop1 = OrderBookImbalanceTop1
module.exports.OrderBookImbalanceFull = OrderBookImbalanceFull
module.exports.Microprice = Microprice
+13
View File
@@ -8812,6 +8812,19 @@ node_candle_pattern!(
wc::FallingThreeMethods,
"FallingThreeMethods"
);
node_candle_pattern!(
UpsideGapThreeMethodsNode,
wc::UpsideGapThreeMethods,
"UpsideGapThreeMethods"
);
node_candle_pattern!(
DownsideGapThreeMethodsNode,
wc::DownsideGapThreeMethods,
"DownsideGapThreeMethods"
);
node_candle_pattern!(StalledPatternNode, wc::StalledPattern, "StalledPattern");
node_candle_pattern!(StickSandwichNode, wc::StickSandwich, "StickSandwich");
node_candle_pattern!(TakuriNode, wc::Takuri, "Takuri");
// ============================== Microstructure: Order Book ==============================
//
+10
View File
@@ -275,6 +275,11 @@ from ._wickra import (
ShortLine,
RisingThreeMethods,
FallingThreeMethods,
UpsideGapThreeMethods,
DownsideGapThreeMethods,
StalledPattern,
StickSandwich,
Takuri,
# Microstructure: order book
OrderBookImbalanceTop1,
OrderBookImbalanceTopN,
@@ -577,6 +582,11 @@ __all__ = [
"ShortLine",
"RisingThreeMethods",
"FallingThreeMethods",
"UpsideGapThreeMethods",
"DownsideGapThreeMethods",
"StalledPattern",
"StickSandwich",
"Takuri",
# Microstructure: order book
"OrderBookImbalanceTop1",
"OrderBookImbalanceTopN",
+18
View File
@@ -11675,6 +11675,19 @@ candle_pattern_no_param!(
wc::FallingThreeMethods,
"FallingThreeMethods"
);
candle_pattern_no_param!(
PyUpsideGapThreeMethods,
wc::UpsideGapThreeMethods,
"UpsideGapThreeMethods"
);
candle_pattern_no_param!(
PyDownsideGapThreeMethods,
wc::DownsideGapThreeMethods,
"DownsideGapThreeMethods"
);
candle_pattern_no_param!(PyStalledPattern, wc::StalledPattern, "StalledPattern");
candle_pattern_no_param!(PyStickSandwich, wc::StickSandwich, "StickSandwich");
candle_pattern_no_param!(PyTakuri, wc::Takuri, "Takuri");
// ============================== Microstructure: Order Book ==============================
//
// Order-book indicators consume a depth snapshot rather than OHLCV. Streaming
@@ -14206,6 +14219,11 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyShortLine>()?;
m.add_class::<PyRisingThreeMethods>()?;
m.add_class::<PyFallingThreeMethods>()?;
m.add_class::<PyUpsideGapThreeMethods>()?;
m.add_class::<PyDownsideGapThreeMethods>()?;
m.add_class::<PyStalledPattern>()?;
m.add_class::<PyStickSandwich>()?;
m.add_class::<PyTakuri>()?;
// Microstructure: order book.
m.add_class::<PyOrderBookImbalanceTop1>()?;
m.add_class::<PyOrderBookImbalanceTopN>()?;
@@ -663,6 +663,26 @@ CANDLE_SCALAR = {
lambda: ta.FallingThreeMethods(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"UpsideGapThreeMethods": (
lambda: ta.UpsideGapThreeMethods(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"DownsideGapThreeMethods": (
lambda: ta.DownsideGapThreeMethods(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"StalledPattern": (
lambda: ta.StalledPattern(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"StickSandwich": (
lambda: ta.StickSandwich(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"Takuri": (
lambda: ta.Takuri(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
}
@@ -1951,6 +1971,39 @@ def test_falling_three_methods_reference():
assert t.update((12.0, 13.1, 11.9, 13.0, 1.0, 3)) == pytest.approx(0.0)
assert t.update((12.5, 12.6, 8.9, 9.0, 1.0, 4)) == pytest.approx(-1.0)
def test_upside_gap_three_methods_reference():
t = ta.UpsideGapThreeMethods()
assert t.update((10.0, 11.2, 9.8, 11.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((12.0, 13.2, 11.9, 13.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((12.5, 12.6, 10.4, 10.5, 1.0, 2)) == pytest.approx(1.0)
def test_downside_gap_three_methods_reference():
t = ta.DownsideGapThreeMethods()
assert t.update((13.0, 13.2, 11.8, 12.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((11.0, 11.1, 9.8, 10.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((10.5, 12.6, 10.4, 12.5, 1.0, 2)) == pytest.approx(-1.0)
def test_stalled_pattern_reference():
t = ta.StalledPattern()
assert t.update((10.0, 12.05, 9.9, 12.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((11.0, 14.05, 10.9, 14.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((14.0, 14.6, 13.95, 14.15, 1.0, 2)) == pytest.approx(-1.0)
def test_stick_sandwich_reference():
t = ta.StickSandwich()
assert t.update((12.0, 12.1, 9.9, 10.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((10.5, 11.6, 10.4, 11.5, 1.0, 1)) == pytest.approx(0.0)
assert t.update((11.5, 11.6, 9.9, 10.0, 1.0, 2)) == pytest.approx(1.0)
def test_takuri_reference():
t = ta.Takuri()
assert t.update((10.0, 10.05, 7.0, 10.0, 1.0, 0)) == pytest.approx(1.0)
# --- Lifecycle ------------------------------------------------------------
+13
View File
@@ -6389,6 +6389,19 @@ wasm_candle_pattern!(
wc::FallingThreeMethods,
FallingThreeMethods
);
wasm_candle_pattern!(
WasmUpsideGapThreeMethods,
wc::UpsideGapThreeMethods,
UpsideGapThreeMethods
);
wasm_candle_pattern!(
WasmDownsideGapThreeMethods,
wc::DownsideGapThreeMethods,
DownsideGapThreeMethods
);
wasm_candle_pattern!(WasmStalledPattern, wc::StalledPattern, StalledPattern);
wasm_candle_pattern!(WasmStickSandwich, wc::StickSandwich, StickSandwich);
wasm_candle_pattern!(WasmTakuri, wc::Takuri, Takuri);
// ============================== Microstructure: Order Book ==============================
//
@@ -0,0 +1,210 @@
//! Downside Gap Three Methods candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Downside Gap Three Methods — a 3-bar bearish continuation. Two black candles
/// decline with a downside body gap between them, then a white candle opens inside
/// the second body and closes inside the first body, partially filling the gap
/// without erasing the prior decline.
///
/// ```text
/// bar1 black, bar2 black
/// downside body gap: open2 < close1 (bar2's body sits entirely below bar1's)
/// bar3 white, opens within bar2's body and closes within bar1's body
/// ```
///
/// Output is `1.0` when the pattern completes and `0.0` otherwise. Downside Gap
/// Three Methods is a single-direction (bearish-only) continuation, so it never
/// emits `+1.0`; its bullish mirror is [`crate::UpsideGapThreeMethods`]. 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, DownsideGapThreeMethods, Indicator};
///
/// let mut indicator = DownsideGapThreeMethods::new();
/// indicator.update(Candle::new(13.0, 13.2, 11.8, 12.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(11.0, 11.1, 9.8, 10.0, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(10.5, 12.6, 10.4, 12.5, 1.0, 2).unwrap());
/// assert_eq!(out, Some(-1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct DownsideGapThreeMethods {
c1: Option<Candle>,
c2: Option<Candle>,
has_emitted: bool,
}
impl DownsideGapThreeMethods {
/// Construct a new Downside Gap Three Methods detector.
pub const fn new() -> Self {
Self {
c1: None,
c2: None,
has_emitted: false,
}
}
}
impl Indicator for DownsideGapThreeMethods {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
self.has_emitted = true;
let bar1 = self.c1;
let bar2 = self.c2;
self.c1 = self.c2;
self.c2 = Some(candle);
let (Some(bar1), Some(bar2)) = (bar1, bar2) else {
return Some(0.0);
};
// bar1 and bar2 are both black.
if bar1.close >= bar1.open || bar2.close >= bar2.open {
return Some(0.0);
}
// Downside body gap: bar2's body sits entirely below bar1's.
if bar2.open >= bar1.close {
return Some(0.0);
}
// bar3 is white.
if candle.close <= candle.open {
return Some(0.0);
}
// bar3 opens within bar2's body and closes within bar1's body.
if candle.open > bar2.close
&& candle.open < bar2.open
&& candle.close > bar1.close
&& candle.close < bar1.open
{
return Some(-1.0);
}
Some(0.0)
}
fn reset(&mut self) {
self.c1 = None;
self.c2 = None;
self.has_emitted = false;
}
fn warmup_period(&self) -> usize {
3
}
fn is_ready(&self) -> bool {
self.has_emitted
}
fn name(&self) -> &'static str {
"DownsideGapThreeMethods"
}
}
#[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 = DownsideGapThreeMethods::new();
assert_eq!(t.name(), "DownsideGapThreeMethods");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
}
#[test]
fn downside_gap_three_methods_is_minus_one() {
let mut t = DownsideGapThreeMethods::new();
assert_eq!(t.update(c(13.0, 13.2, 11.8, 12.0, 0)), Some(0.0));
assert_eq!(t.update(c(11.0, 11.1, 9.8, 10.0, 1)), Some(0.0));
assert_eq!(t.update(c(10.5, 12.6, 10.4, 12.5, 2)), Some(-1.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = DownsideGapThreeMethods::new();
assert_eq!(t.update(c(13.0, 13.2, 11.8, 12.0, 0)), Some(0.0));
assert_eq!(t.update(c(11.0, 11.1, 9.8, 10.0, 1)), Some(0.0));
}
#[test]
fn non_black_first_bars_yield_zero() {
let mut t = DownsideGapThreeMethods::new();
// bar1 is white.
t.update(c(11.0, 13.2, 10.8, 13.0, 0));
t.update(c(11.0, 11.1, 9.8, 10.0, 1));
assert_eq!(t.update(c(10.5, 12.6, 10.4, 12.5, 2)), Some(0.0));
}
#[test]
fn no_gap_yields_zero() {
let mut t = DownsideGapThreeMethods::new();
t.update(c(13.0, 13.2, 11.8, 12.0, 0));
// bar2 opens above bar1's close -> no downside body gap.
t.update(c(12.5, 12.6, 11.4, 11.5, 1));
assert_eq!(t.update(c(11.5, 12.6, 11.4, 12.0, 2)), Some(0.0));
}
#[test]
fn third_bar_not_white_yields_zero() {
let mut t = DownsideGapThreeMethods::new();
t.update(c(13.0, 13.2, 11.8, 12.0, 0));
t.update(c(11.0, 11.1, 9.8, 10.0, 1));
// bar3 black.
assert_eq!(t.update(c(12.5, 12.6, 10.4, 10.5, 2)), Some(0.0));
}
#[test]
fn third_bar_outside_bodies_yields_zero() {
let mut t = DownsideGapThreeMethods::new();
t.update(c(13.0, 13.2, 11.8, 12.0, 0));
t.update(c(11.0, 11.1, 9.8, 10.0, 1));
// bar3 white but closes above bar1's body.
assert_eq!(t.update(c(10.5, 14.0, 10.4, 13.5, 2)), Some(0.0));
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = 200.0 - i as f64;
c(base, base + 0.1, base - 5.2, base - 5.0, i)
})
.collect();
let mut a = DownsideGapThreeMethods::new();
let mut b = DownsideGapThreeMethods::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = DownsideGapThreeMethods::new();
t.update(c(13.0, 13.2, 11.8, 12.0, 0));
t.update(c(11.0, 11.1, 9.8, 10.0, 1));
t.update(c(10.5, 12.6, 10.4, 12.5, 2));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(13.0, 13.2, 11.8, 12.0, 0)), Some(0.0));
}
}
+16 -1
View File
@@ -67,6 +67,7 @@ mod doji_star;
mod donchian;
mod donchian_stop;
mod double_bollinger;
mod downside_gap_three_methods;
mod dpo;
mod dragonfly_doji;
mod drawdown_duration;
@@ -210,18 +211,21 @@ mod smma;
mod sortino_ratio;
mod spearman_correlation;
mod spinning_top;
mod stalled_pattern;
mod standard_error;
mod standard_error_bands;
mod starc_bands;
mod stc;
mod std_dev;
mod step_trailing_stop;
mod stick_sandwich;
mod stoch_rsi;
mod stochastic;
mod super_smoother;
mod super_trend;
mod t3;
mod taker_buy_sell_ratio;
mod takuri;
mod td_combo;
mod td_countdown;
mod td_demarker;
@@ -256,6 +260,7 @@ mod two_crows;
mod typical_price;
mod ulcer_index;
mod ultimate_oscillator;
mod upside_gap_three_methods;
mod upside_gap_two_crows;
mod value_area;
mod value_at_risk;
@@ -346,6 +351,7 @@ pub use doji_star::DojiStar;
pub use donchian::{Donchian, DonchianOutput};
pub use donchian_stop::{DonchianStop, DonchianStopOutput};
pub use double_bollinger::{DoubleBollinger, DoubleBollingerOutput};
pub use downside_gap_three_methods::DownsideGapThreeMethods;
pub use dpo::Dpo;
pub use dragonfly_doji::DragonflyDoji;
pub use drawdown_duration::DrawdownDuration;
@@ -489,18 +495,21 @@ pub use smma::Smma;
pub use sortino_ratio::SortinoRatio;
pub use spearman_correlation::SpearmanCorrelation;
pub use spinning_top::SpinningTop;
pub use stalled_pattern::StalledPattern;
pub use standard_error::StandardError;
pub use standard_error_bands::{StandardErrorBands, StandardErrorBandsOutput};
pub use starc_bands::{StarcBands, StarcBandsOutput};
pub use stc::Stc;
pub use std_dev::StdDev;
pub use step_trailing_stop::StepTrailingStop;
pub use stick_sandwich::StickSandwich;
pub use stoch_rsi::StochRsi;
pub use stochastic::{Stochastic, StochasticOutput};
pub use super_smoother::SuperSmoother;
pub use super_trend::{SuperTrend, SuperTrendOutput};
pub use t3::T3;
pub use taker_buy_sell_ratio::TakerBuySellRatio;
pub use takuri::Takuri;
pub use td_combo::TdCombo;
pub use td_countdown::TdCountdown;
pub use td_demarker::TdDeMarker;
@@ -535,6 +544,7 @@ pub use two_crows::TwoCrows;
pub use typical_price::TypicalPrice;
pub use ulcer_index::UlcerIndex;
pub use ultimate_oscillator::UltimateOscillator;
pub use upside_gap_three_methods::UpsideGapThreeMethods;
pub use upside_gap_two_crows::UpsideGapTwoCrows;
pub use value_area::{ValueArea, ValueAreaOutput};
pub use value_at_risk::ValueAtRisk;
@@ -860,6 +870,11 @@ pub const FAMILIES: &[(&str, &[&str])] = &[
"ShortLine",
"RisingThreeMethods",
"FallingThreeMethods",
"UpsideGapThreeMethods",
"DownsideGapThreeMethods",
"StalledPattern",
"StickSandwich",
"Takuri",
],
),
(
@@ -951,6 +966,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, 274, "FAMILIES total drifted from indicator count");
assert_eq!(total, 279, "FAMILIES total drifted from indicator count");
}
}
@@ -0,0 +1,237 @@
//! Stalled Pattern (Deliberation) candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Stalled Pattern (also called Deliberation) — a 3-bar bearish reversal warning.
/// Two long white candles push higher, then a small-bodied white candle opens at
/// or near the top of the second body and barely advances — the rally is running
/// out of breath, hinting that buyers are losing control.
///
/// ```text
/// long body = |close open| >= 0.5 * (high low)
/// small body = |close open| <= 0.3 * (high low)
/// bar1, bar2 long white; bar3 small white
/// rising closes: close3 > close2 > close1
/// bar3 rides the shoulder: open3 >= close2 0.1 * (high2 low2)
/// ```
///
/// Output is `1.0` when the pattern completes and `0.0` otherwise. Stalled Pattern
/// is a single-direction (bearish-only) warning, so it never emits `+1.0`. The
/// first two bars always return `0.0` because the three-bar window is not yet
/// filled. Body thresholds follow the geometric house style rather than TA-Lib's
/// rolling averages. Pattern-shape check only — no trend filter is applied; combine
/// with a trend indicator for actionable signals.
///
/// # Signed ±1 encoding
///
/// This detector emits the uniform candlestick sign convention shared across the
/// pattern family — `1.0` bearish, `0.0` no pattern — so it drops straight into
/// a machine-learning feature matrix as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, StalledPattern};
///
/// let mut indicator = StalledPattern::new();
/// indicator.update(Candle::new(10.0, 12.05, 9.9, 12.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(11.0, 14.05, 10.9, 14.0, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(14.0, 14.6, 13.95, 14.15, 1.0, 2).unwrap());
/// assert_eq!(out, Some(-1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct StalledPattern {
c1: Option<Candle>,
c2: Option<Candle>,
has_emitted: bool,
}
impl StalledPattern {
/// Construct a new Stalled Pattern detector.
pub const fn new() -> Self {
Self {
c1: None,
c2: None,
has_emitted: false,
}
}
}
impl Indicator for StalledPattern {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
self.has_emitted = true;
let bar1 = self.c1;
let bar2 = self.c2;
self.c1 = self.c2;
self.c2 = 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;
let range3 = candle.high - candle.low;
if range1 <= 0.0 || range2 <= 0.0 || range3 <= 0.0 {
return Some(0.0);
}
// All three candles are white.
if bar1.close <= bar1.open || bar2.close <= bar2.open || candle.close <= candle.open {
return Some(0.0);
}
// Rising closes.
if candle.close <= bar2.close || bar2.close <= bar1.close {
return Some(0.0);
}
// bar1 and bar2 are long bodies.
if bar1.close - bar1.open < 0.5 * range1 || bar2.close - bar2.open < 0.5 * range2 {
return Some(0.0);
}
// bar3 is a small body.
if candle.close - candle.open > 0.3 * range3 {
return Some(0.0);
}
// bar3 opens at or near the top of bar2's body (rides the shoulder).
if candle.open >= bar2.close - 0.1 * range2 {
return Some(-1.0);
}
Some(0.0)
}
fn reset(&mut self) {
self.c1 = None;
self.c2 = None;
self.has_emitted = false;
}
fn warmup_period(&self) -> usize {
3
}
fn is_ready(&self) -> bool {
self.has_emitted
}
fn name(&self) -> &'static str {
"StalledPattern"
}
}
#[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 = StalledPattern::new();
assert_eq!(t.name(), "StalledPattern");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
}
#[test]
fn stalled_pattern_is_minus_one() {
let mut t = StalledPattern::new();
assert_eq!(t.update(c(10.0, 12.05, 9.9, 12.0, 0)), Some(0.0));
assert_eq!(t.update(c(11.0, 14.05, 10.9, 14.0, 1)), Some(0.0));
assert_eq!(t.update(c(14.0, 14.6, 13.95, 14.15, 2)), Some(-1.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = StalledPattern::new();
assert_eq!(t.update(c(10.0, 12.05, 9.9, 12.0, 0)), Some(0.0));
assert_eq!(t.update(c(11.0, 14.05, 10.9, 14.0, 1)), Some(0.0));
}
#[test]
fn zero_range_yields_zero() {
let mut t = StalledPattern::new();
t.update(c(10.0, 12.05, 9.9, 12.0, 0));
t.update(c(11.0, 14.05, 10.9, 14.0, 1));
// bar3 has zero range.
assert_eq!(t.update(c(14.0, 14.0, 14.0, 14.0, 2)), Some(0.0));
}
#[test]
fn non_white_yields_zero() {
let mut t = StalledPattern::new();
t.update(c(10.0, 12.05, 9.9, 12.0, 0));
t.update(c(11.0, 14.05, 10.9, 14.0, 1));
// bar3 is black.
assert_eq!(t.update(c(14.2, 14.6, 13.95, 14.05, 2)), Some(0.0));
}
#[test]
fn non_rising_closes_yield_zero() {
let mut t = StalledPattern::new();
t.update(c(10.0, 12.05, 9.9, 12.0, 0));
t.update(c(11.0, 14.05, 10.9, 14.0, 1));
// bar3 closes below bar2's close (white but not advancing).
assert_eq!(t.update(c(13.5, 14.0, 13.45, 13.6, 2)), Some(0.0));
}
#[test]
fn short_first_bodies_yield_zero() {
let mut t = StalledPattern::new();
// bar1 is white but its body is short relative to range.
t.update(c(11.5, 14.0, 10.0, 12.0, 0));
t.update(c(11.0, 14.05, 10.9, 14.0, 1));
assert_eq!(t.update(c(14.0, 14.6, 13.95, 14.15, 2)), Some(0.0));
}
#[test]
fn large_third_body_yields_zero() {
let mut t = StalledPattern::new();
t.update(c(10.0, 12.05, 9.9, 12.0, 0));
t.update(c(11.0, 14.05, 10.9, 14.0, 1));
// bar3 has a large body (not a small stalling candle).
assert_eq!(t.update(c(14.0, 16.05, 13.95, 16.0, 2)), Some(0.0));
}
#[test]
fn third_bar_off_shoulder_yields_zero() {
let mut t = StalledPattern::new();
t.update(c(10.0, 12.05, 9.9, 12.0, 0));
t.update(c(11.0, 14.05, 10.9, 14.0, 1));
// bar3 is a small white candle but opens well below bar2's close,
// so it is not riding the shoulder.
assert_eq!(t.update(c(13.6, 14.1, 12.55, 14.05, 2)), 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 + 2.05, base - 0.1, base + 2.0, i)
})
.collect();
let mut a = StalledPattern::new();
let mut b = StalledPattern::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = StalledPattern::new();
t.update(c(10.0, 12.05, 9.9, 12.0, 0));
t.update(c(11.0, 14.05, 10.9, 14.0, 1));
t.update(c(14.0, 14.6, 13.95, 14.15, 2));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(10.0, 12.05, 9.9, 12.0, 0)), Some(0.0));
}
}
@@ -0,0 +1,213 @@
//! Stick Sandwich candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Stick Sandwich — a 3-bar bullish reversal. A black candle is followed by a
/// white candle that trades entirely above the first close, then a second black
/// candle drives price back down to close at the same level as the first. The
/// matching closes "sandwich" the white candle and mark a support floor.
///
/// ```text
/// bar1 black, bar2 white, bar3 black
/// bar2 trades above bar1's close: low2 > close1
/// matching closes: |close3 close1| <= 0.1 * (high1 low1)
/// ```
///
/// Output is `+1.0` when the pattern completes and `0.0` otherwise. Stick Sandwich
/// 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. The matching-close tolerance follows the geometric house style (a fixed
/// fraction of the first bar's range) rather than TA-Lib's rolling averages.
/// Pattern-shape check only — no trend filter is applied; combine with a trend
/// indicator for actionable signals.
///
/// # Signed ±1 encoding
///
/// This detector emits the uniform candlestick sign convention shared across the
/// pattern family — `+1.0` bullish, `0.0` no pattern — so it drops straight into
/// a machine-learning feature matrix as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, StickSandwich};
///
/// let mut indicator = StickSandwich::new();
/// indicator.update(Candle::new(12.0, 12.1, 9.9, 10.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(10.5, 11.6, 10.4, 11.5, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(11.5, 11.6, 9.9, 10.0, 1.0, 2).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct StickSandwich {
c1: Option<Candle>,
c2: Option<Candle>,
has_emitted: bool,
}
impl StickSandwich {
/// Construct a new Stick Sandwich detector.
pub const fn new() -> Self {
Self {
c1: None,
c2: None,
has_emitted: false,
}
}
}
impl Indicator for StickSandwich {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
self.has_emitted = true;
let bar1 = self.c1;
let bar2 = self.c2;
self.c1 = self.c2;
self.c2 = Some(candle);
let (Some(bar1), Some(bar2)) = (bar1, bar2) else {
return Some(0.0);
};
// bar1 black, bar2 white, bar3 black.
if bar1.close >= bar1.open || bar2.close <= bar2.open || candle.close >= candle.open {
return Some(0.0);
}
// The white candle trades entirely above the first close.
if bar2.low <= bar1.close {
return Some(0.0);
}
// The two black candles close at the same level (the sandwich).
let range1 = bar1.high - bar1.low;
if (candle.close - bar1.close).abs() <= 0.1 * range1 {
return Some(1.0);
}
Some(0.0)
}
fn reset(&mut self) {
self.c1 = None;
self.c2 = None;
self.has_emitted = false;
}
fn warmup_period(&self) -> usize {
3
}
fn is_ready(&self) -> bool {
self.has_emitted
}
fn name(&self) -> &'static str {
"StickSandwich"
}
}
#[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 = StickSandwich::new();
assert_eq!(t.name(), "StickSandwich");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
}
#[test]
fn stick_sandwich_is_plus_one() {
let mut t = StickSandwich::new();
assert_eq!(t.update(c(12.0, 12.1, 9.9, 10.0, 0)), Some(0.0));
assert_eq!(t.update(c(10.5, 11.6, 10.4, 11.5, 1)), Some(0.0));
assert_eq!(t.update(c(11.5, 11.6, 9.9, 10.0, 2)), Some(1.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = StickSandwich::new();
assert_eq!(t.update(c(12.0, 12.1, 9.9, 10.0, 0)), Some(0.0));
assert_eq!(t.update(c(10.5, 11.6, 10.4, 11.5, 1)), Some(0.0));
}
#[test]
fn first_candle_not_black_yields_zero() {
let mut t = StickSandwich::new();
// bar1 white.
t.update(c(9.9, 12.1, 9.8, 10.0, 0));
t.update(c(10.5, 11.6, 10.4, 11.5, 1));
assert_eq!(t.update(c(11.5, 11.6, 9.9, 10.0, 2)), Some(0.0));
}
#[test]
fn middle_candle_not_white_yields_zero() {
let mut t = StickSandwich::new();
t.update(c(12.0, 12.1, 9.9, 10.0, 0));
// bar2 black.
t.update(c(11.5, 11.6, 10.4, 10.5, 1));
assert_eq!(t.update(c(11.5, 11.6, 9.9, 10.0, 2)), Some(0.0));
}
#[test]
fn third_candle_not_black_yields_zero() {
let mut t = StickSandwich::new();
t.update(c(12.0, 12.1, 9.9, 10.0, 0));
t.update(c(10.5, 11.6, 10.4, 11.5, 1));
// bar3 white.
assert_eq!(t.update(c(9.9, 11.6, 9.8, 10.0, 2)), Some(0.0));
}
#[test]
fn middle_low_not_above_first_close_yields_zero() {
let mut t = StickSandwich::new();
t.update(c(12.0, 12.1, 9.9, 10.0, 0));
// bar2 white but dips below bar1's close.
t.update(c(10.5, 11.6, 9.0, 11.5, 1));
assert_eq!(t.update(c(11.5, 11.6, 9.9, 10.0, 2)), Some(0.0));
}
#[test]
fn mismatched_closes_yield_zero() {
let mut t = StickSandwich::new();
t.update(c(12.0, 12.1, 9.9, 10.0, 0));
t.update(c(10.5, 11.6, 10.4, 11.5, 1));
// bar3 black but closes well away from bar1's close.
assert_eq!(t.update(c(11.5, 11.6, 7.9, 8.0, 2)), 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 + 2.0, base + 2.1, base - 0.1, base, i)
})
.collect();
let mut a = StickSandwich::new();
let mut b = StickSandwich::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = StickSandwich::new();
t.update(c(12.0, 12.1, 9.9, 10.0, 0));
t.update(c(10.5, 11.6, 10.4, 11.5, 1));
t.update(c(11.5, 11.6, 9.9, 10.0, 2));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(12.0, 12.1, 9.9, 10.0, 0)), Some(0.0));
}
}
+167
View File
@@ -0,0 +1,167 @@
//! Takuri candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Takuri — a single-bar bullish reversal, a stricter Dragonfly Doji. Open, close,
/// and high sit at the very top of the bar with a negligible upper shadow, while an
/// exceptionally long lower shadow shows price was driven sharply down and then bid
/// all the way back — an emphatic rejection of the lows.
///
/// ```text
/// range = high low
/// doji = |close open| <= 0.1 * range
/// negligible upper = high max(open, close) <= 0.05 * range
/// very long lower = min(open, close) low >= 0.7 * range
/// ```
///
/// Output is `+1.0` when the Takuri prints and `0.0` otherwise. Takuri is a
/// single-direction (bullish-only) shape, so it never emits `1.0`. Its tighter
/// upper-shadow and longer lower-shadow thresholds make it a strict subset of
/// [`crate::DragonflyDoji`]. Body and shadow thresholds follow the geometric house
/// style (fixed fractions of the bar range) rather than TA-Lib's rolling averages.
/// Pattern-shape check only — no trend filter is applied; combine with a trend
/// indicator for actionable signals.
///
/// # Signed ±1 encoding
///
/// This detector emits the uniform candlestick sign convention shared across the
/// pattern family — `+1.0` bullish, `0.0` no pattern — so it drops straight into
/// a machine-learning feature matrix as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, Takuri};
///
/// let mut indicator = Takuri::new();
/// // Body at the top, very long lower shadow.
/// let candle = Candle::new(10.0, 10.05, 7.0, 10.0, 1.0, 0).unwrap();
/// assert_eq!(indicator.update(candle), Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct Takuri {
has_emitted: bool,
}
impl Takuri {
/// Construct a new Takuri detector.
pub const fn new() -> Self {
Self { has_emitted: false }
}
}
impl Indicator for Takuri {
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);
}
if (candle.close - candle.open).abs() > 0.1 * range {
return Some(0.0);
}
let upper = candle.high - candle.open.max(candle.close);
let lower = candle.open.min(candle.close) - candle.low;
if upper <= 0.05 * range && lower >= 0.7 * 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 {
"Takuri"
}
}
#[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 = Takuri::new();
assert_eq!(t.name(), "Takuri");
assert_eq!(t.warmup_period(), 1);
assert!(!t.is_ready());
}
#[test]
fn takuri_is_plus_one() {
let mut t = Takuri::new();
assert_eq!(t.update(c(10.0, 10.05, 7.0, 10.0, 0)), Some(1.0));
}
#[test]
fn non_doji_body_yields_zero() {
let mut t = Takuri::new();
// Large body -> not a doji.
assert_eq!(t.update(c(10.0, 12.0, 7.0, 11.5, 0)), Some(0.0));
}
#[test]
fn upper_shadow_yields_zero() {
let mut t = Takuri::new();
// Long upper shadow -> not a Takuri.
assert_eq!(t.update(c(10.0, 14.0, 7.0, 10.0, 0)), Some(0.0));
}
#[test]
fn dragonfly_but_not_takuri_yields_zero() {
let mut t = Takuri::new();
// Upper shadow ~0.07 of range: a Dragonfly Doji, but exceeds Takuri's
// tighter 0.05 ceiling.
assert_eq!(t.update(c(10.0, 10.24, 7.0, 10.0, 0)), Some(0.0));
}
#[test]
fn zero_range_yields_zero() {
let mut t = Takuri::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 + 0.02, base - 4.0, base, i)
})
.collect();
let mut a = Takuri::new();
let mut b = Takuri::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = Takuri::new();
t.update(c(10.0, 10.05, 7.0, 10.0, 0));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
}
}
@@ -0,0 +1,210 @@
//! Upside Gap Three Methods candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Upside Gap Three Methods — a 3-bar bullish continuation. Two white candles
/// advance with an upside body gap between them, then a black candle opens inside
/// the second body and closes inside the first body, partially filling the gap
/// without erasing the prior advance.
///
/// ```text
/// bar1 white, bar2 white
/// upside body gap: open2 > close1 (bar2's body sits entirely above bar1's)
/// bar3 black, opens within bar2's body and closes within bar1's body
/// ```
///
/// Output is `+1.0` when the pattern completes and `0.0` otherwise. Upside Gap
/// Three Methods is a single-direction (bullish-only) continuation, so it never
/// emits `1.0`; its bearish mirror is [`crate::DownsideGapThreeMethods`]. The
/// first two bars always return `0.0` because the three-bar window is not yet
/// filled. Pattern-shape check only — no trend filter is applied; combine with a
/// trend indicator for actionable signals.
///
/// # Signed ±1 encoding
///
/// This detector emits the uniform candlestick sign convention shared across the
/// pattern family — `+1.0` bullish, `0.0` no pattern — so it drops straight into
/// a machine-learning feature matrix as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, UpsideGapThreeMethods};
///
/// let mut indicator = UpsideGapThreeMethods::new();
/// indicator.update(Candle::new(10.0, 11.2, 9.8, 11.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(12.0, 13.2, 11.9, 13.0, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(12.5, 12.6, 10.4, 10.5, 1.0, 2).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct UpsideGapThreeMethods {
c1: Option<Candle>,
c2: Option<Candle>,
has_emitted: bool,
}
impl UpsideGapThreeMethods {
/// Construct a new Upside Gap Three Methods detector.
pub const fn new() -> Self {
Self {
c1: None,
c2: None,
has_emitted: false,
}
}
}
impl Indicator for UpsideGapThreeMethods {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
self.has_emitted = true;
let bar1 = self.c1;
let bar2 = self.c2;
self.c1 = self.c2;
self.c2 = Some(candle);
let (Some(bar1), Some(bar2)) = (bar1, bar2) else {
return Some(0.0);
};
// bar1 and bar2 are both white.
if bar1.close <= bar1.open || bar2.close <= bar2.open {
return Some(0.0);
}
// Upside body gap: bar2's body sits entirely above bar1's.
if bar2.open <= bar1.close {
return Some(0.0);
}
// bar3 is black.
if candle.close >= candle.open {
return Some(0.0);
}
// bar3 opens within bar2's body and closes within bar1's body.
if 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.c1 = None;
self.c2 = None;
self.has_emitted = false;
}
fn warmup_period(&self) -> usize {
3
}
fn is_ready(&self) -> bool {
self.has_emitted
}
fn name(&self) -> &'static str {
"UpsideGapThreeMethods"
}
}
#[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 = UpsideGapThreeMethods::new();
assert_eq!(t.name(), "UpsideGapThreeMethods");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
}
#[test]
fn upside_gap_three_methods_is_plus_one() {
let mut t = UpsideGapThreeMethods::new();
assert_eq!(t.update(c(10.0, 11.2, 9.8, 11.0, 0)), Some(0.0));
assert_eq!(t.update(c(12.0, 13.2, 11.9, 13.0, 1)), Some(0.0));
assert_eq!(t.update(c(12.5, 12.6, 10.4, 10.5, 2)), Some(1.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = UpsideGapThreeMethods::new();
assert_eq!(t.update(c(10.0, 11.2, 9.8, 11.0, 0)), Some(0.0));
assert_eq!(t.update(c(12.0, 13.2, 11.9, 13.0, 1)), Some(0.0));
}
#[test]
fn non_white_first_bars_yield_zero() {
let mut t = UpsideGapThreeMethods::new();
// bar1 is black.
t.update(c(11.0, 11.2, 9.8, 10.0, 0));
t.update(c(12.0, 13.2, 11.9, 13.0, 1));
assert_eq!(t.update(c(12.5, 12.6, 10.4, 10.5, 2)), Some(0.0));
}
#[test]
fn no_gap_yields_zero() {
let mut t = UpsideGapThreeMethods::new();
t.update(c(10.0, 13.2, 9.8, 13.0, 0));
// bar2 opens below bar1's close -> no upside body gap.
t.update(c(11.0, 13.2, 10.9, 12.5, 1));
assert_eq!(t.update(c(12.0, 12.6, 10.4, 10.5, 2)), Some(0.0));
}
#[test]
fn third_bar_not_black_yields_zero() {
let mut t = UpsideGapThreeMethods::new();
t.update(c(10.0, 11.2, 9.8, 11.0, 0));
t.update(c(12.0, 13.2, 11.9, 13.0, 1));
// bar3 white.
assert_eq!(t.update(c(10.5, 12.6, 10.4, 12.5, 2)), Some(0.0));
}
#[test]
fn third_bar_outside_bodies_yields_zero() {
let mut t = UpsideGapThreeMethods::new();
t.update(c(10.0, 11.2, 9.8, 11.0, 0));
t.update(c(12.0, 13.2, 11.9, 13.0, 1));
// bar3 black but closes below bar1's body.
assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 2)), 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 = UpsideGapThreeMethods::new();
let mut b = UpsideGapThreeMethods::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = UpsideGapThreeMethods::new();
t.update(c(10.0, 11.2, 9.8, 11.0, 0));
t.update(c(12.0, 13.2, 11.9, 13.0, 1));
t.update(c(12.5, 12.6, 10.4, 10.5, 2));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(10.0, 11.2, 9.8, 11.0, 0)), Some(0.0));
}
}
+29 -27
View File
@@ -65,19 +65,20 @@ pub use indicators::{
ConditionalValueAtRisk, ConnorsRsi, Coppock, Counterattack, CumulativeVolumeDelta,
CyberneticCycle, Decycler, DecyclerOscillator, Dema, DemandIndex, DemarkPivots,
DemarkPivotsOutput, DepthSlope, DetrendedStdDev, Doji, DojiStar, Donchian, DonchianOutput,
DonchianStop, DonchianStopOutput, DoubleBollinger, DoubleBollingerOutput, Dpo, DragonflyDoji,
DrawdownDuration, EaseOfMovement, EffectiveSpread, EhlersStochastic, ElderImpulse, Ema,
EmpiricalModeDecomposition, Engulfing, EveningDojiStar, Evwma, FallingThreeMethods, 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, HikkakeModified, HilbertDominantCycle, HistoricalVolatility, Hma,
HomingPigeon, HurstChannel, HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput,
IdenticalThreeCrows, InNeck, Inertia, InformationRatio, InitialBalance, InitialBalanceOutput,
InstantaneousTrendline, InverseFisherTransform, InvertedHammer, Jma, Kama, KellyCriterion,
Keltner, KeltnerOutput, Kicking, KickingByLength, Kst, KstOutput, Kurtosis, Kvo, KylesLambda,
LadderBottom, LaguerreRsi, LeadLagCrossCorrelation, LeadLagCrossCorrelationOutput, LinRegAngle,
DonchianStop, DonchianStopOutput, DoubleBollinger, DoubleBollingerOutput,
DownsideGapThreeMethods, Dpo, DragonflyDoji, DrawdownDuration, EaseOfMovement, EffectiveSpread,
EhlersStochastic, ElderImpulse, Ema, EmpiricalModeDecomposition, Engulfing, EveningDojiStar,
Evwma, FallingThreeMethods, 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, HikkakeModified,
HilbertDominantCycle, HistoricalVolatility, Hma, HomingPigeon, HurstChannel,
HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput, IdenticalThreeCrows, InNeck,
Inertia, InformationRatio, InitialBalance, InitialBalanceOutput, InstantaneousTrendline,
InverseFisherTransform, InvertedHammer, Jma, Kama, KellyCriterion, Keltner, KeltnerOutput,
Kicking, KickingByLength, Kst, KstOutput, Kurtosis, Kvo, KylesLambda, LadderBottom,
LaguerreRsi, LeadLagCrossCorrelation, LeadLagCrossCorrelationOutput, LinRegAngle,
LinRegChannel, LinRegChannelOutput, LinRegSlope, LinearRegression, LiquidationFeatures,
LiquidationFeaturesOutput, LongLeggedDoji, LongLine, LongShortRatio, MaEnvelope,
MaEnvelopeOutput, MacdIndicator, MacdOutput, Mama, MamaOutput, MarketFacilitationIndex,
@@ -92,20 +93,21 @@ pub use indicators::{
RisingThreeMethods, Roc, RogersSatchellVolatility, RollingVwap, RoofingFilter, Rsi, Rvi,
RviVolatility, Rwi, RwiOutput, SeparatingLines, SharpeRatio, ShootingStar, ShortLine,
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, Thrusting, Tii, TradeImbalance,
TreynorRatio, Trima, Trix, TrueRange, Tsi, Tsv, TtmSqueeze, TtmSqueezeOutput, Tweezer,
TwoCrows, TypicalPrice, UlcerIndex, UltimateOscillator, UpsideGapTwoCrows, ValueArea,
ValueAreaOutput, ValueAtRisk, Variance, VerticalHorizontalFilter, Vidya, VoltyStop,
VolumeOscillator, VolumePriceTrend, Vortex, VortexOutput, Vwap, VwapStdDevBands,
VwapStdDevBandsOutput, Vwma, Vzo, WaveTrend, WaveTrendOutput, WeightedClose, WilliamsFractals,
WilliamsFractalsOutput, WilliamsR, Wma, WoodiePivots, WoodiePivotsOutput, YangZhangVolatility,
YoyoExit, ZScore, ZeroLagMacd, ZeroLagMacdOutput, ZigZag, ZigZagOutput, Zlema, FAMILIES, T3,
SpinningTop, StalledPattern, StandardError, StandardErrorBands, StandardErrorBandsOutput,
StarcBands, StarcBandsOutput, Stc, StdDev, StepTrailingStop, StickSandwich, StochRsi,
Stochastic, StochasticOutput, SuperSmoother, SuperTrend, SuperTrendOutput, TakerBuySellRatio,
Takuri, TdCombo, TdCountdown, TdDeMarker, TdDifferential, TdLines, TdLinesOutput, TdOpen,
TdPressure, TdRangeProjection, TdRangeProjectionOutput, TdRei, TdRiskLevel, TdRiskLevelOutput,
TdSequential, TdSequentialOutput, TdSetup, Tema, TermStructureBasis, ThreeInside,
ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, Thrusting, Tii,
TradeImbalance, TreynorRatio, Trima, Trix, TrueRange, Tsi, Tsv, TtmSqueeze, TtmSqueezeOutput,
Tweezer, TwoCrows, TypicalPrice, UlcerIndex, UltimateOscillator, UpsideGapThreeMethods,
UpsideGapTwoCrows, ValueArea, ValueAreaOutput, ValueAtRisk, Variance, VerticalHorizontalFilter,
Vidya, VoltyStop, VolumeOscillator, VolumePriceTrend, Vortex, VortexOutput, Vwap,
VwapStdDevBands, VwapStdDevBandsOutput, Vwma, Vzo, WaveTrend, WaveTrendOutput, WeightedClose,
WilliamsFractals, WilliamsFractalsOutput, WilliamsR, Wma, WoodiePivots, WoodiePivotsOutput,
YangZhangVolatility, YoyoExit, ZScore, ZeroLagMacd, ZeroLagMacdOutput, ZigZag, ZigZagOutput,
Zlema, FAMILIES, T3,
};
// `FootprintLevel` is a row element of `FootprintOutput`, re-exported on its own
// line so the indicator-count tooling (which scans the braced block above and
+1 -1
View File
@@ -8,7 +8,7 @@ That includes:
[Python](https://docs.wickra.org/Quickstart-Python),
[Node](https://docs.wickra.org/Quickstart-Node), and
[WASM](https://docs.wickra.org/Quickstart-WASM).
- A per-indicator deep dive for every one of the **279 indicators** across
- A per-indicator deep dive for every one of the **284 indicators** across
the sixteen families (Moving Averages, Momentum Oscillators, Trend &
Directional, Price Oscillators, Volatility & Bands, Bands & Channels,
Trailing Stops, Volume, Price Statistics, Ehlers / Cycle DSP, Pivots &
+6 -1
View File
@@ -23,7 +23,7 @@
use libfuzzer_sys::fuzz_target;
use wickra_core::{
AbandonedBaby, AccelerationBands, AcceleratorOscillator, AdOscillator, Adl, AdvanceBlock, Adx, Adxr, Alligator, AnchoredVwap, Aroon, AroonOscillator, Atr, AtrBands, AtrTrailingStop, AwesomeOscillator, AwesomeOscillatorHistogram, BalanceOfPower, BatchExt, BeltHold, Breakaway, Camarilla, Candle, Cci, ChaikinMoneyFlow, ChaikinOscillator, ChaikinVolatility, ChandeKrollStop, ChandelierExit, ChoppinessIndex, ClassicPivots, Counterattack, DemandIndex, DemarkPivots, Doji, DojiStar, Donchian, DonchianStop, DragonflyDoji, EaseOfMovement, Engulfing, EveningDojiStar, Evwma, FallingThreeMethods, FibonacciPivots, ForceIndex, FractalChaosBands, GapSideBySideWhite, GarmanKlassVolatility, GravestoneDoji, Hammer, HangingMan, Harami, HeikinAshi, HiLoActivator, HighWave, Hikkake, HikkakeModified, HomingPigeon, HurstChannel, Ichimoku, IdenticalThreeCrows, InNeck, Indicator, Inertia, InitialBalance, InvertedHammer, Keltner, Kicking, KickingByLength, Kvo, LadderBottom, LongLeggedDoji, LongLine, MarketFacilitationIndex, Marubozu, MassIndex, MatHold, MatchingLow, MedianPrice, Mfi, MorningDojiStar, MorningEveningStar, Natr, Nvi, Obv, OnNeck, OpeningRange, ParkinsonVolatility, Pgo, PiercingDarkCloud, Psar, Pvi, RickshawMan, RisingThreeMethods, RogersSatchellVolatility, RollingVwap, Rvi, Rwi, SeparatingLines, ShootingStar, ShortLine, Smi, SpinningTop, StarcBands, Stochastic, SuperTrend, TdCombo, TdCountdown, TdDeMarker, TdDifferential, TdLines, TdOpen, TdPressure, TdRangeProjection, TdRei, TdRiskLevel, TdSequential, TdSetup, ThreeInside, ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, Thrusting, TrueRange, Tsv, TtmSqueeze, Tweezer, TwoCrows, TypicalPrice, UltimateOscillator, UpsideGapTwoCrows, ValueArea, VoltyStop, VolumeOscillator, VolumePriceTrend, Vortex, Vwap, VwapStdDevBands, Vwma, Vzo, WaveTrend, WeightedClose, WilliamsFractals, WilliamsR, WoodiePivots, YangZhangVolatility, YoyoExit, ZigZag
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, DownsideGapThreeMethods, DragonflyDoji, EaseOfMovement, Engulfing, EveningDojiStar, Evwma, FallingThreeMethods, FibonacciPivots, ForceIndex, FractalChaosBands, GapSideBySideWhite, GarmanKlassVolatility, GravestoneDoji, Hammer, HangingMan, Harami, HeikinAshi, HiLoActivator, HighWave, Hikkake, HikkakeModified, HomingPigeon, HurstChannel, Ichimoku, IdenticalThreeCrows, InNeck, Indicator, Inertia, InitialBalance, InvertedHammer, Keltner, Kicking, KickingByLength, Kvo, LadderBottom, LongLeggedDoji, LongLine, MarketFacilitationIndex, Marubozu, MassIndex, MatHold, MatchingLow, MedianPrice, Mfi, MorningDojiStar, MorningEveningStar, Natr, Nvi, Obv, OnNeck, OpeningRange, ParkinsonVolatility, Pgo, PiercingDarkCloud, Psar, Pvi, RickshawMan, RisingThreeMethods, RogersSatchellVolatility, RollingVwap, Rvi, Rwi, SeparatingLines, ShootingStar, ShortLine, Smi, SpinningTop, StalledPattern, StarcBands, StickSandwich, Stochastic, SuperTrend, Takuri, TdCombo, TdCountdown, TdDeMarker, TdDifferential, TdLines, TdOpen, TdPressure, TdRangeProjection, TdRei, TdRiskLevel, TdSequential, TdSetup, ThreeInside, ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, Thrusting, TrueRange, Tsv, TtmSqueeze, Tweezer, TwoCrows, TypicalPrice, UltimateOscillator, UpsideGapThreeMethods, 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(Takuri::new, &candles);
drive(StickSandwich::new, &candles);
drive(StalledPattern::new, &candles);
drive(DownsideGapThreeMethods::new, &candles);
drive(UpsideGapThreeMethods::new, &candles);
drive(FallingThreeMethods::new, &candles);
drive(RisingThreeMethods::new, &candles);
drive(ShortLine::new, &candles);