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

* feat: add Abandoned Baby candlestick pattern (CDLABANDONEDBABY)

* feat: add Advance Block candlestick pattern (CDLADVANCEBLOCK)

* feat: add Belt Hold candlestick pattern (CDLBELTHOLD)

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

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

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

* chore: sync indicator count to 254

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
This commit is contained in:
kingchenc
2026-06-02 16:34:15 +02:00
committed by GitHub
parent 73415cd2dc
commit 03ceac1f3b
19 changed files with 1343 additions and 41 deletions
+10
View File
@@ -245,6 +245,11 @@ from ._wickra import (
IdenticalThreeCrows,
ThreeLineStrike,
ThreeStarsInSouth,
AbandonedBaby,
AdvanceBlock,
BeltHold,
Breakaway,
Counterattack,
# Microstructure: order book
OrderBookImbalanceTop1,
OrderBookImbalanceTopN,
@@ -517,6 +522,11 @@ __all__ = [
"IdenticalThreeCrows",
"ThreeLineStrike",
"ThreeStarsInSouth",
"AbandonedBaby",
"AdvanceBlock",
"BeltHold",
"Breakaway",
"Counterattack",
# Microstructure: order book
"OrderBookImbalanceTop1",
"OrderBookImbalanceTopN",
+10
View File
@@ -11632,7 +11632,12 @@ candle_pattern_no_param!(
wc::ThreeStarsInSouth,
"ThreeStarsInSouth"
);
candle_pattern_no_param!(PyAbandonedBaby, wc::AbandonedBaby, "AbandonedBaby");
candle_pattern_no_param!(PyAdvanceBlock, wc::AdvanceBlock, "AdvanceBlock");
candle_pattern_no_param!(PyBeltHold, wc::BeltHold, "BeltHold");
candle_pattern_no_param!(PyBreakaway, wc::Breakaway, "Breakaway");
candle_pattern_no_param!(PyCounterattack, wc::Counterattack, "Counterattack");
// ============================== Microstructure: Order Book ==============================
//
// Order-book indicators consume a depth snapshot rather than OHLCV. Streaming
@@ -14134,6 +14139,11 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyIdenticalThreeCrows>()?;
m.add_class::<PyThreeLineStrike>()?;
m.add_class::<PyThreeStarsInSouth>()?;
m.add_class::<PyAbandonedBaby>()?;
m.add_class::<PyAdvanceBlock>()?;
m.add_class::<PyBeltHold>()?;
m.add_class::<PyBreakaway>()?;
m.add_class::<PyCounterattack>()?;
// Microstructure: order book.
m.add_class::<PyOrderBookImbalanceTop1>()?;
m.add_class::<PyOrderBookImbalanceTopN>()?;
@@ -543,6 +543,26 @@ CANDLE_SCALAR = {
lambda: ta.ThreeStarsInSouth(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"AbandonedBaby": (
lambda: ta.AbandonedBaby(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"AdvanceBlock": (
lambda: ta.AdvanceBlock(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"BeltHold": (
lambda: ta.BeltHold(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"Breakaway": (
lambda: ta.Breakaway(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"Counterattack": (
lambda: ta.Counterattack(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
}
@@ -1644,6 +1664,27 @@ def test_fractal_chaos_bands_detects_peak_and_trough():
assert out[5, 1] == pytest.approx(0.5)
def test_belt_hold_reference():
t = ta.BeltHold()
assert t.update((10.0, 12.0, 10.0, 11.5, 1.0, 0)) == pytest.approx(1.0)
assert t.update((12.0, 12.0, 10.0, 10.5, 1.0, 1)) == pytest.approx(-1.0)
def test_breakaway_reference():
t = ta.Breakaway()
assert t.update((20.0, 20.2, 14.8, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((14.0, 14.1, 11.9, 12.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((12.5, 13.0, 10.5, 11.0, 1.0, 2)) == pytest.approx(0.0)
assert t.update((11.0, 11.5, 9.0, 9.5, 1.0, 3)) == pytest.approx(0.0)
assert t.update((9.5, 14.7, 9.4, 14.5, 1.0, 4)) == pytest.approx(1.0)
def test_counterattack_reference():
t = ta.Counterattack()
assert t.update((20.0, 20.1, 14.9, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((10.0, 15.1, 9.9, 15.0, 1.0, 1)) == pytest.approx(1.0)
# --- Lifecycle ------------------------------------------------------------
@@ -1907,6 +1948,20 @@ def test_three_stars_in_south_reference():
assert t.update((15.0, 15.0, 14.0, 14.0, 1.0, 2)) == pytest.approx(1.0)
def test_abandoned_baby_reference():
t = ta.AbandonedBaby()
assert t.update((20.0, 20.1, 14.9, 15.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((13.0, 13.1, 12.9, 13.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((16.0, 18.1, 15.9, 18.0, 1.0, 2)) == pytest.approx(1.0)
def test_advance_block_reference():
t = ta.AdvanceBlock()
assert t.update((10.0, 13.1, 9.9, 13.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((12.0, 14.3, 11.9, 14.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((13.5, 15.0, 13.4, 14.5, 1.0, 2)) == pytest.approx(-1.0)
# --- Lifecycle ------------------------------------------------------------