feat: TA-Lib candlestick patterns — abandoned/advance/belt/break/counter (part 2 of 9) (#132)

* feat: add Abandoned Baby candlestick pattern (CDLABANDONEDBABY)

* feat: add Advance Block candlestick pattern (CDLADVANCEBLOCK)

* feat: add Belt Hold candlestick pattern (CDLBELTHOLD)

* feat: add Breakaway and Counterattack candlestick patterns (CDLBREAKAWAY, CDLCOUNTERATTACK)

Breakaway is a 5-bar reversal: a trend gaps away on the second bar, drifts
two more bars, then the fifth bar snaps back and closes inside the bar1/bar2
body gap (bullish +1, bearish -1). Counterattack is a 2-bar reversal where an
opposite-coloured long second bar closes level with the first (the counterattack
line; bullish +1, bearish -1).

Also suppress libtest's spanless `large_stack_arrays` false positive in
wickra-core test builds: the `#[test]` harness collects every test into a
compiler-generated array of references that crosses clippy's 16 KB threshold
once the suite passes ~2048 unit tests. The allow is scoped to `cfg(test)`, so
library code is still linted for genuinely large stack arrays.

* chore: sync indicator count to 254

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
This commit is contained in:
kingchenc
2026-06-02 16:34:15 +02:00
committed by GitHub
co-authored by wickra-bot
parent 73415cd2dc
commit 03ceac1f3b
19 changed files with 1343 additions and 41 deletions
+9
View File
@@ -28,6 +28,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Three Stars in the South** — a rare three-bar bullish reversal - **Three Stars in the South** — a rare three-bar bullish reversal
(`CDL3STARSINSOUTH`): three shrinking red candles each carving a higher low (`CDL3STARSINSOUTH`): three shrinking red candles each carving a higher low
and contracting toward a tiny black marubozu as selling exhausts. and contracting toward a tiny black marubozu as selling exhausts.
- **Abandoned Baby** — a strong three-bar reversal (`CDLABANDONEDBABY`): a doji
isolated by price gaps on both sides; bullish `+1` after a decline, bearish
`1` after an advance.
- **Advance Block** — a three-bar bearish warning (`CDLADVANCEBLOCK`): three
green candles to higher closes whose bodies shrink as their upper shadows
lengthen, signalling the advance is stalling.
- **Belt-hold** — a single-bar reversal that opens at one extreme of its range and runs the other way; bullish +1, bearish -1 (`CDLBELTHOLD`).
- **Breakaway** — a 5-bar reversal that gaps with the trend, drifts two more bars, then snaps back into the bar1/bar2 body gap; bullish +1, bearish -1 (`CDLBREAKAWAY`).
- **Counterattack** — a 2-bar reversal where an opposite-coloured second bar closes level with the first (the counterattack line); bullish +1, bearish -1 (`CDLCOUNTERATTACK`).
- **Derivatives family — funding & open interest (part 1).** A new family of - **Derivatives family — funding & open interest (part 1).** A new family of
indicators that consume a perpetual / futures tick (`DerivativesTick`, indicators that consume a perpetual / futures tick (`DerivativesTick`,
bundling funding rate, mark / index / futures price, open interest, bundling funding rate, mark / index / futures price, open interest,
+5 -5
View File
@@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=249" 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=254" alt="Wickra — streaming-first technical indicators" width="100%"></a>
</p> </p>
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml) [![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), [Node](https://docs.wickra.org/Quickstart-Node),
[WASM](https://docs.wickra.org/Quickstart-WASM). [WASM](https://docs.wickra.org/Quickstart-WASM).
- **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for - **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for
every one of the 249 indicators; start at the every one of the 254 indicators; start at the
[indicators overview](https://docs.wickra.org/Indicators-Overview). [indicators overview](https://docs.wickra.org/Indicators-Overview).
- **Reference** — [warmup periods](https://docs.wickra.org/Warmup-Periods), - **Reference** — [warmup periods](https://docs.wickra.org/Warmup-Periods),
[streaming vs batch](https://docs.wickra.org/Streaming-vs-Batch), [streaming vs batch](https://docs.wickra.org/Streaming-vs-Batch),
@@ -135,7 +135,7 @@ python -m benchmarks.compare_libraries
## Indicators ## Indicators
249 streaming-first indicators across eighteen families. Every one passes the 254 streaming-first indicators across eighteen families. Every one passes the
`batch == streaming` equivalence test, reference-value tests, and reset `batch == streaming` equivalence test, reference-value tests, and reset
semantics tests. Each has a per-indicator deep dive (formula, parameters, semantics tests. Each has a per-indicator deep dive (formula, parameters,
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview). 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 | | 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 | | 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 | | 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 | | 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 |
| 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 | | 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 | | 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 | | 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/ wickra/
├── crates/ ├── crates/
│ ├── wickra-core/ core engine + all 249 indicators │ ├── wickra-core/ core engine + all 254 indicators
│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/ │ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/
│ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds │ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds
├── bindings/ ├── bindings/
@@ -240,6 +240,11 @@ const candleScalar = {
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) }, 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) }, 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) }, 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) },
AbandonedBaby: { make: () => new wickra.AbandonedBaby(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
AdvanceBlock: { make: () => new wickra.AdvanceBlock(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
BeltHold: { make: () => new wickra.BeltHold(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
Breakaway: { make: () => new wickra.Breakaway(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
Counterattack: { make: () => new wickra.Counterattack(), 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)) { for (const [name, d] of Object.entries(candleScalar)) {
+45
View File
@@ -2255,6 +2255,51 @@ export declare class ThreeStarsInSouth {
isReady(): boolean isReady(): boolean
warmupPeriod(): number warmupPeriod(): number
} }
export type AbandonedBabyNode = AbandonedBaby
export declare class AbandonedBaby {
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 AdvanceBlockNode = AdvanceBlock
export declare class AdvanceBlock {
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 BeltHoldNode = BeltHold
export declare class BeltHold {
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 BreakawayNode = Breakaway
export declare class Breakaway {
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 CounterattackNode = Counterattack
export declare class Counterattack {
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 type OrderBookImbalanceTop1Node = OrderBookImbalanceTop1
export declare class OrderBookImbalanceTop1 { export declare class OrderBookImbalanceTop1 {
constructor() constructor()
+6 -1
View File
@@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`) 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, 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, 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.version = version
module.exports.SMA = SMA module.exports.SMA = SMA
@@ -520,6 +520,11 @@ module.exports.UpsideGapTwoCrows = UpsideGapTwoCrows
module.exports.IdenticalThreeCrows = IdenticalThreeCrows module.exports.IdenticalThreeCrows = IdenticalThreeCrows
module.exports.ThreeLineStrike = ThreeLineStrike module.exports.ThreeLineStrike = ThreeLineStrike
module.exports.ThreeStarsInSouth = ThreeStarsInSouth module.exports.ThreeStarsInSouth = ThreeStarsInSouth
module.exports.AbandonedBaby = AbandonedBaby
module.exports.AdvanceBlock = AdvanceBlock
module.exports.BeltHold = BeltHold
module.exports.Breakaway = Breakaway
module.exports.Counterattack = Counterattack
module.exports.OrderBookImbalanceTop1 = OrderBookImbalanceTop1 module.exports.OrderBookImbalanceTop1 = OrderBookImbalanceTop1
module.exports.OrderBookImbalanceFull = OrderBookImbalanceFull module.exports.OrderBookImbalanceFull = OrderBookImbalanceFull
module.exports.Microprice = Microprice module.exports.Microprice = Microprice
+5
View File
@@ -8770,6 +8770,11 @@ node_candle_pattern!(
wc::ThreeStarsInSouth, wc::ThreeStarsInSouth,
"ThreeStarsInSouth" "ThreeStarsInSouth"
); );
node_candle_pattern!(AbandonedBabyNode, wc::AbandonedBaby, "AbandonedBaby");
node_candle_pattern!(AdvanceBlockNode, wc::AdvanceBlock, "AdvanceBlock");
node_candle_pattern!(BeltHoldNode, wc::BeltHold, "BeltHold");
node_candle_pattern!(BreakawayNode, wc::Breakaway, "Breakaway");
node_candle_pattern!(CounterattackNode, wc::Counterattack, "Counterattack");
// ============================== Microstructure: Order Book ============================== // ============================== Microstructure: Order Book ==============================
// //
+10
View File
@@ -245,6 +245,11 @@ from ._wickra import (
IdenticalThreeCrows, IdenticalThreeCrows,
ThreeLineStrike, ThreeLineStrike,
ThreeStarsInSouth, ThreeStarsInSouth,
AbandonedBaby,
AdvanceBlock,
BeltHold,
Breakaway,
Counterattack,
# Microstructure: order book # Microstructure: order book
OrderBookImbalanceTop1, OrderBookImbalanceTop1,
OrderBookImbalanceTopN, OrderBookImbalanceTopN,
@@ -517,6 +522,11 @@ __all__ = [
"IdenticalThreeCrows", "IdenticalThreeCrows",
"ThreeLineStrike", "ThreeLineStrike",
"ThreeStarsInSouth", "ThreeStarsInSouth",
"AbandonedBaby",
"AdvanceBlock",
"BeltHold",
"Breakaway",
"Counterattack",
# Microstructure: order book # Microstructure: order book
"OrderBookImbalanceTop1", "OrderBookImbalanceTop1",
"OrderBookImbalanceTopN", "OrderBookImbalanceTopN",
+10
View File
@@ -11632,7 +11632,12 @@ candle_pattern_no_param!(
wc::ThreeStarsInSouth, wc::ThreeStarsInSouth,
"ThreeStarsInSouth" "ThreeStarsInSouth"
); );
candle_pattern_no_param!(PyAbandonedBaby, wc::AbandonedBaby, "AbandonedBaby");
candle_pattern_no_param!(PyAdvanceBlock, wc::AdvanceBlock, "AdvanceBlock");
candle_pattern_no_param!(PyBeltHold, wc::BeltHold, "BeltHold");
candle_pattern_no_param!(PyBreakaway, wc::Breakaway, "Breakaway");
candle_pattern_no_param!(PyCounterattack, wc::Counterattack, "Counterattack");
// ============================== Microstructure: Order Book ============================== // ============================== Microstructure: Order Book ==============================
// //
// Order-book indicators consume a depth snapshot rather than OHLCV. Streaming // Order-book indicators consume a depth snapshot rather than OHLCV. Streaming
@@ -14134,6 +14139,11 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyIdenticalThreeCrows>()?; m.add_class::<PyIdenticalThreeCrows>()?;
m.add_class::<PyThreeLineStrike>()?; m.add_class::<PyThreeLineStrike>()?;
m.add_class::<PyThreeStarsInSouth>()?; m.add_class::<PyThreeStarsInSouth>()?;
m.add_class::<PyAbandonedBaby>()?;
m.add_class::<PyAdvanceBlock>()?;
m.add_class::<PyBeltHold>()?;
m.add_class::<PyBreakaway>()?;
m.add_class::<PyCounterattack>()?;
// Microstructure: order book. // Microstructure: order book.
m.add_class::<PyOrderBookImbalanceTop1>()?; m.add_class::<PyOrderBookImbalanceTop1>()?;
m.add_class::<PyOrderBookImbalanceTopN>()?; m.add_class::<PyOrderBookImbalanceTopN>()?;
@@ -543,6 +543,26 @@ CANDLE_SCALAR = {
lambda: ta.ThreeStarsInSouth(), lambda: ta.ThreeStarsInSouth(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c), lambda ind, h, l, c, v: ind.batch(c, h, l, c),
), ),
"AbandonedBaby": (
lambda: ta.AbandonedBaby(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"AdvanceBlock": (
lambda: ta.AdvanceBlock(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"BeltHold": (
lambda: ta.BeltHold(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"Breakaway": (
lambda: ta.Breakaway(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"Counterattack": (
lambda: ta.Counterattack(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
} }
@@ -1644,6 +1664,27 @@ def test_fractal_chaos_bands_detects_peak_and_trough():
assert out[5, 1] == pytest.approx(0.5) assert out[5, 1] == pytest.approx(0.5)
def test_belt_hold_reference():
t = ta.BeltHold()
assert t.update((10.0, 12.0, 10.0, 11.5, 1.0, 0)) == pytest.approx(1.0)
assert t.update((12.0, 12.0, 10.0, 10.5, 1.0, 1)) == pytest.approx(-1.0)
def test_breakaway_reference():
t = ta.Breakaway()
assert t.update((20.0, 20.2, 14.8, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((14.0, 14.1, 11.9, 12.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((12.5, 13.0, 10.5, 11.0, 1.0, 2)) == pytest.approx(0.0)
assert t.update((11.0, 11.5, 9.0, 9.5, 1.0, 3)) == pytest.approx(0.0)
assert t.update((9.5, 14.7, 9.4, 14.5, 1.0, 4)) == pytest.approx(1.0)
def test_counterattack_reference():
t = ta.Counterattack()
assert t.update((20.0, 20.1, 14.9, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((10.0, 15.1, 9.9, 15.0, 1.0, 1)) == pytest.approx(1.0)
# --- Lifecycle ------------------------------------------------------------ # --- Lifecycle ------------------------------------------------------------
@@ -1907,6 +1948,20 @@ def test_three_stars_in_south_reference():
assert t.update((15.0, 15.0, 14.0, 14.0, 1.0, 2)) == pytest.approx(1.0) assert t.update((15.0, 15.0, 14.0, 14.0, 1.0, 2)) == pytest.approx(1.0)
def test_abandoned_baby_reference():
t = ta.AbandonedBaby()
assert t.update((20.0, 20.1, 14.9, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((13.0, 13.1, 12.9, 13.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((16.0, 18.1, 15.9, 18.0, 1.0, 2)) == pytest.approx(1.0)
def test_advance_block_reference():
t = ta.AdvanceBlock()
assert t.update((10.0, 13.1, 9.9, 13.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((12.0, 14.3, 11.9, 14.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((13.5, 15.0, 13.4, 14.5, 1.0, 2)) == pytest.approx(-1.0)
# --- Lifecycle ------------------------------------------------------------ # --- Lifecycle ------------------------------------------------------------
+5
View File
@@ -6347,6 +6347,11 @@ wasm_candle_pattern!(
wc::ThreeStarsInSouth, wc::ThreeStarsInSouth,
ThreeStarsInSouth ThreeStarsInSouth
); );
wasm_candle_pattern!(WasmAbandonedBaby, wc::AbandonedBaby, AbandonedBaby);
wasm_candle_pattern!(WasmAdvanceBlock, wc::AdvanceBlock, AdvanceBlock);
wasm_candle_pattern!(WasmBeltHold, wc::BeltHold, BeltHold);
wasm_candle_pattern!(WasmBreakaway, wc::Breakaway, Breakaway);
wasm_candle_pattern!(WasmCounterattack, wc::Counterattack, Counterattack);
// ============================== Microstructure: Order Book ============================== // ============================== Microstructure: Order Book ==============================
// //
@@ -0,0 +1,247 @@
//! Abandoned Baby candlestick pattern.
use crate::error::{Error, Result};
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Abandoned Baby — a strong 3-bar reversal where a doji is "abandoned" by price
/// gaps on both sides, isolating it from the candles before and after.
///
/// ```text
/// tol = tolerance * max(|bar2.open|, |bar2.close|)
/// bar2 doji (|bar2.close bar2.open| <= tol)
///
/// bullish (+1.0): bar1 red, bar2 gaps fully below bar1 (bar2.high < bar1.low),
/// bar3 green and gaps fully above bar2 (bar3.low > bar2.high)
/// bearish (1.0): bar1 green, bar2 gaps fully above bar1 (bar2.low > bar1.high),
/// bar3 red and gaps fully below bar2 (bar3.high < bar2.low)
/// ```
///
/// Output is `0.0` otherwise. 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 bounds how flat the middle candle must be to count as a doji; it
/// 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, `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::{AbandonedBaby, Candle, Indicator};
///
/// let mut indicator = AbandonedBaby::new();
/// indicator.update(Candle::new(20.0, 20.1, 14.9, 15.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(13.0, 13.1, 12.9, 13.0, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(16.0, 18.1, 15.9, 18.0, 1.0, 2).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone)]
pub struct AbandonedBaby {
tolerance: f64,
prev: Option<Candle>,
prev_prev: Option<Candle>,
has_emitted: bool,
}
impl Default for AbandonedBaby {
fn default() -> Self {
Self::new()
}
}
impl AbandonedBaby {
/// Construct a detector with the default relative doji 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 doji tolerance.
///
/// `tolerance` must lie in `[0, 1)`.
pub fn with_tolerance(tolerance: f64) -> Result<Self> {
if !(0.0..1.0).contains(&tolerance) {
return Err(Error::InvalidPeriod {
message: "abandoned baby tolerance must lie in [0, 1)",
});
}
Ok(Self {
tolerance,
prev: None,
prev_prev: None,
has_emitted: false,
})
}
/// Configured relative doji tolerance.
pub fn tolerance(&self) -> f64 {
self.tolerance
}
}
impl Indicator for AbandonedBaby {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
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 * bar2.open.abs().max(bar2.close.abs());
let bar2_is_doji = (bar2.close - bar2.open).abs() <= tol;
if !bar2_is_doji {
return Some(0.0);
}
// Bullish: red bar1, doji gaps below, green bar3 gaps above.
if bar1.close < bar1.open
&& bar2.high < bar1.low
&& candle.close > candle.open
&& candle.low > bar2.high
{
return Some(1.0);
}
// Bearish: green bar1, doji gaps above, red bar3 gaps below.
if bar1.close > bar1.open
&& bar2.low > bar1.high
&& candle.close < candle.open
&& candle.high < 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 {
"AbandonedBaby"
}
}
#[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!(AbandonedBaby::with_tolerance(-0.01).is_err());
assert!(AbandonedBaby::with_tolerance(1.0).is_err());
}
#[test]
fn accepts_valid_tolerance() {
let t = AbandonedBaby::with_tolerance(0.0).unwrap();
assert!((t.tolerance() - 0.0).abs() < 1e-12);
}
#[test]
fn accessors_and_metadata() {
let t = AbandonedBaby::default();
assert_eq!(t.name(), "AbandonedBaby");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
assert!((t.tolerance() - 0.001).abs() < 1e-12);
}
#[test]
fn bullish_abandoned_baby_is_plus_one() {
let mut t = AbandonedBaby::new();
assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(13.0, 13.1, 12.9, 13.0, 1)), Some(0.0));
assert_eq!(t.update(c(16.0, 18.1, 15.9, 18.0, 2)), Some(1.0));
}
#[test]
fn bearish_abandoned_baby_is_minus_one() {
let mut t = AbandonedBaby::new();
assert_eq!(t.update(c(15.0, 20.1, 14.9, 20.0, 0)), Some(0.0));
assert_eq!(t.update(c(22.0, 22.1, 21.9, 22.0, 1)), Some(0.0));
assert_eq!(t.update(c(19.0, 19.1, 16.9, 17.0, 2)), Some(-1.0));
}
#[test]
fn middle_not_doji_yields_zero() {
let mut t = AbandonedBaby::new();
t.update(c(20.0, 20.1, 14.9, 15.0, 0));
// Middle bar has a wide body -> not a doji.
assert_eq!(t.update(c(13.0, 14.0, 11.0, 11.5, 1)), Some(0.0));
assert_eq!(t.update(c(16.0, 18.1, 15.9, 18.0, 2)), Some(0.0));
}
#[test]
fn no_gap_yields_zero() {
let mut t = AbandonedBaby::new();
t.update(c(20.0, 20.1, 14.9, 15.0, 0));
// Doji overlaps bar1's range -> no gap.
assert_eq!(t.update(c(15.0, 15.1, 14.9, 15.0, 1)), Some(0.0));
assert_eq!(t.update(c(16.0, 18.1, 15.9, 18.0, 2)), Some(0.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = AbandonedBaby::new();
assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(13.0, 13.1, 12.9, 13.0, 1)), Some(0.0));
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = 100.0 + (i as f64 * 0.3).sin() * 5.0;
c(base, base + 1.0, base - 1.0, base + 0.5, i)
})
.collect();
let mut a = AbandonedBaby::new();
let mut b = AbandonedBaby::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = AbandonedBaby::new();
t.update(c(20.0, 20.1, 14.9, 15.0, 0));
t.update(c(13.0, 13.1, 12.9, 13.0, 1));
t.update(c(16.0, 18.1, 15.9, 18.0, 2));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), Some(0.0));
}
}
@@ -0,0 +1,192 @@
//! Advance Block candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Advance Block — a 3-bar bearish warning: three green candles still pushing to
/// higher closes, but visibly running out of steam — each real body shrinks while
/// the upper shadows lengthen, hinting the advance is about to stall.
///
/// ```text
/// all three green & higher closes
/// each opens inside the prior body
/// shrinking bodies (body3 < body2 < body1)
/// upper shadow of bar3 >= upper shadow of bar2 and bar3 has an upper shadow
/// ```
///
/// Output is `1.0` when the pattern completes and `0.0` otherwise. Advance Block
/// 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. 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::{AdvanceBlock, Candle, Indicator};
///
/// let mut indicator = AdvanceBlock::new();
/// indicator.update(Candle::new(10.0, 13.1, 9.9, 13.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(12.0, 14.3, 11.9, 14.0, 1.0, 1).unwrap());
/// let out = indicator
/// .update(Candle::new(13.5, 15.0, 13.4, 14.5, 1.0, 2).unwrap());
/// assert_eq!(out, Some(-1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct AdvanceBlock {
prev: Option<Candle>,
prev_prev: Option<Candle>,
has_emitted: bool,
}
impl AdvanceBlock {
/// Construct a new Advance Block detector.
pub const fn new() -> Self {
Self {
prev: None,
prev_prev: None,
has_emitted: false,
}
}
}
impl Indicator for AdvanceBlock {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
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 body1 = bar1.close - bar1.open;
let body2 = bar2.close - bar2.open;
let body3 = candle.close - candle.open;
let upper2 = bar2.high - bar2.close;
let upper3 = candle.high - candle.close;
if bar1.close > bar1.open
&& bar2.close > bar2.open
&& candle.close > candle.open
&& bar2.close > bar1.close
&& candle.close > bar2.close
&& bar2.open >= bar1.open
&& bar2.open <= bar1.close
&& candle.open >= bar2.open
&& candle.open <= bar2.close
&& body2 < body1
&& body3 < body2
&& upper3 >= upper2
&& upper3 > 0.0
{
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 {
"AdvanceBlock"
}
}
#[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 = AdvanceBlock::new();
assert_eq!(t.name(), "AdvanceBlock");
assert_eq!(t.warmup_period(), 3);
assert!(!t.is_ready());
}
#[test]
fn advance_block_is_minus_one() {
let mut t = AdvanceBlock::new();
assert_eq!(t.update(c(10.0, 13.1, 9.9, 13.0, 0)), Some(0.0));
assert_eq!(t.update(c(12.0, 14.3, 11.9, 14.0, 1)), Some(0.0));
assert_eq!(t.update(c(13.5, 15.0, 13.4, 14.5, 2)), Some(-1.0));
}
#[test]
fn strong_advance_yields_zero() {
let mut t = AdvanceBlock::new();
// Bodies grow instead of shrinking -> a strong advance, not blocked.
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.6, 10.4, 12.5, 1)), Some(0.0));
assert_eq!(t.update(c(11.5, 14.1, 11.4, 14.0, 2)), Some(0.0));
}
#[test]
fn no_upper_shadow_growth_yields_zero() {
let mut t = AdvanceBlock::new();
t.update(c(10.0, 13.1, 9.9, 13.0, 0));
t.update(c(12.0, 14.3, 11.9, 14.0, 1));
// bar3 shrinking body but no upper shadow -> not blocked.
assert_eq!(t.update(c(13.5, 14.5, 13.4, 14.5, 2)), Some(0.0));
}
#[test]
fn first_two_bars_return_zero() {
let mut t = AdvanceBlock::new();
assert_eq!(t.update(c(10.0, 13.1, 9.9, 13.0, 0)), Some(0.0));
assert_eq!(t.update(c(12.0, 14.3, 11.9, 14.0, 1)), Some(0.0));
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = 100.0 + i as f64;
c(base, base + 2.0, base - 0.2, base + 1.5, i)
})
.collect();
let mut a = AdvanceBlock::new();
let mut b = AdvanceBlock::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = AdvanceBlock::new();
t.update(c(10.0, 13.1, 9.9, 13.0, 0));
t.update(c(12.0, 14.3, 11.9, 14.0, 1));
t.update(c(13.5, 15.0, 13.4, 14.5, 2));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(10.0, 13.1, 9.9, 13.0, 0)), Some(0.0));
}
}
@@ -0,0 +1,211 @@
//! Belt-hold candlestick pattern.
use crate::error::{Error, Result};
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Belt-hold — a single-bar reversal: a long candle that opens at one extreme of
/// its range (an "opening marubozu") and runs the other way.
///
/// ```text
/// range = high low
/// bullish (+1.0): green, opens at the low (open low <= tol * range) & long body
/// bearish (1.0): red, opens at the high (high open <= tol * range) & long body
/// long body = |close open| >= 0.5 * range
/// ```
///
/// Output is `0.0` when the opening side carries a shadow, the body is short, or
/// the range is degenerate. `shadow_tolerance` defaults to `0.05` (5 % of the bar
/// range allowed on the opening side) 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, `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::{BeltHold, Candle, Indicator};
///
/// let mut indicator = BeltHold::new();
/// // Bullish belt-hold: opens at the low, closes near the high.
/// let candle = Candle::new(10.0, 12.0, 10.0, 11.5, 1.0, 0).unwrap();
/// assert_eq!(indicator.update(candle), Some(1.0));
/// ```
#[derive(Debug, Clone)]
pub struct BeltHold {
shadow_tolerance: f64,
has_emitted: bool,
}
impl Default for BeltHold {
fn default() -> Self {
Self::new()
}
}
impl BeltHold {
/// Construct a Belt-hold detector with the default 5 % opening-shadow tolerance.
pub const fn new() -> Self {
Self {
shadow_tolerance: 0.05,
has_emitted: false,
}
}
/// Construct a Belt-hold detector with a custom opening-shadow tolerance.
///
/// `shadow_tolerance` must lie in `[0, 1)`.
pub fn with_tolerance(shadow_tolerance: f64) -> Result<Self> {
if !(0.0..1.0).contains(&shadow_tolerance) {
return Err(Error::InvalidPeriod {
message: "belt-hold shadow tolerance must lie in [0, 1)",
});
}
Ok(Self {
shadow_tolerance,
has_emitted: false,
})
}
/// Configured opening-shadow tolerance.
pub fn shadow_tolerance(&self) -> f64 {
self.shadow_tolerance
}
}
impl Indicator for BeltHold {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
self.has_emitted = true;
let range = candle.high - candle.low;
if range <= 0.0 {
return Some(0.0);
}
let body = candle.close - candle.open;
if body.abs() < 0.5 * range {
return Some(0.0);
}
let tol = self.shadow_tolerance * range;
// Bullish: opens at the low (no lower shadow), green body.
if body > 0.0 && candle.open - candle.low <= tol {
return Some(1.0);
}
// Bearish: opens at the high (no upper shadow), red body.
if body < 0.0 && candle.high - candle.open <= tol {
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 {
"BeltHold"
}
}
#[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!(BeltHold::with_tolerance(-0.01).is_err());
assert!(BeltHold::with_tolerance(1.0).is_err());
}
#[test]
fn accepts_valid_tolerance() {
let t = BeltHold::with_tolerance(0.0).unwrap();
assert!((t.shadow_tolerance() - 0.0).abs() < 1e-12);
}
#[test]
fn accessors_and_metadata() {
let t = BeltHold::default();
assert_eq!(t.name(), "BeltHold");
assert_eq!(t.warmup_period(), 1);
assert!(!t.is_ready());
assert!((t.shadow_tolerance() - 0.05).abs() < 1e-12);
}
#[test]
fn bullish_belt_hold_is_plus_one() {
let mut t = BeltHold::new();
assert_eq!(t.update(c(10.0, 12.0, 10.0, 11.5, 0)), Some(1.0));
}
#[test]
fn bearish_belt_hold_is_minus_one() {
let mut t = BeltHold::new();
assert_eq!(t.update(c(12.0, 12.0, 10.0, 10.5, 0)), Some(-1.0));
}
#[test]
fn opening_shadow_yields_zero() {
let mut t = BeltHold::new();
// Opens 0.5 above the low -> lower shadow exceeds tolerance.
assert_eq!(t.update(c(10.5, 12.0, 10.0, 11.5, 0)), Some(0.0));
}
#[test]
fn short_body_yields_zero() {
let mut t = BeltHold::new();
// Body 0.5 < half the range (1.0) -> not a long belt-hold.
assert_eq!(t.update(c(10.0, 12.0, 10.0, 10.5, 0)), Some(0.0));
}
#[test]
fn zero_range_yields_zero() {
let mut t = BeltHold::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 + 2.0, base, base + 1.8, i)
})
.collect();
let mut a = BeltHold::new();
let mut b = BeltHold::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = BeltHold::new();
t.update(c(10.0, 12.0, 10.0, 11.5, 0));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
}
}
@@ -0,0 +1,250 @@
//! Breakaway candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Breakaway — a 5-bar reversal that fades an exhausted run. A trend gaps away on
/// the second bar, drifts two more bars in the same direction, then the fifth bar
/// snaps the other way and closes back inside the body gap left between the first
/// and second bars, signalling the move has broken away from the crowd and is
/// turning.
///
/// ```text
/// bullish (+1.0) — appears in a decline:
/// bar1 black (close < open)
/// bar2 black & its body gaps DOWN below bar1's body (bar2.open < bar1.close)
/// bar3 extends lower (high & low below bar2)
/// bar4 black & extends lower (high & low below bar3)
/// bar5 green & closes inside the bar1/bar2 body gap (bar2.open < close < bar1.close)
///
/// bearish (1.0) — the mirror in an advance:
/// bar1 white (close > open)
/// bar2 white & its body gaps UP above bar1's body (bar2.open > bar1.close)
/// bar3 extends higher (high & low above bar2)
/// bar4 white & extends higher (high & low above bar3)
/// bar5 red & closes inside the bar1/bar2 body gap (bar1.close < close < bar2.open)
/// ```
///
/// The middle bar (`bar3`) may be either colour — only its high/low must extend
/// the run. Output is `+1.0` bullish, `1.0` bearish, `0.0` otherwise. The first
/// four bars always return `0.0` because the five-bar window is not yet filled.
/// Pattern-shape check only — no trend filter is applied; combine with a trend
/// indicator for actionable signals. Recognition uses TA-Lib's
/// `CDLBREAKAWAY` body-gap and high/low ordering rules directly; it does not add
/// TA-Lib's rolling body-length average, matching the geometric house style of
/// the other multi-bar patterns in this family.
///
/// # 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::{Breakaway, Candle, Indicator};
///
/// let mut indicator = Breakaway::new();
/// indicator.update(Candle::new(20.0, 20.2, 14.8, 15.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(14.0, 14.1, 11.9, 12.0, 1.0, 1).unwrap());
/// indicator.update(Candle::new(12.5, 13.0, 10.5, 11.0, 1.0, 2).unwrap());
/// indicator.update(Candle::new(11.0, 11.5, 9.0, 9.5, 1.0, 3).unwrap());
/// let out = indicator
/// .update(Candle::new(9.5, 14.7, 9.4, 14.5, 1.0, 4).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct Breakaway {
c1: Option<Candle>,
c2: Option<Candle>,
c3: Option<Candle>,
c4: Option<Candle>,
has_emitted: bool,
}
impl Breakaway {
/// Construct a new Breakaway detector.
pub const fn new() -> Self {
Self {
c1: None,
c2: None,
c3: None,
c4: None,
has_emitted: false,
}
}
}
impl Indicator for Breakaway {
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;
let bar3 = self.c3;
let bar4 = self.c4;
self.c1 = self.c2;
self.c2 = self.c3;
self.c3 = self.c4;
self.c4 = Some(candle);
let (Some(bar1), Some(bar2), Some(bar3), Some(bar4)) = (bar1, bar2, bar3, bar4) else {
return Some(0.0);
};
// Bullish: a decline gaps lower, runs two more bars down, then a green
// bar5 closes back inside the bar1/bar2 body gap.
if bar1.close < bar1.open
&& bar2.close < bar2.open
&& bar2.open < bar1.close
&& bar3.high < bar2.high
&& bar3.low < bar2.low
&& bar4.close < bar4.open
&& bar4.high < bar3.high
&& bar4.low < bar3.low
&& candle.close > candle.open
&& candle.close > bar2.open
&& candle.close < bar1.close
{
return Some(1.0);
}
// Bearish: the mirror — an advance gaps higher, runs two more bars up,
// then a red bar5 closes back inside the bar1/bar2 body gap.
if bar1.close > bar1.open
&& bar2.close > bar2.open
&& bar2.open > bar1.close
&& bar3.high > bar2.high
&& bar3.low > bar2.low
&& bar4.close > bar4.open
&& bar4.high > bar3.high
&& bar4.low > bar3.low
&& candle.close < candle.open
&& candle.close < bar2.open
&& candle.close > bar1.close
{
return Some(-1.0);
}
Some(0.0)
}
fn reset(&mut self) {
self.c1 = None;
self.c2 = None;
self.c3 = None;
self.c4 = None;
self.has_emitted = false;
}
fn warmup_period(&self) -> usize {
5
}
fn is_ready(&self) -> bool {
self.has_emitted
}
fn name(&self) -> &'static str {
"Breakaway"
}
}
#[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 = Breakaway::new();
assert_eq!(t.name(), "Breakaway");
assert_eq!(t.warmup_period(), 5);
assert!(!t.is_ready());
}
#[test]
fn bullish_breakaway_is_plus_one() {
let mut t = Breakaway::new();
assert_eq!(t.update(c(20.0, 20.2, 14.8, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(14.0, 14.1, 11.9, 12.0, 1)), Some(0.0));
assert_eq!(t.update(c(12.5, 13.0, 10.5, 11.0, 2)), Some(0.0));
assert_eq!(t.update(c(11.0, 11.5, 9.0, 9.5, 3)), Some(0.0));
assert_eq!(t.update(c(9.5, 14.7, 9.4, 14.5, 4)), Some(1.0));
}
#[test]
fn bearish_breakaway_is_minus_one() {
let mut t = Breakaway::new();
assert_eq!(t.update(c(15.0, 20.2, 14.8, 20.0, 0)), Some(0.0));
assert_eq!(t.update(c(21.0, 23.1, 20.9, 23.0, 1)), Some(0.0));
assert_eq!(t.update(c(22.5, 24.5, 21.5, 24.0, 2)), Some(0.0));
assert_eq!(t.update(c(24.0, 26.5, 23.0, 26.0, 3)), Some(0.0));
assert_eq!(t.update(c(27.0, 27.2, 20.4, 20.5, 4)), Some(-1.0));
}
#[test]
fn no_body_gap_yields_zero() {
let mut t = Breakaway::new();
// bar2 does not gap below bar1's body (bar2.open >= bar1.close).
t.update(c(20.0, 20.2, 14.8, 15.0, 0));
t.update(c(16.0, 16.1, 13.9, 14.0, 1));
t.update(c(13.5, 14.0, 11.5, 12.0, 2));
t.update(c(12.0, 12.5, 10.0, 10.5, 3));
assert_eq!(t.update(c(10.5, 15.7, 10.4, 15.5, 4)), Some(0.0));
}
#[test]
fn bullish_close_outside_gap_yields_zero() {
let mut t = Breakaway::new();
t.update(c(20.0, 20.2, 14.8, 15.0, 0));
t.update(c(14.0, 14.1, 11.9, 12.0, 1));
t.update(c(12.5, 13.0, 10.5, 11.0, 2));
t.update(c(11.0, 11.5, 9.0, 9.5, 3));
// bar5 closes at 13.0 — below bar2.open (14), so outside the body gap.
assert_eq!(t.update(c(9.5, 13.2, 9.4, 13.0, 4)), Some(0.0));
}
#[test]
fn first_four_bars_return_zero() {
let mut t = Breakaway::new();
assert_eq!(t.update(c(20.0, 20.2, 14.8, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(14.0, 14.1, 11.9, 12.0, 1)), Some(0.0));
assert_eq!(t.update(c(12.5, 13.0, 10.5, 11.0, 2)), Some(0.0));
assert_eq!(t.update(c(11.0, 11.5, 9.0, 9.5, 3)), 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.0, base - 0.5, base + 1.5, i)
})
.collect();
let mut a = Breakaway::new();
let mut b = Breakaway::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = Breakaway::new();
t.update(c(20.0, 20.2, 14.8, 15.0, 0));
t.update(c(14.0, 14.1, 11.9, 12.0, 1));
t.update(c(12.5, 13.0, 10.5, 11.0, 2));
t.update(c(11.0, 11.5, 9.0, 9.5, 3));
t.update(c(9.5, 14.7, 9.4, 14.5, 4));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(20.0, 20.2, 14.8, 15.0, 0)), Some(0.0));
}
}
@@ -0,0 +1,245 @@
//! Counterattack candlestick pattern.
use crate::error::{Error, Result};
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Counterattack — a 2-bar reversal where the second bar storms back to close
/// right where the first bar closed. A long candle runs with the trend, then an
/// opposite-coloured long candle opens far in the trend direction and rallies (or
/// sells off) all the way back to the prior close — the two closes meeting forms
/// the "counterattack line".
///
/// ```text
/// long bodies = |close open| >= 0.5 * (high low) (both bars)
/// equal closes = |close2 close1| <= tol * mean(range1, range2)
/// bullish (+1.0): bar1 black (down), bar2 white (up), equal closes
/// bearish (1.0): bar1 white (up), bar2 black (down), equal closes
/// ```
///
/// Output is `+1.0` bullish, `1.0` bearish, and `0.0` when the bodies are short,
/// the colours match, or the closes are not level. The first bar always returns
/// `0.0` because the two-bar window is not yet filled. `equal_tolerance` defaults
/// to `0.05` (TA-Lib's `CDLCOUNTERATTACK` "equal" factor — 5 % of the mean bar
/// range) and must lie in `[0, 1)`. The body-length test uses a fixed half-range
/// fraction rather than TA-Lib's rolling body average, matching the geometric
/// house style of this pattern family. 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, Counterattack, Indicator};
///
/// let mut indicator = Counterattack::new();
/// // Bullish: a long black bar, then a long white bar closing at the same level.
/// indicator.update(Candle::new(20.0, 20.1, 14.9, 15.0, 1.0, 0).unwrap());
/// let out = indicator
/// .update(Candle::new(10.0, 15.1, 9.9, 15.0, 1.0, 1).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone)]
pub struct Counterattack {
equal_tolerance: f64,
prev: Option<Candle>,
has_emitted: bool,
}
impl Default for Counterattack {
fn default() -> Self {
Self::new()
}
}
impl Counterattack {
/// Construct a Counterattack detector with the default 5 % equal-close tolerance.
pub const fn new() -> Self {
Self {
equal_tolerance: 0.05,
prev: None,
has_emitted: false,
}
}
/// Construct a Counterattack detector with a custom equal-close tolerance.
///
/// `equal_tolerance` is the fraction of the mean bar range within which the
/// two closes must agree and must lie in `[0, 1)`.
pub fn with_tolerance(equal_tolerance: f64) -> Result<Self> {
if !(0.0..1.0).contains(&equal_tolerance) {
return Err(Error::InvalidPeriod {
message: "counterattack equal tolerance must lie in [0, 1)",
});
}
Ok(Self {
equal_tolerance,
prev: None,
has_emitted: false,
})
}
/// Configured equal-close tolerance.
pub fn equal_tolerance(&self) -> f64 {
self.equal_tolerance
}
}
impl Indicator for Counterattack {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
self.has_emitted = true;
let prev = self.prev;
self.prev = Some(candle);
let Some(bar1) = prev else {
return Some(0.0);
};
let range1 = bar1.high - bar1.low;
let range2 = candle.high - candle.low;
let body1 = bar1.close - bar1.open;
let body2 = candle.close - candle.open;
let long1 = body1.abs() >= 0.5 * range1;
let long2 = body2.abs() >= 0.5 * range2;
let tol = self.equal_tolerance * 0.5 * (range1 + range2);
let equal_close = (candle.close - bar1.close).abs() <= tol;
if !(long1 && long2 && equal_close) {
return Some(0.0);
}
// Bullish: a long black bar met by a long white bar closing level.
if body1 < 0.0 && body2 > 0.0 {
return Some(1.0);
}
// Bearish: a long white bar met by a long black bar closing level.
if body1 > 0.0 && body2 < 0.0 {
return Some(-1.0);
}
Some(0.0)
}
fn reset(&mut self) {
self.prev = None;
self.has_emitted = false;
}
fn warmup_period(&self) -> usize {
2
}
fn is_ready(&self) -> bool {
self.has_emitted
}
fn name(&self) -> &'static str {
"Counterattack"
}
}
#[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!(Counterattack::with_tolerance(-0.01).is_err());
assert!(Counterattack::with_tolerance(1.0).is_err());
}
#[test]
fn accepts_valid_tolerance() {
let t = Counterattack::with_tolerance(0.0).unwrap();
assert!((t.equal_tolerance() - 0.0).abs() < 1e-12);
}
#[test]
fn accessors_and_metadata() {
let t = Counterattack::default();
assert_eq!(t.name(), "Counterattack");
assert_eq!(t.warmup_period(), 2);
assert!(!t.is_ready());
assert!((t.equal_tolerance() - 0.05).abs() < 1e-12);
}
#[test]
fn bullish_counterattack_is_plus_one() {
let mut t = Counterattack::new();
assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(10.0, 15.1, 9.9, 15.0, 1)), Some(1.0));
}
#[test]
fn bearish_counterattack_is_minus_one() {
let mut t = Counterattack::new();
assert_eq!(t.update(c(15.0, 20.1, 14.9, 20.0, 0)), Some(0.0));
assert_eq!(t.update(c(25.0, 25.1, 19.9, 20.0, 1)), Some(-1.0));
}
#[test]
fn unequal_close_yields_zero() {
let mut t = Counterattack::new();
t.update(c(20.0, 20.1, 14.9, 15.0, 0));
// Second close at 17.0 is far from the first close (15.0) -> not level.
assert_eq!(t.update(c(10.0, 17.1, 9.9, 17.0, 1)), Some(0.0));
}
#[test]
fn same_color_yields_zero() {
let mut t = Counterattack::new();
// Both bars black -> not opposite colours.
t.update(c(20.0, 20.1, 14.9, 15.0, 0));
assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 1)), Some(0.0));
}
#[test]
fn short_body_yields_zero() {
let mut t = Counterattack::new();
// Second bar has a tiny body relative to its range.
t.update(c(20.0, 20.1, 14.9, 15.0, 0));
assert_eq!(t.update(c(14.8, 20.0, 9.9, 15.2, 1)), Some(0.0));
}
#[test]
fn first_bar_returns_zero() {
let mut t = Counterattack::new();
assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.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 + 2.0, base - 2.0, base + 1.5, i)
})
.collect();
let mut a = Counterattack::new();
let mut b = Counterattack::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = Counterattack::new();
t.update(c(20.0, 20.1, 14.9, 15.0, 0));
t.update(c(10.0, 15.1, 9.9, 15.0, 1));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(20.0, 20.1, 14.9, 15.0, 0)), Some(0.0));
}
}
+16 -1
View File
@@ -4,11 +4,13 @@
//! [`FAMILIES`]. Every public name is re-exported flat from this module and //! [`FAMILIES`]. Every public name is re-exported flat from this module and
//! from the crate root for convenience. //! from the crate root for convenience.
mod abandoned_baby;
mod acceleration_bands; mod acceleration_bands;
mod accelerator_oscillator; mod accelerator_oscillator;
mod ad_oscillator; mod ad_oscillator;
mod adaptive_cycle; mod adaptive_cycle;
mod adl; mod adl;
mod advance_block;
mod adx; mod adx;
mod adxr; mod adxr;
mod alligator; mod alligator;
@@ -26,9 +28,11 @@ mod average_drawdown;
mod awesome_oscillator; mod awesome_oscillator;
mod awesome_oscillator_histogram; mod awesome_oscillator_histogram;
mod balance_of_power; mod balance_of_power;
mod belt_hold;
mod beta; mod beta;
mod bollinger; mod bollinger;
mod bollinger_bandwidth; mod bollinger_bandwidth;
mod breakaway;
mod calendar_spread; mod calendar_spread;
mod calmar_ratio; mod calmar_ratio;
mod camarilla_pivots; mod camarilla_pivots;
@@ -48,6 +52,7 @@ mod cointegration;
mod conditional_value_at_risk; mod conditional_value_at_risk;
mod connors_rsi; mod connors_rsi;
mod coppock; mod coppock;
mod counterattack;
mod cvd; mod cvd;
mod cybernetic_cycle; mod cybernetic_cycle;
mod decycler; mod decycler;
@@ -253,11 +258,13 @@ mod zero_lag_macd;
mod zig_zag; mod zig_zag;
mod zlema; mod zlema;
pub use abandoned_baby::AbandonedBaby;
pub use acceleration_bands::{AccelerationBands, AccelerationBandsOutput}; pub use acceleration_bands::{AccelerationBands, AccelerationBandsOutput};
pub use accelerator_oscillator::AcceleratorOscillator; pub use accelerator_oscillator::AcceleratorOscillator;
pub use ad_oscillator::AdOscillator; pub use ad_oscillator::AdOscillator;
pub use adaptive_cycle::AdaptiveCycle; pub use adaptive_cycle::AdaptiveCycle;
pub use adl::Adl; pub use adl::Adl;
pub use advance_block::AdvanceBlock;
pub use adx::{Adx, AdxOutput}; pub use adx::{Adx, AdxOutput};
pub use adxr::Adxr; pub use adxr::Adxr;
pub use alligator::{Alligator, AlligatorOutput}; pub use alligator::{Alligator, AlligatorOutput};
@@ -275,9 +282,11 @@ pub use average_drawdown::AverageDrawdown;
pub use awesome_oscillator::AwesomeOscillator; pub use awesome_oscillator::AwesomeOscillator;
pub use awesome_oscillator_histogram::AwesomeOscillatorHistogram; pub use awesome_oscillator_histogram::AwesomeOscillatorHistogram;
pub use balance_of_power::BalanceOfPower; pub use balance_of_power::BalanceOfPower;
pub use belt_hold::BeltHold;
pub use beta::Beta; pub use beta::Beta;
pub use bollinger::{BollingerBands, BollingerOutput}; pub use bollinger::{BollingerBands, BollingerOutput};
pub use bollinger_bandwidth::BollingerBandwidth; pub use bollinger_bandwidth::BollingerBandwidth;
pub use breakaway::Breakaway;
pub use calendar_spread::CalendarSpread; pub use calendar_spread::CalendarSpread;
pub use calmar_ratio::CalmarRatio; pub use calmar_ratio::CalmarRatio;
pub use camarilla_pivots::{Camarilla, CamarillaPivotsOutput}; pub use camarilla_pivots::{Camarilla, CamarillaPivotsOutput};
@@ -297,6 +306,7 @@ pub use cointegration::{Cointegration, CointegrationOutput};
pub use conditional_value_at_risk::ConditionalValueAtRisk; pub use conditional_value_at_risk::ConditionalValueAtRisk;
pub use connors_rsi::ConnorsRsi; pub use connors_rsi::ConnorsRsi;
pub use coppock::Coppock; pub use coppock::Coppock;
pub use counterattack::Counterattack;
pub use cvd::CumulativeVolumeDelta; pub use cvd::CumulativeVolumeDelta;
pub use cybernetic_cycle::CyberneticCycle; pub use cybernetic_cycle::CyberneticCycle;
pub use decycler::Decycler; pub use decycler::Decycler;
@@ -770,6 +780,11 @@ pub const FAMILIES: &[(&str, &[&str])] = &[
"IdenticalThreeCrows", "IdenticalThreeCrows",
"ThreeLineStrike", "ThreeLineStrike",
"ThreeStarsInSouth", "ThreeStarsInSouth",
"AbandonedBaby",
"AdvanceBlock",
"BeltHold",
"Breakaway",
"Counterattack",
], ],
), ),
( (
@@ -861,6 +876,6 @@ mod family_tests {
// the actual indicator count is the early-warning signal that an // the actual indicator count is the early-warning signal that an
// indicator was added without being assigned a family. // indicator was added without being assigned a family.
let total: usize = FAMILIES.iter().map(|(_, ns)| ns.len()).sum(); let total: usize = FAMILIES.iter().map(|(_, ns)| ns.len()).sum();
assert_eq!(total, 244, "FAMILIES total drifted from indicator count"); assert_eq!(total, 249, "FAMILIES total drifted from indicator count");
} }
} }
+20 -13
View File
@@ -35,6 +35,12 @@
//! ``` //! ```
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
// The libtest harness collects every `#[test]` into a compiler-generated array
// of test references. With 2000+ unit tests that array exceeds clippy's 16 KB
// `large_stack_arrays` threshold; the diagnostic is spanless libtest scaffolding,
// not our code, so it cannot be silenced at a call site. Suppress it only in test
// builds — library code is still linted for genuinely large stack arrays.
#![cfg_attr(test, allow(clippy::large_stack_arrays))]
mod derivatives; mod derivatives;
mod error; mod error;
@@ -47,19 +53,20 @@ pub mod indicators;
pub use derivatives::DerivativesTick; pub use derivatives::DerivativesTick;
pub use error::{Error, Result}; pub use error::{Error, Result};
pub use indicators::{ pub use indicators::{
AccelerationBands, AccelerationBandsOutput, AcceleratorOscillator, AdOscillator, AdaptiveCycle, AbandonedBaby, AccelerationBands, AccelerationBandsOutput, AcceleratorOscillator, AdOscillator,
Adl, Adx, AdxOutput, Adxr, Alligator, AlligatorOutput, Alma, Alpha, AnchoredVwap, Apo, Aroon, AdaptiveCycle, Adl, AdvanceBlock, Adx, AdxOutput, Adxr, Alligator, AlligatorOutput, Alma,
AroonOscillator, AroonOutput, Atr, AtrBands, AtrBandsOutput, AtrTrailingStop, Autocorrelation, Alpha, AnchoredVwap, Apo, Aroon, AroonOscillator, AroonOutput, Atr, AtrBands, AtrBandsOutput,
AverageDrawdown, AwesomeOscillator, AwesomeOscillatorHistogram, BalanceOfPower, Beta, AtrTrailingStop, Autocorrelation, AverageDrawdown, AwesomeOscillator,
BollingerBands, BollingerBandwidth, BollingerOutput, CalendarSpread, CalmarRatio, Camarilla, AwesomeOscillatorHistogram, BalanceOfPower, BeltHold, Beta, BollingerBands, BollingerBandwidth,
CamarillaPivotsOutput, Cci, CenterOfGravity, Cfo, ChaikinMoneyFlow, ChaikinOscillator, BollingerOutput, Breakaway, CalendarSpread, CalmarRatio, Camarilla, CamarillaPivotsOutput, Cci,
ChaikinVolatility, ChandeKrollStop, ChandeKrollStopOutput, ChandelierExit, CenterOfGravity, Cfo, ChaikinMoneyFlow, ChaikinOscillator, ChaikinVolatility, ChandeKrollStop,
ChandelierExitOutput, ChoppinessIndex, ClassicPivots, ClassicPivotsOutput, Cmo, ChandeKrollStopOutput, ChandelierExit, ChandelierExitOutput, ChoppinessIndex, ClassicPivots,
CoefficientOfVariation, Cointegration, CointegrationOutput, ConditionalValueAtRisk, ConnorsRsi, ClassicPivotsOutput, Cmo, CoefficientOfVariation, Cointegration, CointegrationOutput,
Coppock, CumulativeVolumeDelta, CyberneticCycle, Decycler, DecyclerOscillator, Dema, ConditionalValueAtRisk, ConnorsRsi, Coppock, Counterattack, CumulativeVolumeDelta,
DemandIndex, DemarkPivots, DemarkPivotsOutput, DepthSlope, DetrendedStdDev, Doji, Donchian, CyberneticCycle, Decycler, DecyclerOscillator, Dema, DemandIndex, DemarkPivots,
DonchianOutput, DonchianStop, DonchianStopOutput, DoubleBollinger, DoubleBollingerOutput, Dpo, DemarkPivotsOutput, DepthSlope, DetrendedStdDev, Doji, Donchian, DonchianOutput, DonchianStop,
DrawdownDuration, EaseOfMovement, EffectiveSpread, EhlersStochastic, ElderImpulse, Ema, DonchianStopOutput, DoubleBollinger, DoubleBollingerOutput, Dpo, DrawdownDuration,
EaseOfMovement, EffectiveSpread, EhlersStochastic, ElderImpulse, Ema,
EmpiricalModeDecomposition, Engulfing, Evwma, Fama, FibonacciPivots, FibonacciPivotsOutput, EmpiricalModeDecomposition, Engulfing, Evwma, Fama, FibonacciPivots, FibonacciPivotsOutput,
FisherTransform, Footprint, FootprintOutput, ForceIndex, FractalChaosBands, FisherTransform, Footprint, FootprintOutput, ForceIndex, FractalChaosBands,
FractalChaosBandsOutput, Frama, FundingBasis, FundingRate, FundingRateMean, FundingRateZScore, FractalChaosBandsOutput, Frama, FundingBasis, FundingRate, FundingRateMean, FundingRateZScore,
+1 -1
View File
@@ -8,7 +8,7 @@ That includes:
[Python](https://docs.wickra.org/Quickstart-Python), [Python](https://docs.wickra.org/Quickstart-Python),
[Node](https://docs.wickra.org/Quickstart-Node), and [Node](https://docs.wickra.org/Quickstart-Node), and
[WASM](https://docs.wickra.org/Quickstart-WASM). [WASM](https://docs.wickra.org/Quickstart-WASM).
- A per-indicator deep dive for every one of the **249 indicators** across - A per-indicator deep dive for every one of the **254 indicators** across
the sixteen families (Moving Averages, Momentum Oscillators, Trend & the sixteen families (Moving Averages, Momentum Oscillators, Trend &
Directional, Price Oscillators, Volatility & Bands, Bands & Channels, Directional, Price Oscillators, Volatility & Bands, Bands & Channels,
Trailing Stops, Volume, Price Statistics, Ehlers / Cycle DSP, Pivots & Trailing Stops, Volume, Price Statistics, Ehlers / Cycle DSP, Pivots &
+6 -20
View File
@@ -23,26 +23,7 @@
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use wickra_core::{ use wickra_core::{
AccelerationBands, AcceleratorOscillator, AdOscillator, Adl, Adx, Adxr, Alligator, 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, Donchian, DonchianStop, EaseOfMovement, Engulfing, Evwma, FibonacciPivots, ForceIndex, FractalChaosBands, GarmanKlassVolatility, 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, 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
AnchoredVwap, Aroon, AroonOscillator, Atr, AtrBands, AtrTrailingStop, AwesomeOscillator,
AwesomeOscillatorHistogram, BalanceOfPower, BatchExt, Camarilla, Candle, Cci, ChaikinMoneyFlow,
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,
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, ThreeLineStrike, ThreeOutside,
ThreeSoldiersOrCrows, ThreeStarsInSouth, TrueRange,
Tsv, TtmSqueeze, Tweezer, TwoCrows, TypicalPrice, UltimateOscillator, UpsideGapTwoCrows,
ValueArea, VoltyStop,
VolumeOscillator, VolumePriceTrend, Vortex, Vwap, VwapStdDevBands, Vwma, Vzo, WaveTrend,
WeightedClose, WilliamsFractals, WilliamsR, WoodiePivots, YangZhangVolatility, YoyoExit,
ZigZag,
}; };
/// Convert a flat `f64` stream into a `Vec<Candle>` by chunking it into /// Convert a flat `f64` stream into a `Vec<Candle>` by chunking it into
@@ -297,6 +278,11 @@ fuzz_target!(|data: Vec<f64>| {
} }
// --- Candlestick Patterns (family 14) --- // --- Candlestick Patterns (family 14) ---
drive(Counterattack::new, &candles);
drive(Breakaway::new, &candles);
drive(BeltHold::new, &candles);
drive(AbandonedBaby::new, &candles);
drive(AdvanceBlock::new, &candles);
drive(Doji::new, &candles); drive(Doji::new, &candles);
drive(|| Doji::new().signed(), &candles); drive(|| Doji::new().signed(), &candles);
drive(Hammer::new, &candles); drive(Hammer::new, &candles);