feat: TA-Lib candlestick patterns — doji-star/gap/high-wave/hikkake (part 4 of 9) (#135)

* feat: add doji-star, gap, high-wave and hikkake candlestick patterns

Five patterns, all `Input = Candle`, `Output = f64`:

- Evening Doji Star (CDLEVENINGDOJISTAR) — bearish top reversal: long white bar,
  a doji gapping up, then a black bar closing deep into the first body; -1
  (penetration configurable, default 0.3).
- Morning Doji Star (CDLMORNINGDOJISTAR) — bullish bottom reversal mirror; +1.
- Gap Side-by-Side White (CDLGAPSIDESIDEWHITE) — two similar white candles
  opening side by side after a gap, a continuation; gap up +1, gap down -1.
- High-Wave (CDLHIGHWAVE) — a small body with very long shadows on both sides,
  an extreme indecision flag; +1 on detection.
- Hikkake (CDLHIKKAKE) — an inside bar followed by a failed breakout (a trap);
  bullish +1, bearish -1.

Counter 259 -> 264 (mod-count == lib counted block; FAMILIES total 254 -> 259).

* chore: sync indicator count to 264

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
This commit is contained in:
kingchenc
2026-06-02 16:54:47 +02:00
committed by GitHub
parent 244d754707
commit d43bc9ddf3
19 changed files with 1280 additions and 45 deletions
+10
View File
@@ -255,6 +255,11 @@ from ._wickra import (
GravestoneDoji,
LongLeggedDoji,
RickshawMan,
EveningDojiStar,
MorningDojiStar,
GapSideBySideWhite,
HighWave,
Hikkake,
# Microstructure: order book
OrderBookImbalanceTop1,
OrderBookImbalanceTopN,
@@ -537,6 +542,11 @@ __all__ = [
"GravestoneDoji",
"LongLeggedDoji",
"RickshawMan",
"EveningDojiStar",
"MorningDojiStar",
"GapSideBySideWhite",
"HighWave",
"Hikkake",
# Microstructure: order book
"OrderBookImbalanceTop1",
"OrderBookImbalanceTopN",
+14
View File
@@ -11643,6 +11643,15 @@ candle_pattern_no_param!(PyDragonflyDoji, wc::DragonflyDoji, "DragonflyDoji");
candle_pattern_no_param!(PyGravestoneDoji, wc::GravestoneDoji, "GravestoneDoji");
candle_pattern_no_param!(PyLongLeggedDoji, wc::LongLeggedDoji, "LongLeggedDoji");
candle_pattern_no_param!(PyRickshawMan, wc::RickshawMan, "RickshawMan");
candle_pattern_no_param!(PyEveningDojiStar, wc::EveningDojiStar, "EveningDojiStar");
candle_pattern_no_param!(PyMorningDojiStar, wc::MorningDojiStar, "MorningDojiStar");
candle_pattern_no_param!(
PyGapSideBySideWhite,
wc::GapSideBySideWhite,
"GapSideBySideWhite"
);
candle_pattern_no_param!(PyHighWave, wc::HighWave, "HighWave");
candle_pattern_no_param!(PyHikkake, wc::Hikkake, "Hikkake");
// ============================== Microstructure: Order Book ==============================
//
// Order-book indicators consume a depth snapshot rather than OHLCV. Streaming
@@ -14154,6 +14163,11 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyGravestoneDoji>()?;
m.add_class::<PyLongLeggedDoji>()?;
m.add_class::<PyRickshawMan>()?;
m.add_class::<PyEveningDojiStar>()?;
m.add_class::<PyMorningDojiStar>()?;
m.add_class::<PyGapSideBySideWhite>()?;
m.add_class::<PyHighWave>()?;
m.add_class::<PyHikkake>()?;
// Microstructure: order book.
m.add_class::<PyOrderBookImbalanceTop1>()?;
m.add_class::<PyOrderBookImbalanceTopN>()?;
@@ -583,6 +583,26 @@ CANDLE_SCALAR = {
lambda: ta.RickshawMan(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"EveningDojiStar": (
lambda: ta.EveningDojiStar(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"MorningDojiStar": (
lambda: ta.MorningDojiStar(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"GapSideBySideWhite": (
lambda: ta.GapSideBySideWhite(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"HighWave": (
lambda: ta.HighWave(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"Hikkake": (
lambda: ta.Hikkake(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
}
@@ -1731,6 +1751,39 @@ def test_rickshaw_man_reference():
t = ta.RickshawMan()
assert t.update((10.0, 12.0, 8.0, 10.0, 1.0, 0)) == pytest.approx(1.0)
def test_evening_doji_star_reference():
t = ta.EveningDojiStar()
assert t.update((10.0, 15.1, 9.9, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((17.0, 17.1, 16.9, 17.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((16.0, 16.1, 11.9, 12.0, 1.0, 2)) == pytest.approx(-1.0)
def test_morning_doji_star_reference():
t = ta.MorningDojiStar()
assert t.update((15.0, 15.1, 9.9, 10.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((8.0, 8.1, 7.9, 8.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((9.0, 13.1, 8.9, 13.0, 1.0, 2)) == pytest.approx(1.0)
def test_gap_side_by_side_white_reference():
t = ta.GapSideBySideWhite()
assert t.update((10.0, 11.1, 9.9, 11.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((13.0, 14.1, 12.9, 14.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((13.0, 14.1, 12.9, 14.0, 1.0, 2)) == pytest.approx(1.0)
def test_high_wave_reference():
t = ta.HighWave()
assert t.update((10.0, 12.0, 8.0, 10.3, 1.0, 0)) == pytest.approx(1.0)
def test_hikkake_reference():
t = ta.Hikkake()
assert t.update((10.0, 15.0, 5.0, 12.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((11.0, 13.0, 8.0, 12.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((9.0, 12.0, 6.0, 7.0, 1.0, 2)) == pytest.approx(1.0)
# --- Lifecycle ------------------------------------------------------------