feat: TA-Lib candlestick patterns — matching-low/lines/three-methods (part 7 of 9) (#139)

Adds five TA-Lib candlestick patterns, all `Input = Candle`, `Output = f64`
(`+1.0` bullish / `-1.0` bearish / `0.0` no pattern), wired across core,
Python/Node/WASM bindings, fuzz, and tests.

- **Matching Low** (`CDLMATCHINGLOW`) — 2-bar bullish reversal: two black candles in a decline share the same close, signalling selling pressure is exhausting; bullish +1.
- **Long Line** (`CDLLONGLINE`) — a candle whose range beats a rolling average of recent ranges with a body-dominated range; bullish +1 (white) / bearish -1 (black).
- **Short Line** (`CDLSHORTLINE`) — a compact candle whose range falls below the rolling average with a body-dominated range; bullish +1 (white) / bearish -1 (black).
- **Rising Three Methods** (`CDLRISEFALL3METHODS`) — 5-bar bullish continuation: a long white candle, three small bars holding within its range, then a white breakout to new highs; bullish +1.
- **Falling Three Methods** (`CDLRISEFALL3METHODS`) — the bearish mirror: a long black candle, three small bars within its range, then a black breakdown to new lows; bearish -1.

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

Stacked on #138 (part 6 of 9); base retargets to `main` as the chain merges.
This commit is contained in:
kingchenc
2026-06-02 17:16:15 +02:00
committed by GitHub
parent 04ae145126
commit c2c85c7ecf
19 changed files with 1285 additions and 41 deletions
+5
View File
@@ -57,6 +57,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Kicking by Length** — a kicking pattern signalled by the colour of the longer marubozu; +1 / -1 (`CDLKICKINGBYLENGTH`). - **Kicking by Length** — a kicking pattern signalled by the colour of the longer marubozu; +1 / -1 (`CDLKICKINGBYLENGTH`).
- **Ladder Bottom** — three descending black candles, a fourth with an upper shadow, then a white candle gapping up, a bullish reversal; +1 (`CDLLADDERBOTTOM`). - **Ladder Bottom** — three descending black candles, a fourth with an upper shadow, then a white candle gapping up, a bullish reversal; +1 (`CDLLADDERBOTTOM`).
- **Mat Hold** — a long white candle, a holding three-bar pullback, then a new-high white candle, a bullish continuation; +1 (`CDLMATHOLD`). - **Mat Hold** — a long white candle, a holding three-bar pullback, then a new-high white candle, a bullish continuation; +1 (`CDLMATHOLD`).
- **Matching Low** — a 2-bar bullish reversal where two black candles in a decline share the same close, signalling selling pressure is exhausting; bullish +1 (`CDLMATCHINGLOW`).
- **Long Line** — a single long-bodied candle with short shadows; bullish +1 (white) or bearish -1 (black) by colour (`CDLLONGLINE`).
- **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`).
- **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=274" 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=279" 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 274 indicators; start at the every one of the 279 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
274 streaming-first indicators across eighteen families. Every one passes the 279 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, 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 | | 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 |
| 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 274 indicators │ ├── wickra-core/ core engine + all 279 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/
@@ -265,6 +265,11 @@ const candleScalar = {
KickingByLength: { make: () => new wickra.KickingByLength(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, KickingByLength: { make: () => new wickra.KickingByLength(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
LadderBottom: { make: () => new wickra.LadderBottom(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, LadderBottom: { make: () => new wickra.LadderBottom(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
MatHold: { make: () => new wickra.MatHold(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) }, MatHold: { make: () => new wickra.MatHold(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
MatchingLow: { make: () => new wickra.MatchingLow(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
LongLine: { make: () => new wickra.LongLine(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
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) },
}; };
for (const [name, d] of Object.entries(candleScalar)) { for (const [name, d] of Object.entries(candleScalar)) {
+45
View File
@@ -2480,6 +2480,51 @@ export declare class MatHold {
isReady(): boolean isReady(): boolean
warmupPeriod(): number warmupPeriod(): number
} }
export type MatchingLowNode = MatchingLow
export declare class MatchingLow {
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 LongLineNode = LongLine
export declare class LongLine {
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 ShortLineNode = ShortLine
export declare class ShortLine {
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 RisingThreeMethodsNode = RisingThreeMethods
export declare class RisingThreeMethods {
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 FallingThreeMethodsNode = FallingThreeMethods
export declare class FallingThreeMethods {
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, 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, 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, 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
@@ -545,6 +545,11 @@ module.exports.Kicking = Kicking
module.exports.KickingByLength = KickingByLength module.exports.KickingByLength = KickingByLength
module.exports.LadderBottom = LadderBottom module.exports.LadderBottom = LadderBottom
module.exports.MatHold = MatHold module.exports.MatHold = MatHold
module.exports.MatchingLow = MatchingLow
module.exports.LongLine = LongLine
module.exports.ShortLine = ShortLine
module.exports.RisingThreeMethods = RisingThreeMethods
module.exports.FallingThreeMethods = FallingThreeMethods
module.exports.OrderBookImbalanceTop1 = OrderBookImbalanceTop1 module.exports.OrderBookImbalanceTop1 = OrderBookImbalanceTop1
module.exports.OrderBookImbalanceFull = OrderBookImbalanceFull module.exports.OrderBookImbalanceFull = OrderBookImbalanceFull
module.exports.Microprice = Microprice module.exports.Microprice = Microprice
+13
View File
@@ -8799,6 +8799,19 @@ node_candle_pattern!(KickingNode, wc::Kicking, "Kicking");
node_candle_pattern!(KickingByLengthNode, wc::KickingByLength, "KickingByLength"); node_candle_pattern!(KickingByLengthNode, wc::KickingByLength, "KickingByLength");
node_candle_pattern!(LadderBottomNode, wc::LadderBottom, "LadderBottom"); node_candle_pattern!(LadderBottomNode, wc::LadderBottom, "LadderBottom");
node_candle_pattern!(MatHoldNode, wc::MatHold, "MatHold"); node_candle_pattern!(MatHoldNode, wc::MatHold, "MatHold");
node_candle_pattern!(MatchingLowNode, wc::MatchingLow, "MatchingLow");
node_candle_pattern!(LongLineNode, wc::LongLine, "LongLine");
node_candle_pattern!(ShortLineNode, wc::ShortLine, "ShortLine");
node_candle_pattern!(
RisingThreeMethodsNode,
wc::RisingThreeMethods,
"RisingThreeMethods"
);
node_candle_pattern!(
FallingThreeMethodsNode,
wc::FallingThreeMethods,
"FallingThreeMethods"
);
// ============================== Microstructure: Order Book ============================== // ============================== Microstructure: Order Book ==============================
// //
+10
View File
@@ -270,6 +270,11 @@ from ._wickra import (
KickingByLength, KickingByLength,
LadderBottom, LadderBottom,
MatHold, MatHold,
MatchingLow,
LongLine,
ShortLine,
RisingThreeMethods,
FallingThreeMethods,
# Microstructure: order book # Microstructure: order book
OrderBookImbalanceTop1, OrderBookImbalanceTop1,
OrderBookImbalanceTopN, OrderBookImbalanceTopN,
@@ -567,6 +572,11 @@ __all__ = [
"KickingByLength", "KickingByLength",
"LadderBottom", "LadderBottom",
"MatHold", "MatHold",
"MatchingLow",
"LongLine",
"ShortLine",
"RisingThreeMethods",
"FallingThreeMethods",
# Microstructure: order book # Microstructure: order book
"OrderBookImbalanceTop1", "OrderBookImbalanceTop1",
"OrderBookImbalanceTopN", "OrderBookImbalanceTopN",
+18
View File
@@ -11662,6 +11662,19 @@ candle_pattern_no_param!(PyKicking, wc::Kicking, "Kicking");
candle_pattern_no_param!(PyKickingByLength, wc::KickingByLength, "KickingByLength"); candle_pattern_no_param!(PyKickingByLength, wc::KickingByLength, "KickingByLength");
candle_pattern_no_param!(PyLadderBottom, wc::LadderBottom, "LadderBottom"); candle_pattern_no_param!(PyLadderBottom, wc::LadderBottom, "LadderBottom");
candle_pattern_no_param!(PyMatHold, wc::MatHold, "MatHold"); candle_pattern_no_param!(PyMatHold, wc::MatHold, "MatHold");
candle_pattern_no_param!(PyMatchingLow, wc::MatchingLow, "MatchingLow");
candle_pattern_no_param!(PyLongLine, wc::LongLine, "LongLine");
candle_pattern_no_param!(PyShortLine, wc::ShortLine, "ShortLine");
candle_pattern_no_param!(
PyRisingThreeMethods,
wc::RisingThreeMethods,
"RisingThreeMethods"
);
candle_pattern_no_param!(
PyFallingThreeMethods,
wc::FallingThreeMethods,
"FallingThreeMethods"
);
// ============================== 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
@@ -14188,6 +14201,11 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyKickingByLength>()?; m.add_class::<PyKickingByLength>()?;
m.add_class::<PyLadderBottom>()?; m.add_class::<PyLadderBottom>()?;
m.add_class::<PyMatHold>()?; m.add_class::<PyMatHold>()?;
m.add_class::<PyMatchingLow>()?;
m.add_class::<PyLongLine>()?;
m.add_class::<PyShortLine>()?;
m.add_class::<PyRisingThreeMethods>()?;
m.add_class::<PyFallingThreeMethods>()?;
// Microstructure: order book. // Microstructure: order book.
m.add_class::<PyOrderBookImbalanceTop1>()?; m.add_class::<PyOrderBookImbalanceTop1>()?;
m.add_class::<PyOrderBookImbalanceTopN>()?; m.add_class::<PyOrderBookImbalanceTopN>()?;
@@ -643,6 +643,26 @@ CANDLE_SCALAR = {
lambda: ta.MatHold(), lambda: ta.MatHold(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c), lambda ind, h, l, c, v: ind.batch(c, h, l, c),
), ),
"MatchingLow": (
lambda: ta.MatchingLow(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"LongLine": (
lambda: ta.LongLine(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"ShortLine": (
lambda: ta.ShortLine(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"RisingThreeMethods": (
lambda: ta.RisingThreeMethods(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"FallingThreeMethods": (
lambda: ta.FallingThreeMethods(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
} }
@@ -1891,6 +1911,46 @@ def test_mat_hold_reference():
assert t.update((15.0, 15.1, 14.4, 14.5, 1.0, 3)) == pytest.approx(0.0) assert t.update((15.0, 15.1, 14.4, 14.5, 1.0, 3)) == pytest.approx(0.0)
assert t.update((14.5, 17.1, 14.4, 17.0, 1.0, 4)) == pytest.approx(1.0) assert t.update((14.5, 17.1, 14.4, 17.0, 1.0, 4)) == pytest.approx(1.0)
def test_matching_low_reference():
t = ta.MatchingLow()
assert t.update((15.0, 15.1, 9.9, 10.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((13.0, 13.1, 9.9, 10.0, 1.0, 1)) == pytest.approx(1.0)
def test_long_line_reference():
t = ta.LongLine()
# Five quiet bars fill the rolling range average, then a wide solid white bar.
for ts in range(5):
assert t.update((10.0, 10.5, 9.5, 10.2, 1.0, ts)) == pytest.approx(0.0)
assert t.update((10.0, 13.0, 9.9, 12.9, 1.0, 5)) == pytest.approx(1.0)
def test_short_line_reference():
t = ta.ShortLine()
# Five wide bars fill the rolling range average, then a compact solid white bar.
for ts in range(5):
assert t.update((10.0, 13.0, 9.5, 12.9, 1.0, ts)) == pytest.approx(0.0)
assert t.update((10.0, 11.0, 9.9, 10.9, 1.0, 5)) == pytest.approx(1.0)
def test_rising_three_methods_reference():
t = ta.RisingThreeMethods()
assert t.update((10.0, 15.1, 9.9, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((14.0, 14.1, 12.9, 13.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((13.5, 13.6, 12.4, 12.5, 1.0, 2)) == pytest.approx(0.0)
assert t.update((13.0, 13.1, 11.9, 12.0, 1.0, 3)) == pytest.approx(0.0)
assert t.update((12.5, 16.1, 12.4, 16.0, 1.0, 4)) == pytest.approx(1.0)
def test_falling_three_methods_reference():
t = ta.FallingThreeMethods()
assert t.update((15.0, 15.1, 9.9, 10.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((11.0, 12.1, 10.9, 12.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((11.5, 12.6, 11.4, 12.5, 1.0, 2)) == pytest.approx(0.0)
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)
# --- Lifecycle ------------------------------------------------------------ # --- Lifecycle ------------------------------------------------------------
+13
View File
@@ -6376,6 +6376,19 @@ wasm_candle_pattern!(WasmKicking, wc::Kicking, Kicking);
wasm_candle_pattern!(WasmKickingByLength, wc::KickingByLength, KickingByLength); wasm_candle_pattern!(WasmKickingByLength, wc::KickingByLength, KickingByLength);
wasm_candle_pattern!(WasmLadderBottom, wc::LadderBottom, LadderBottom); wasm_candle_pattern!(WasmLadderBottom, wc::LadderBottom, LadderBottom);
wasm_candle_pattern!(WasmMatHold, wc::MatHold, MatHold); wasm_candle_pattern!(WasmMatHold, wc::MatHold, MatHold);
wasm_candle_pattern!(WasmMatchingLow, wc::MatchingLow, MatchingLow);
wasm_candle_pattern!(WasmLongLine, wc::LongLine, LongLine);
wasm_candle_pattern!(WasmShortLine, wc::ShortLine, ShortLine);
wasm_candle_pattern!(
WasmRisingThreeMethods,
wc::RisingThreeMethods,
RisingThreeMethods
);
wasm_candle_pattern!(
WasmFallingThreeMethods,
wc::FallingThreeMethods,
FallingThreeMethods
);
// ============================== Microstructure: Order Book ============================== // ============================== Microstructure: Order Book ==============================
// //
@@ -0,0 +1,213 @@
//! Falling Three Methods candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Falling Three Methods — a 5-bar bearish continuation. A long black candle is
/// followed by three small bars that drift up but stay inside its range (a brief
/// rest), then a second long black candle closes below the first, resuming the
/// decline.
///
/// ```text
/// long body = |close open| >= 0.5 * (high low)
/// bar1 black & long
/// bar2, bar3, bar4 small bodies, each contained within bar1's high/low range
/// bar5 black, closing below bar1's close
/// ```
///
/// Output is `1.0` when the pattern completes and `0.0` otherwise. Falling Three
/// Methods is a single-direction (bearish-only) continuation, so it never emits
/// `+1.0`. The first four bars always return `0.0` because the five-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, FallingThreeMethods, Indicator};
///
/// let mut indicator = FallingThreeMethods::new();
/// indicator.update(Candle::new(15.0, 15.1, 9.9, 10.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(11.0, 12.1, 10.9, 12.0, 1.0, 1).unwrap());
/// indicator.update(Candle::new(11.5, 12.6, 11.4, 12.5, 1.0, 2).unwrap());
/// indicator.update(Candle::new(12.0, 13.1, 11.9, 13.0, 1.0, 3).unwrap());
/// let out = indicator
/// .update(Candle::new(12.5, 12.6, 8.9, 9.0, 1.0, 4).unwrap());
/// assert_eq!(out, Some(-1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct FallingThreeMethods {
c1: Option<Candle>,
c2: Option<Candle>,
c3: Option<Candle>,
c4: Option<Candle>,
has_emitted: bool,
}
impl FallingThreeMethods {
/// Construct a new Falling Three Methods detector.
pub const fn new() -> Self {
Self {
c1: None,
c2: None,
c3: None,
c4: None,
has_emitted: false,
}
}
}
impl Indicator for FallingThreeMethods {
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);
};
let range1 = bar1.high - bar1.low;
if range1 <= 0.0 {
return Some(0.0);
}
let body1 = bar1.open - bar1.close;
if body1 < 0.5 * range1 {
return Some(0.0); // bar1 must be a long black body
}
// The three middle bars stay within bar1's range with smaller bodies.
for mid in [bar2, bar3, bar4] {
if (mid.close - mid.open).abs() >= body1 || mid.high > bar1.high || mid.low < bar1.low {
return Some(0.0);
}
}
// bar5 is a black candle closing below bar1's close.
if candle.close < candle.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 {
"FallingThreeMethods"
}
}
#[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 = FallingThreeMethods::new();
assert_eq!(t.name(), "FallingThreeMethods");
assert_eq!(t.warmup_period(), 5);
assert!(!t.is_ready());
}
#[test]
fn falling_three_methods_is_minus_one() {
let mut t = FallingThreeMethods::new();
assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
assert_eq!(t.update(c(11.0, 12.1, 10.9, 12.0, 1)), Some(0.0));
assert_eq!(t.update(c(11.5, 12.6, 11.4, 12.5, 2)), Some(0.0));
assert_eq!(t.update(c(12.0, 13.1, 11.9, 13.0, 3)), Some(0.0));
assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 4)), Some(-1.0));
}
#[test]
fn middle_bar_breaks_range_yields_zero() {
let mut t = FallingThreeMethods::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
t.update(c(11.0, 12.1, 10.9, 12.0, 1));
// bar3 pokes below bar1's low.
t.update(c(11.5, 12.6, 9.0, 12.5, 2));
t.update(c(12.0, 13.1, 11.9, 13.0, 3));
assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 4)), Some(0.0));
}
#[test]
fn bar5_not_new_low_yields_zero() {
let mut t = FallingThreeMethods::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
t.update(c(11.0, 12.1, 10.9, 12.0, 1));
t.update(c(11.5, 12.6, 11.4, 12.5, 2));
t.update(c(12.0, 13.1, 11.9, 13.0, 3));
// bar5 black but closes above bar1's close.
assert_eq!(t.update(c(12.5, 12.6, 10.4, 10.5, 4)), Some(0.0));
}
#[test]
fn first_four_bars_return_zero() {
let mut t = FallingThreeMethods::new();
assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
assert_eq!(t.update(c(11.0, 12.1, 10.9, 12.0, 1)), Some(0.0));
assert_eq!(t.update(c(11.5, 12.6, 11.4, 12.5, 2)), Some(0.0));
assert_eq!(t.update(c(12.0, 13.1, 11.9, 13.0, 3)), 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 + 5.0, base + 5.1, base - 0.1, base, i)
})
.collect();
let mut a = FallingThreeMethods::new();
let mut b = FallingThreeMethods::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = FallingThreeMethods::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
t.update(c(11.0, 12.1, 10.9, 12.0, 1));
t.update(c(11.5, 12.6, 11.4, 12.5, 2));
t.update(c(12.0, 13.1, 11.9, 13.0, 3));
t.update(c(12.5, 12.6, 8.9, 9.0, 4));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
}
}
@@ -0,0 +1,229 @@
//! Long Line candlestick pattern.
use crate::error::{Error, Result};
use crate::ohlcv::Candle;
use crate::traits::Indicator;
use std::collections::VecDeque;
/// Long Line — a single candle whose range is *longer* than the recent average and
/// whose body dominates that range (a solid directional bar). Because "long" only
/// has meaning relative to recent activity, the detector compares each candle's
/// range against a rolling average of the previous `period` ranges.
///
/// ```text
/// avg = mean range of the previous `period` candles
/// long line = range > avg AND |close open| >= 0.5 * range
/// white -> +1.0, black -> 1.0
/// ```
///
/// Output is `+1.0` (long white line), `1.0` (long black line), or `0.0`
/// otherwise. The first `period` candles return `0.0` while the rolling average
/// fills. `period` defaults to `5` and must be at least `1`. This rolling baseline
/// is the one place the family departs from a purely intra-candle rule, since a
/// short/long classification is inherently scale-relative. 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 as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, LongLine};
///
/// let mut indicator = LongLine::new();
/// // Five quiet bars fill the rolling average.
/// for ts in 0..5 {
/// indicator.update(Candle::new(10.0, 10.5, 9.5, 10.2, 1.0, ts).unwrap());
/// }
/// // A wide solid white bar is a long white line.
/// let out = indicator
/// .update(Candle::new(10.0, 13.0, 9.9, 12.9, 1.0, 5).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone)]
pub struct LongLine {
period: usize,
ranges: VecDeque<f64>,
}
impl Default for LongLine {
fn default() -> Self {
Self::new()
}
}
impl LongLine {
/// Construct a Long Line detector with the default 5-candle rolling average.
pub const fn new() -> Self {
Self {
period: 5,
ranges: VecDeque::new(),
}
}
/// Construct a Long Line detector with a custom averaging period.
///
/// `period` must be at least `1`.
pub fn with_period(period: usize) -> Result<Self> {
if period == 0 {
return Err(Error::PeriodZero);
}
Ok(Self {
period,
ranges: VecDeque::new(),
})
}
/// Configured averaging period.
pub fn period(&self) -> usize {
self.period
}
}
impl Indicator for LongLine {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
let range = candle.high - candle.low;
let body = candle.close - candle.open;
if self.ranges.len() < self.period {
self.ranges.push_back(range);
return Some(0.0);
}
let avg = self.ranges.iter().sum::<f64>() / self.period as f64;
self.ranges.push_back(range);
self.ranges.pop_front();
if range > avg && body.abs() >= 0.5 * range {
return Some(if body > 0.0 { 1.0 } else { -1.0 });
}
Some(0.0)
}
fn reset(&mut self) {
self.ranges.clear();
}
fn warmup_period(&self) -> usize {
self.period
}
fn is_ready(&self) -> bool {
self.ranges.len() >= self.period
}
fn name(&self) -> &'static str {
"LongLine"
}
}
#[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()
}
fn warm(t: &mut LongLine) {
for ts in 0..5 {
assert_eq!(t.update(c(10.0, 10.5, 9.5, 10.2, ts)), Some(0.0));
}
}
#[test]
fn rejects_zero_period() {
assert!(LongLine::with_period(0).is_err());
}
#[test]
fn accepts_valid_period() {
let t = LongLine::with_period(10).unwrap();
assert_eq!(t.period(), 10);
}
#[test]
fn accessors_and_metadata() {
let t = LongLine::new();
assert_eq!(t.name(), "LongLine");
assert_eq!(t.warmup_period(), 5);
assert!(!t.is_ready());
assert_eq!(t.period(), 5);
}
#[test]
fn long_white_line_is_plus_one() {
let mut t = LongLine::new();
warm(&mut t);
assert!(t.is_ready());
assert_eq!(t.update(c(10.0, 13.0, 9.9, 12.9, 5)), Some(1.0));
}
#[test]
fn long_black_line_is_minus_one() {
let mut t = LongLine::new();
warm(&mut t);
assert_eq!(t.update(c(13.0, 13.1, 9.9, 10.0, 5)), Some(-1.0));
}
#[test]
fn short_range_yields_zero() {
let mut t = LongLine::new();
warm(&mut t);
// Range no bigger than the average -> not a long line.
assert_eq!(t.update(c(10.0, 10.5, 9.5, 10.2, 5)), Some(0.0));
}
#[test]
fn wide_range_small_body_yields_zero() {
let mut t = LongLine::new();
warm(&mut t);
// Wide range but a tiny body -> a spinning top, not a long line.
assert_eq!(t.update(c(10.5, 13.0, 9.9, 10.6, 5)), Some(0.0));
}
#[test]
fn warmup_returns_zero() {
let mut t = LongLine::new();
for ts in 0..5 {
assert_eq!(t.update(c(10.0, 13.0, 9.9, 12.9, ts)), Some(0.0));
}
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = 100.0 + i as f64;
if i % 7 == 0 {
c(base, base + 4.0, base - 0.1, base + 3.9, i)
} else {
c(base, base + 0.5, base - 0.5, base + 0.2, i)
}
})
.collect();
let mut a = LongLine::new();
let mut b = LongLine::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = LongLine::new();
warm(&mut t);
t.update(c(10.0, 13.0, 9.9, 12.9, 5));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(10.0, 13.0, 9.9, 12.9, 0)), Some(0.0));
}
}
@@ -0,0 +1,166 @@
//! Matching Low candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Matching Low — a 2-bar bullish reversal. Two black candles in a decline close
/// at the *same* level: the second sell-off cannot push price any lower, so the
/// matching closes mark a support floor.
///
/// ```text
/// bar1, bar2 both black
/// equal closes = |close2 close1| <= 0.05 · mean(range1, range2)
/// ```
///
/// Output is `+1.0` when the pattern completes and `0.0` otherwise. Matching Low
/// is a single-direction (bullish-only) reversal, so it never emits `1.0`. The
/// first bar always returns `0.0` because the two-bar window is not yet filled.
/// The close-equality tolerance follows the geometric house style rather than
/// TA-Lib's rolling averages. Pattern-shape check only — no trend filter is
/// applied; combine with a trend indicator for actionable signals.
///
/// # Signed ±1 encoding
///
/// This detector emits the uniform candlestick sign convention shared across the
/// pattern family — `+1.0` bullish, `0.0` no pattern — so it drops straight into
/// a machine-learning feature matrix as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, MatchingLow};
///
/// let mut indicator = MatchingLow::new();
/// indicator.update(Candle::new(15.0, 15.1, 9.9, 10.0, 1.0, 0).unwrap());
/// let out = indicator
/// .update(Candle::new(13.0, 13.1, 9.9, 10.0, 1.0, 1).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct MatchingLow {
prev: Option<Candle>,
has_emitted: bool,
}
impl MatchingLow {
/// Construct a new Matching Low detector.
pub const fn new() -> Self {
Self {
prev: None,
has_emitted: false,
}
}
}
impl Indicator for MatchingLow {
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 mean_range = 0.5 * ((bar1.high - bar1.low) + (candle.high - candle.low));
let tol = 0.05 * mean_range;
if bar1.close < bar1.open
&& candle.close < candle.open
&& (candle.close - bar1.close).abs() <= tol
{
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 {
"MatchingLow"
}
}
#[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 = MatchingLow::new();
assert_eq!(t.name(), "MatchingLow");
assert_eq!(t.warmup_period(), 2);
assert!(!t.is_ready());
}
#[test]
fn matching_low_is_plus_one() {
let mut t = MatchingLow::new();
assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
assert_eq!(t.update(c(13.0, 13.1, 9.9, 10.0, 1)), Some(1.0));
}
#[test]
fn different_close_yields_zero() {
let mut t = MatchingLow::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
// Second close well away from the first.
assert_eq!(t.update(c(13.0, 13.1, 11.4, 11.5, 1)), Some(0.0));
}
#[test]
fn second_bar_white_yields_zero() {
let mut t = MatchingLow::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
assert_eq!(t.update(c(9.0, 10.1, 8.9, 10.0, 1)), Some(0.0));
}
#[test]
fn first_bar_returns_zero() {
let mut t = MatchingLow::new();
assert_eq!(t.update(c(15.0, 15.1, 9.9, 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 + 2.0, base + 2.1, base - 0.1, base, i)
})
.collect();
let mut a = MatchingLow::new();
let mut b = MatchingLow::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = MatchingLow::new();
t.update(c(15.0, 15.1, 9.9, 10.0, 0));
t.update(c(13.0, 13.1, 9.9, 10.0, 1));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), Some(0.0));
}
}
+16 -1
View File
@@ -79,6 +79,7 @@ mod empirical_mode_decomposition;
mod engulfing; mod engulfing;
mod evening_doji_star; mod evening_doji_star;
mod evwma; mod evwma;
mod falling_three_methods;
mod fama; mod fama;
mod fibonacci_pivots; mod fibonacci_pivots;
mod fisher_transform; mod fisher_transform;
@@ -136,6 +137,7 @@ mod linreg_channel;
mod linreg_slope; mod linreg_slope;
mod liquidation_features; mod liquidation_features;
mod long_legged_doji; mod long_legged_doji;
mod long_line;
mod long_short_ratio; mod long_short_ratio;
mod ma_envelope; mod ma_envelope;
mod macd; mod macd;
@@ -144,6 +146,7 @@ mod market_facilitation_index;
mod marubozu; mod marubozu;
mod mass_index; mod mass_index;
mod mat_hold; mod mat_hold;
mod matching_low;
mod max_drawdown; mod max_drawdown;
mod mcginley_dynamic; mod mcginley_dynamic;
mod median_absolute_deviation; mod median_absolute_deviation;
@@ -186,6 +189,7 @@ mod recovery_factor;
mod relative_strength_ab; mod relative_strength_ab;
mod renko_trailing_stop; mod renko_trailing_stop;
mod rickshaw_man; mod rickshaw_man;
mod rising_three_methods;
mod roc; mod roc;
mod rogers_satchell; mod rogers_satchell;
mod roofing_filter; mod roofing_filter;
@@ -196,6 +200,7 @@ mod rwi;
mod separating_lines; mod separating_lines;
mod sharpe_ratio; mod sharpe_ratio;
mod shooting_star; mod shooting_star;
mod short_line;
mod signed_volume; mod signed_volume;
mod sine_wave; mod sine_wave;
mod skewness; mod skewness;
@@ -353,6 +358,7 @@ pub use empirical_mode_decomposition::EmpiricalModeDecomposition;
pub use engulfing::Engulfing; pub use engulfing::Engulfing;
pub use evening_doji_star::EveningDojiStar; pub use evening_doji_star::EveningDojiStar;
pub use evwma::Evwma; pub use evwma::Evwma;
pub use falling_three_methods::FallingThreeMethods;
pub use fama::Fama; pub use fama::Fama;
pub use fibonacci_pivots::{FibonacciPivots, FibonacciPivotsOutput}; pub use fibonacci_pivots::{FibonacciPivots, FibonacciPivotsOutput};
pub use fisher_transform::FisherTransform; pub use fisher_transform::FisherTransform;
@@ -410,6 +416,7 @@ pub use linreg_channel::{LinRegChannel, LinRegChannelOutput};
pub use linreg_slope::LinRegSlope; pub use linreg_slope::LinRegSlope;
pub use liquidation_features::{LiquidationFeatures, LiquidationFeaturesOutput}; pub use liquidation_features::{LiquidationFeatures, LiquidationFeaturesOutput};
pub use long_legged_doji::LongLeggedDoji; pub use long_legged_doji::LongLeggedDoji;
pub use long_line::LongLine;
pub use long_short_ratio::LongShortRatio; pub use long_short_ratio::LongShortRatio;
pub use ma_envelope::{MaEnvelope, MaEnvelopeOutput}; pub use ma_envelope::{MaEnvelope, MaEnvelopeOutput};
pub use macd::{MacdIndicator, MacdOutput}; pub use macd::{MacdIndicator, MacdOutput};
@@ -418,6 +425,7 @@ pub use market_facilitation_index::MarketFacilitationIndex;
pub use marubozu::Marubozu; pub use marubozu::Marubozu;
pub use mass_index::MassIndex; pub use mass_index::MassIndex;
pub use mat_hold::MatHold; pub use mat_hold::MatHold;
pub use matching_low::MatchingLow;
pub use max_drawdown::MaxDrawdown; pub use max_drawdown::MaxDrawdown;
pub use mcginley_dynamic::McGinleyDynamic; pub use mcginley_dynamic::McGinleyDynamic;
pub use median_absolute_deviation::MedianAbsoluteDeviation; pub use median_absolute_deviation::MedianAbsoluteDeviation;
@@ -460,6 +468,7 @@ pub use recovery_factor::RecoveryFactor;
pub use relative_strength_ab::{RelativeStrengthAB, RelativeStrengthOutput}; pub use relative_strength_ab::{RelativeStrengthAB, RelativeStrengthOutput};
pub use renko_trailing_stop::RenkoTrailingStop; pub use renko_trailing_stop::RenkoTrailingStop;
pub use rickshaw_man::RickshawMan; pub use rickshaw_man::RickshawMan;
pub use rising_three_methods::RisingThreeMethods;
pub use roc::Roc; pub use roc::Roc;
pub use rogers_satchell::RogersSatchellVolatility; pub use rogers_satchell::RogersSatchellVolatility;
pub use roofing_filter::RoofingFilter; pub use roofing_filter::RoofingFilter;
@@ -470,6 +479,7 @@ pub use rwi::{Rwi, RwiOutput};
pub use separating_lines::SeparatingLines; pub use separating_lines::SeparatingLines;
pub use sharpe_ratio::SharpeRatio; pub use sharpe_ratio::SharpeRatio;
pub use shooting_star::ShootingStar; pub use shooting_star::ShootingStar;
pub use short_line::ShortLine;
pub use signed_volume::SignedVolume; pub use signed_volume::SignedVolume;
pub use sine_wave::SineWave; pub use sine_wave::SineWave;
pub use skewness::Skewness; pub use skewness::Skewness;
@@ -845,6 +855,11 @@ pub const FAMILIES: &[(&str, &[&str])] = &[
"KickingByLength", "KickingByLength",
"LadderBottom", "LadderBottom",
"MatHold", "MatHold",
"MatchingLow",
"LongLine",
"ShortLine",
"RisingThreeMethods",
"FallingThreeMethods",
], ],
), ),
( (
@@ -936,6 +951,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, 269, "FAMILIES total drifted from indicator count"); assert_eq!(total, 274, "FAMILIES total drifted from indicator count");
} }
} }
@@ -0,0 +1,213 @@
//! Rising Three Methods candlestick pattern.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Rising Three Methods — a 5-bar bullish continuation. A long white candle is
/// followed by three small bars that drift back but stay inside its range (a brief
/// rest), then a second long white candle closes above the first, resuming the
/// advance.
///
/// ```text
/// long body = |close open| >= 0.5 * (high low)
/// bar1 white & long
/// bar2, bar3, bar4 small bodies, each contained within bar1's high/low range
/// bar5 white, closing above bar1's close
/// ```
///
/// Output is `+1.0` when the pattern completes and `0.0` otherwise. Rising Three
/// Methods is a single-direction (bullish-only) continuation, so it never emits
/// `1.0`. The first four bars always return `0.0` because the five-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` 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, RisingThreeMethods};
///
/// let mut indicator = RisingThreeMethods::new();
/// indicator.update(Candle::new(10.0, 15.1, 9.9, 15.0, 1.0, 0).unwrap());
/// indicator.update(Candle::new(14.0, 14.1, 12.9, 13.0, 1.0, 1).unwrap());
/// indicator.update(Candle::new(13.5, 13.6, 12.4, 12.5, 1.0, 2).unwrap());
/// indicator.update(Candle::new(13.0, 13.1, 11.9, 12.0, 1.0, 3).unwrap());
/// let out = indicator
/// .update(Candle::new(12.5, 16.1, 12.4, 16.0, 1.0, 4).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone, Default)]
pub struct RisingThreeMethods {
c1: Option<Candle>,
c2: Option<Candle>,
c3: Option<Candle>,
c4: Option<Candle>,
has_emitted: bool,
}
impl RisingThreeMethods {
/// Construct a new Rising Three Methods detector.
pub const fn new() -> Self {
Self {
c1: None,
c2: None,
c3: None,
c4: None,
has_emitted: false,
}
}
}
impl Indicator for RisingThreeMethods {
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);
};
let range1 = bar1.high - bar1.low;
if range1 <= 0.0 {
return Some(0.0);
}
let body1 = bar1.close - bar1.open;
if body1 < 0.5 * range1 {
return Some(0.0); // bar1 must be a long white body
}
// The three middle bars stay within bar1's range with smaller bodies.
for mid in [bar2, bar3, bar4] {
if (mid.close - mid.open).abs() >= body1 || mid.high > bar1.high || mid.low < bar1.low {
return Some(0.0);
}
}
// bar5 is a white candle closing above bar1's close.
if candle.close > candle.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 {
"RisingThreeMethods"
}
}
#[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 = RisingThreeMethods::new();
assert_eq!(t.name(), "RisingThreeMethods");
assert_eq!(t.warmup_period(), 5);
assert!(!t.is_ready());
}
#[test]
fn rising_three_methods_is_plus_one() {
let mut t = RisingThreeMethods::new();
assert_eq!(t.update(c(10.0, 15.1, 9.9, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(14.0, 14.1, 12.9, 13.0, 1)), Some(0.0));
assert_eq!(t.update(c(13.5, 13.6, 12.4, 12.5, 2)), Some(0.0));
assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 3)), Some(0.0));
assert_eq!(t.update(c(12.5, 16.1, 12.4, 16.0, 4)), Some(1.0));
}
#[test]
fn middle_bar_breaks_range_yields_zero() {
let mut t = RisingThreeMethods::new();
t.update(c(10.0, 15.1, 9.9, 15.0, 0));
t.update(c(14.0, 14.1, 12.9, 13.0, 1));
// bar3 pokes above bar1's high.
t.update(c(13.5, 16.0, 12.4, 12.5, 2));
t.update(c(13.0, 13.1, 11.9, 12.0, 3));
assert_eq!(t.update(c(12.5, 16.1, 12.4, 16.0, 4)), Some(0.0));
}
#[test]
fn bar5_not_new_high_yields_zero() {
let mut t = RisingThreeMethods::new();
t.update(c(10.0, 15.1, 9.9, 15.0, 0));
t.update(c(14.0, 14.1, 12.9, 13.0, 1));
t.update(c(13.5, 13.6, 12.4, 12.5, 2));
t.update(c(13.0, 13.1, 11.9, 12.0, 3));
// bar5 white but closes below bar1's close.
assert_eq!(t.update(c(12.5, 14.6, 12.4, 14.5, 4)), Some(0.0));
}
#[test]
fn first_four_bars_return_zero() {
let mut t = RisingThreeMethods::new();
assert_eq!(t.update(c(10.0, 15.1, 9.9, 15.0, 0)), Some(0.0));
assert_eq!(t.update(c(14.0, 14.1, 12.9, 13.0, 1)), Some(0.0));
assert_eq!(t.update(c(13.5, 13.6, 12.4, 12.5, 2)), Some(0.0));
assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 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 + 5.2, base - 0.1, base + 5.0, i)
})
.collect();
let mut a = RisingThreeMethods::new();
let mut b = RisingThreeMethods::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = RisingThreeMethods::new();
t.update(c(10.0, 15.1, 9.9, 15.0, 0));
t.update(c(14.0, 14.1, 12.9, 13.0, 1));
t.update(c(13.5, 13.6, 12.4, 12.5, 2));
t.update(c(13.0, 13.1, 11.9, 12.0, 3));
t.update(c(12.5, 16.1, 12.4, 16.0, 4));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(10.0, 15.1, 9.9, 15.0, 0)), Some(0.0));
}
}
@@ -0,0 +1,228 @@
//! Short Line candlestick pattern.
use crate::error::{Error, Result};
use crate::ohlcv::Candle;
use crate::traits::Indicator;
use std::collections::VecDeque;
/// Short Line — a single candle whose range is *shorter* than the recent average
/// while its body still dominates that (small) range: a compact directional bar.
/// As with [`LongLine`](crate::LongLine), "short" only has meaning relative to
/// recent activity, so the detector compares each candle's range against a rolling
/// average of the previous `period` ranges.
///
/// ```text
/// avg = mean range of the previous `period` candles
/// short line = range < avg AND |close open| >= 0.5 * range
/// white -> +1.0, black -> 1.0
/// ```
///
/// Output is `+1.0` (short white line), `1.0` (short black line), or `0.0`
/// otherwise. The first `period` candles return `0.0` while the rolling average
/// fills. `period` defaults to `5` and must be at least `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 as a single dimension.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, ShortLine};
///
/// let mut indicator = ShortLine::new();
/// // Five wide bars fill the rolling average.
/// for ts in 0..5 {
/// indicator.update(Candle::new(10.0, 13.0, 9.5, 12.9, 1.0, ts).unwrap());
/// }
/// // A compact solid white bar is a short white line.
/// let out = indicator
/// .update(Candle::new(10.0, 11.0, 9.9, 10.9, 1.0, 5).unwrap());
/// assert_eq!(out, Some(1.0));
/// ```
#[derive(Debug, Clone)]
pub struct ShortLine {
period: usize,
ranges: VecDeque<f64>,
}
impl Default for ShortLine {
fn default() -> Self {
Self::new()
}
}
impl ShortLine {
/// Construct a Short Line detector with the default 5-candle rolling average.
pub const fn new() -> Self {
Self {
period: 5,
ranges: VecDeque::new(),
}
}
/// Construct a Short Line detector with a custom averaging period.
///
/// `period` must be at least `1`.
pub fn with_period(period: usize) -> Result<Self> {
if period == 0 {
return Err(Error::PeriodZero);
}
Ok(Self {
period,
ranges: VecDeque::new(),
})
}
/// Configured averaging period.
pub fn period(&self) -> usize {
self.period
}
}
impl Indicator for ShortLine {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
let range = candle.high - candle.low;
let body = candle.close - candle.open;
if self.ranges.len() < self.period {
self.ranges.push_back(range);
return Some(0.0);
}
let avg = self.ranges.iter().sum::<f64>() / self.period as f64;
self.ranges.push_back(range);
self.ranges.pop_front();
if range < avg && body.abs() >= 0.5 * range {
return Some(if body > 0.0 { 1.0 } else { -1.0 });
}
Some(0.0)
}
fn reset(&mut self) {
self.ranges.clear();
}
fn warmup_period(&self) -> usize {
self.period
}
fn is_ready(&self) -> bool {
self.ranges.len() >= self.period
}
fn name(&self) -> &'static str {
"ShortLine"
}
}
#[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()
}
fn warm(t: &mut ShortLine) {
for ts in 0..5 {
assert_eq!(t.update(c(10.0, 13.0, 9.5, 12.9, ts)), Some(0.0));
}
}
#[test]
fn rejects_zero_period() {
assert!(ShortLine::with_period(0).is_err());
}
#[test]
fn accepts_valid_period() {
let t = ShortLine::with_period(10).unwrap();
assert_eq!(t.period(), 10);
}
#[test]
fn accessors_and_metadata() {
let t = ShortLine::new();
assert_eq!(t.name(), "ShortLine");
assert_eq!(t.warmup_period(), 5);
assert!(!t.is_ready());
assert_eq!(t.period(), 5);
}
#[test]
fn short_white_line_is_plus_one() {
let mut t = ShortLine::new();
warm(&mut t);
assert!(t.is_ready());
assert_eq!(t.update(c(10.0, 11.0, 9.9, 10.9, 5)), Some(1.0));
}
#[test]
fn short_black_line_is_minus_one() {
let mut t = ShortLine::new();
warm(&mut t);
assert_eq!(t.update(c(10.9, 11.0, 9.9, 10.0, 5)), Some(-1.0));
}
#[test]
fn wide_range_yields_zero() {
let mut t = ShortLine::new();
warm(&mut t);
// Range as wide as the average -> not a short line.
assert_eq!(t.update(c(10.0, 13.0, 9.5, 12.9, 5)), Some(0.0));
}
#[test]
fn short_range_small_body_yields_zero() {
let mut t = ShortLine::new();
warm(&mut t);
// Compact range but a tiny body -> not a solid short line.
assert_eq!(t.update(c(10.4, 11.0, 9.9, 10.5, 5)), Some(0.0));
}
#[test]
fn warmup_returns_zero() {
let mut t = ShortLine::new();
for ts in 0..5 {
assert_eq!(t.update(c(10.0, 11.0, 9.9, 10.9, ts)), Some(0.0));
}
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = 100.0 + i as f64;
if i % 7 == 0 {
c(base, base + 0.6, base - 0.1, base + 0.5, i)
} else {
c(base, base + 3.0, base - 1.0, base + 2.8, i)
}
})
.collect();
let mut a = ShortLine::new();
let mut b = ShortLine::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut t = ShortLine::new();
warm(&mut t);
t.update(c(10.0, 11.0, 9.9, 10.9, 5));
assert!(t.is_ready());
t.reset();
assert!(!t.is_ready());
assert_eq!(t.update(c(10.0, 11.0, 9.9, 10.9, 0)), Some(0.0));
}
}
+33 -32
View File
@@ -67,39 +67,40 @@ pub use indicators::{
DemarkPivotsOutput, DepthSlope, DetrendedStdDev, Doji, DojiStar, Donchian, DonchianOutput, DemarkPivotsOutput, DepthSlope, DetrendedStdDev, Doji, DojiStar, Donchian, DonchianOutput,
DonchianStop, DonchianStopOutput, DoubleBollinger, DoubleBollingerOutput, Dpo, DragonflyDoji, DonchianStop, DonchianStopOutput, DoubleBollinger, DoubleBollingerOutput, Dpo, DragonflyDoji,
DrawdownDuration, EaseOfMovement, EffectiveSpread, EhlersStochastic, ElderImpulse, Ema, DrawdownDuration, EaseOfMovement, EffectiveSpread, EhlersStochastic, ElderImpulse, Ema,
EmpiricalModeDecomposition, Engulfing, EveningDojiStar, Evwma, Fama, FibonacciPivots, EmpiricalModeDecomposition, Engulfing, EveningDojiStar, Evwma, FallingThreeMethods, Fama,
FibonacciPivotsOutput, FisherTransform, Footprint, FootprintOutput, ForceIndex, FibonacciPivots, FibonacciPivotsOutput, FisherTransform, Footprint, FootprintOutput,
FractalChaosBands, FractalChaosBandsOutput, Frama, FundingBasis, FundingRate, FundingRateMean, ForceIndex, FractalChaosBands, FractalChaosBandsOutput, Frama, FundingBasis, FundingRate,
FundingRateZScore, GainLossRatio, GapSideBySideWhite, GarmanKlassVolatility, GravestoneDoji, FundingRateMean, FundingRateZScore, GainLossRatio, GapSideBySideWhite, GarmanKlassVolatility,
Hammer, HangingMan, Harami, HeikinAshi, HeikinAshiOutput, HiLoActivator, HighWave, Hikkake, GravestoneDoji, Hammer, HangingMan, Harami, HeikinAshi, HeikinAshiOutput, HiLoActivator,
HikkakeModified, HilbertDominantCycle, HistoricalVolatility, Hma, HomingPigeon, HurstChannel, HighWave, Hikkake, HikkakeModified, HilbertDominantCycle, HistoricalVolatility, Hma,
HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput, IdenticalThreeCrows, InNeck, HomingPigeon, HurstChannel, HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput,
Inertia, InformationRatio, InitialBalance, InitialBalanceOutput, InstantaneousTrendline, IdenticalThreeCrows, InNeck, Inertia, InformationRatio, InitialBalance, InitialBalanceOutput,
InverseFisherTransform, InvertedHammer, Jma, Kama, KellyCriterion, Keltner, KeltnerOutput, InstantaneousTrendline, InverseFisherTransform, InvertedHammer, Jma, Kama, KellyCriterion,
Kicking, KickingByLength, Kst, KstOutput, Kurtosis, Kvo, KylesLambda, LadderBottom, Keltner, KeltnerOutput, Kicking, KickingByLength, Kst, KstOutput, Kurtosis, Kvo, KylesLambda,
LaguerreRsi, LeadLagCrossCorrelation, LeadLagCrossCorrelationOutput, LinRegAngle, LadderBottom, LaguerreRsi, LeadLagCrossCorrelation, LeadLagCrossCorrelationOutput, LinRegAngle,
LinRegChannel, LinRegChannelOutput, LinRegSlope, LinearRegression, LiquidationFeatures, LinRegChannel, LinRegChannelOutput, LinRegSlope, LinearRegression, LiquidationFeatures,
LiquidationFeaturesOutput, LongLeggedDoji, LongShortRatio, MaEnvelope, MaEnvelopeOutput, LiquidationFeaturesOutput, LongLeggedDoji, LongLine, LongShortRatio, MaEnvelope,
MacdIndicator, MacdOutput, Mama, MamaOutput, MarketFacilitationIndex, Marubozu, MassIndex, MaEnvelopeOutput, MacdIndicator, MacdOutput, Mama, MamaOutput, MarketFacilitationIndex,
MatHold, MaxDrawdown, McGinleyDynamic, MedianAbsoluteDeviation, MedianPrice, Mfi, Microprice, Marubozu, MassIndex, MatHold, MatchingLow, MaxDrawdown, McGinleyDynamic,
Mom, MorningDojiStar, MorningEveningStar, Natr, Nvi, OIPriceDivergence, OIWeighted, Obv, MedianAbsoluteDeviation, MedianPrice, Mfi, Microprice, Mom, MorningDojiStar,
OmegaRatio, OnNeck, OpenInterestDelta, OpeningRange, OpeningRangeOutput, MorningEveningStar, Natr, Nvi, OIPriceDivergence, OIWeighted, Obv, OmegaRatio, OnNeck,
OrderBookImbalanceFull, OrderBookImbalanceTop1, OrderBookImbalanceTopN, PainIndex, OpenInterestDelta, OpeningRange, OpeningRangeOutput, OrderBookImbalanceFull,
PairSpreadZScore, PairwiseBeta, ParkinsonVolatility, PearsonCorrelation, PercentB, OrderBookImbalanceTop1, OrderBookImbalanceTopN, PainIndex, PairSpreadZScore, PairwiseBeta,
PercentageTrailingStop, Pgo, PiercingDarkCloud, Pmo, Ppo, ProfitFactor, Psar, Pvi, ParkinsonVolatility, PearsonCorrelation, PercentB, PercentageTrailingStop, Pgo,
QuotedSpread, RSquared, RealizedSpread, RecoveryFactor, RelativeStrengthAB, PiercingDarkCloud, Pmo, Ppo, ProfitFactor, Psar, Pvi, QuotedSpread, RSquared, RealizedSpread,
RelativeStrengthOutput, RenkoTrailingStop, RickshawMan, Roc, RogersSatchellVolatility, RecoveryFactor, RelativeStrengthAB, RelativeStrengthOutput, RenkoTrailingStop, RickshawMan,
RollingVwap, RoofingFilter, Rsi, Rvi, RviVolatility, Rwi, RwiOutput, SeparatingLines, RisingThreeMethods, Roc, RogersSatchellVolatility, RollingVwap, RoofingFilter, Rsi, Rvi,
SharpeRatio, ShootingStar, SignedVolume, SineWave, Skewness, Sma, Smi, Smma, SortinoRatio, RviVolatility, Rwi, RwiOutput, SeparatingLines, SharpeRatio, ShootingStar, ShortLine,
SpearmanCorrelation, SpinningTop, StandardError, StandardErrorBands, StandardErrorBandsOutput, SignedVolume, SineWave, Skewness, Sma, Smi, Smma, SortinoRatio, SpearmanCorrelation,
StarcBands, StarcBandsOutput, Stc, StdDev, StepTrailingStop, StochRsi, Stochastic, SpinningTop, StandardError, StandardErrorBands, StandardErrorBandsOutput, StarcBands,
StochasticOutput, SuperSmoother, SuperTrend, SuperTrendOutput, TakerBuySellRatio, TdCombo, StarcBandsOutput, Stc, StdDev, StepTrailingStop, StochRsi, Stochastic, StochasticOutput,
TdCountdown, TdDeMarker, TdDifferential, TdLines, TdLinesOutput, TdOpen, TdPressure, SuperSmoother, SuperTrend, SuperTrendOutput, TakerBuySellRatio, TdCombo, TdCountdown,
TdRangeProjection, TdRangeProjectionOutput, TdRei, TdRiskLevel, TdRiskLevelOutput, TdDeMarker, TdDifferential, TdLines, TdLinesOutput, TdOpen, TdPressure, TdRangeProjection,
TdSequential, TdSequentialOutput, TdSetup, Tema, TermStructureBasis, ThreeInside, TdRangeProjectionOutput, TdRei, TdRiskLevel, TdRiskLevelOutput, TdSequential,
ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, Thrusting, Tii, TdSequentialOutput, TdSetup, Tema, TermStructureBasis, ThreeInside, ThreeLineStrike,
TradeImbalance, TreynorRatio, Trima, Trix, TrueRange, Tsi, Tsv, TtmSqueeze, TtmSqueezeOutput, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth, Thrusting, Tii, TradeImbalance,
Tweezer, TwoCrows, TypicalPrice, UlcerIndex, UltimateOscillator, UpsideGapTwoCrows, ValueArea, TreynorRatio, Trima, Trix, TrueRange, Tsi, Tsv, TtmSqueeze, TtmSqueezeOutput, Tweezer,
TwoCrows, TypicalPrice, UlcerIndex, UltimateOscillator, UpsideGapTwoCrows, ValueArea,
ValueAreaOutput, ValueAtRisk, Variance, VerticalHorizontalFilter, Vidya, VoltyStop, ValueAreaOutput, ValueAtRisk, Variance, VerticalHorizontalFilter, Vidya, VoltyStop,
VolumeOscillator, VolumePriceTrend, Vortex, VortexOutput, Vwap, VwapStdDevBands, VolumeOscillator, VolumePriceTrend, Vortex, VortexOutput, Vwap, VwapStdDevBands,
VwapStdDevBandsOutput, Vwma, Vzo, WaveTrend, WaveTrendOutput, WeightedClose, WilliamsFractals, VwapStdDevBandsOutput, Vwma, Vzo, WaveTrend, WaveTrendOutput, WeightedClose, WilliamsFractals,
+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 **274 indicators** across - A per-indicator deep dive for every one of the **279 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 -1
View File
@@ -23,7 +23,7 @@
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use wickra_core::{ 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, 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, MarketFacilitationIndex, Marubozu, MassIndex, MatHold, MedianPrice, Mfi, MorningDojiStar, MorningEveningStar, Natr, Nvi, Obv, OnNeck, OpeningRange, ParkinsonVolatility, Pgo, PiercingDarkCloud, Psar, Pvi, RickshawMan, RogersSatchellVolatility, RollingVwap, Rvi, Rwi, SeparatingLines, ShootingStar, 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, 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
}; };
/// 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
@@ -278,6 +278,11 @@ fuzz_target!(|data: Vec<f64>| {
} }
// --- Candlestick Patterns (family 14) --- // --- Candlestick Patterns (family 14) ---
drive(FallingThreeMethods::new, &candles);
drive(RisingThreeMethods::new, &candles);
drive(ShortLine::new, &candles);
drive(LongLine::new, &candles);
drive(MatchingLow::new, &candles);
drive(MatHold::new, &candles); drive(MatHold::new, &candles);
drive(LadderBottom::new, &candles); drive(LadderBottom::new, &candles);
drive(KickingByLength::new, &candles); drive(KickingByLength::new, &candles);