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:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
[](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)) {
|
||||||
|
|||||||
Vendored
+45
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ==============================
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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 &
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user