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