> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clypt.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Candlestick Patterns

> 48+ CDL pattern recognition operators

## Overview

This page documents **60 operators** (role: `INDICATOR`).

## Quick Reference

| Operator                | Role        | Key Parameters | Ephemeral |
| ----------------------- | ----------- | -------------- | --------- |
| **CDLDOJI**             | `INDICATOR` | —              | No        |
| **CDLHAMMER**           | `INDICATOR` | —              | No        |
| **CDLHANGINGMAN**       | `INDICATOR` | —              | No        |
| **CDLINVERTEDHAMMER**   | `INDICATOR` | —              | No        |
| **CDLSHOOTINGSTAR**     | `INDICATOR` | —              | No        |
| **CDLENGULFING**        | `INDICATOR` | —              | No        |
| **CDLHARAMI**           | `INDICATOR` | —              | No        |
| **CDLMORNINGSTAR**      | `INDICATOR` | —              | No        |
| **CDLEVENINGSTAR**      | `INDICATOR` | —              | No        |
| **CDLMARUBOZU**         | `INDICATOR` | —              | No        |
| **CDL3WHITESOLDIERS**   | `INDICATOR` | —              | No        |
| **CDL3BLACKCROWS**      | `INDICATOR` | —              | No        |
| **CDL2CROWS**           | `INDICATOR` | —              | No        |
| **CDL3INSIDE**          | `INDICATOR` | —              | No        |
| **CDL3LINESTRIKE**      | `INDICATOR` | —              | No        |
| **CDL3STARSINSOUTH**    | `INDICATOR` | —              | No        |
| **CDLABANDONEDBABY**    | `INDICATOR` | —              | No        |
| **CDLADVANCEBLOCK**     | `INDICATOR` | —              | No        |
| **CDLBELTHOLD**         | `INDICATOR` | —              | No        |
| **CDLBREAKAWAY**        | `INDICATOR` | —              | No        |
| **CDLCLOSINGMARUBOZU**  | `INDICATOR` | —              | No        |
| **CDLCONCEALBABYSWALL** | `INDICATOR` | —              | No        |
| **CDLCOUNTERATTACK**    | `INDICATOR` | —              | No        |
| **CDLDARKCLOUDCOVER**   | `INDICATOR` | —              | No        |
| **CDLDOJISTAR**         | `INDICATOR` | —              | No        |
| **CDLDRAGONFLYDOJI**    | `INDICATOR` | —              | No        |
| **CDLEVENINGDOJISTAR**  | `INDICATOR` | —              | No        |
| **CDLGAPSIDESIDEWHITE** | `INDICATOR` | —              | No        |
| **CDLGRAVESTONEDOJI**   | `INDICATOR` | —              | No        |
| **CDLHARAMICROSS**      | `INDICATOR` | —              | No        |
| **CDLHIGHWAVE**         | `INDICATOR` | —              | No        |
| **CDLHIKKAKE**          | `INDICATOR` | —              | No        |
| **CDLHIKKAKEMOD**       | `INDICATOR` | —              | No        |
| **CDLHOMINGPIGEON**     | `INDICATOR` | —              | No        |
| **CDLIDENTICAL3CROWS**  | `INDICATOR` | —              | No        |
| **CDLINNECK**           | `INDICATOR` | —              | No        |
| **CDLKICKING**          | `INDICATOR` | —              | No        |
| **CDLKICKINGBYLENGTH**  | `INDICATOR` | —              | No        |
| **CDLLADDERBOTTOM**     | `INDICATOR` | —              | No        |
| **CDLLONGLEGGEDDOJI**   | `INDICATOR` | —              | No        |
| **CDLLONGLINE**         | `INDICATOR` | —              | No        |
| **CDLMATCHINGLOW**      | `INDICATOR` | —              | No        |
| **CDLMATHOLD**          | `INDICATOR` | —              | No        |
| **CDLMORNINGDOJISTAR**  | `INDICATOR` | —              | No        |
| **CDLONNECK**           | `INDICATOR` | —              | No        |
| **CDLPIERCING**         | `INDICATOR` | —              | No        |
| **CDLRICKSHAWMAN**      | `INDICATOR` | —              | No        |
| **CDLRISEFALL3METHODS** | `INDICATOR` | —              | No        |
| **CDLSEPARATINGLINES**  | `INDICATOR` | —              | No        |
| **CDLSHORTLINE**        | `INDICATOR` | —              | No        |
| **CDLSPINNINGTOP**      | `INDICATOR` | —              | No        |
| **CDLSTALLEDPATTERN**   | `INDICATOR` | —              | No        |
| **CDLSTICKSANDWICH**    | `INDICATOR` | —              | No        |
| **CDLTAKURI**           | `INDICATOR` | —              | No        |
| **CDLTASUKIGAP**        | `INDICATOR` | —              | No        |
| **CDLTHRUSTING**        | `INDICATOR` | —              | No        |
| **CDLTRISTAR**          | `INDICATOR` | —              | No        |
| **CDLUNIQUE3RIVER**     | `INDICATOR` | —              | No        |
| **CDLUPSIDEGAP2CROWS**  | `INDICATOR` | —              | No        |
| **CDLXSIDEGAP3METHODS** | `INDICATOR` | —              | No        |

***

## CDLDOJI

Doji pattern - Open and close are nearly equal.

Example:
pattern = CDLDOJI(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLDOJI(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            if range_size > 0 and body / range_size < 0.1:  # Very small body
                result[i] = 100.0  # Neutral but detected

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLHAMMER

Hammer pattern - Bullish reversal at bottom.

Example:
pattern = CDLHAMMER(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLHAMMER(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            lower_shadow = self._lower_shadow(o[i], l[i], c[i])
            upper_shadow = self._upper_shadow(o[i], h[i], c[i])

            if body > 0:
                # Hammer: long lower shadow, small upper shadow
                if lower_shadow >= 2 * body and upper_shadow <= body * 0.3:
                    result[i] = 100.0  # Bullish

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLHANGINGMAN

Hanging Man pattern - Bearish reversal at top (same shape as hammer).

Example:
pattern = CDLHANGINGMAN(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLHANGINGMAN(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            lower_shadow = self._lower_shadow(o[i], l[i], c[i])
            upper_shadow = self._upper_shadow(o[i], h[i], c[i])

            if body > 0:
                if lower_shadow >= 2 * body and upper_shadow <= body * 0.3:
                    result[i] = -100.0  # Bearish

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLINVERTEDHAMMER

Inverted Hammer pattern - Bullish reversal.

Example:
pattern = CDLINVERTEDHAMMER(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLINVERTEDHAMMER(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            upper_shadow = self._upper_shadow(o[i], h[i], c[i])
            lower_shadow = self._lower_shadow(o[i], l[i], c[i])

            if body > 0:
                if upper_shadow >= 2 * body and lower_shadow <= body * 0.3:
                    result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLSHOOTINGSTAR

Shooting Star pattern - Bearish reversal.

Example:
pattern = CDLSHOOTINGSTAR(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLSHOOTINGSTAR(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            upper_shadow = self._upper_shadow(o[i], h[i], c[i])
            lower_shadow = self._lower_shadow(o[i], l[i], c[i])

            if body > 0:
                if upper_shadow >= 2 * body and lower_shadow <= body * 0.3:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLENGULFING

Engulfing Pattern - Strong reversal signal.

Example:
pattern = CDLENGULFING(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLENGULFING(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)  # Previous candle
    o2, h2, l2, c2 = self._get_ohlc(data, -1)  # Current candle

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Bullish engulfing: prev bearish, current bullish that engulfs
            if self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]):
                if o2[i] <= c1[i] and c2[i] >= o1[i]:
                    result[i] = 100.0

            # Bearish engulfing: prev bullish, current bearish that engulfs
            elif self._is_bullish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]):
                if o2[i] >= c1[i] and c2[i] <= o1[i]:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLHARAMI

Harami Pattern - Potential reversal.

Example:
pattern = CDLHARAMI(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLHARAMI(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Bullish harami: prev bearish, current small bullish inside
            if self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]):
                if o2[i] >= c1[i] and c2[i] <= o1[i]:
                    result[i] = 100.0

            # Bearish harami: prev bullish, current small bearish inside
            elif self._is_bullish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]):
                if o2[i] <= c1[i] and c2[i] >= o1[i]:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLMORNINGSTAR

Morning Star - Bullish three-candle reversal.

Example:
pattern = CDLMORNINGSTAR(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLMORNINGSTAR(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue

            body1 = self._body_size(o1[i], c1[i])
            body2 = self._body_size(o2[i], c2[i])
            body3 = self._body_size(o3[i], c3[i])

            # First: long bearish, Second: small body (star), Third: long bullish
            if (self._is_bearish(o1[i], c1[i]) and
                body2 < body1 * 0.3 and
                self._is_bullish(o3[i], c3[i]) and
                c3[i] > (o1[i] + c1[i]) / 2):  # Closes above midpoint of first
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLEVENINGSTAR

Evening Star - Bearish three-candle reversal.

Example:
pattern = CDLEVENINGSTAR(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLEVENINGSTAR(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue

            body1 = self._body_size(o1[i], c1[i])
            body2 = self._body_size(o2[i], c2[i])

            # First: long bullish, Second: small body (star), Third: long bearish
            if (self._is_bullish(o1[i], c1[i]) and
                body2 < body1 * 0.3 and
                self._is_bearish(o3[i], c3[i]) and
                c3[i] < (o1[i] + c1[i]) / 2):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLMARUBOZU

Marubozu - Strong candle with no shadows.

Example:
pattern = CDLMARUBOZU(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLMARUBOZU(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]

            if range_size > 0 and body / range_size > 0.95:  # Almost no shadows
                if self._is_bullish(o[i], c[i]):
                    result[i] = 100.0
                else:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDL3WHITESOLDIERS

Three White Soldiers - Bullish continuation.

Example:
pattern = CDL3WHITESOLDIERS(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDL3WHITESOLDIERS(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue

            # Three consecutive bullish candles with higher closes
            if (self._is_bullish(o1[i], c1[i]) and
                self._is_bullish(o2[i], c2[i]) and
                self._is_bullish(o3[i], c3[i]) and
                c2[i] > c1[i] and c3[i] > c2[i]):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDL3BLACKCROWS

Three Black Crows - Bearish continuation.

Example:
pattern = CDL3BLACKCROWS(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDL3BLACKCROWS(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue

            # Three consecutive bearish candles with lower closes
            if (self._is_bearish(o1[i], c1[i]) and
                self._is_bearish(o2[i], c2[i]) and
                self._is_bearish(o3[i], c3[i]) and
                c2[i] < c1[i] and c3[i] < c2[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDL2CROWS

Two Crows - Bearish reversal pattern.

Example:
pattern = CDL2CROWS(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDL2CROWS(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            # First: long bullish, Second: small bearish gap up, Third: bearish engulfs second
            if (self._is_bullish(o1[i], c1[i]) and
                self._is_bearish(o2[i], c2[i]) and o2[i] > c1[i] and
                self._is_bearish(o3[i], c3[i]) and o3[i] > o2[i] and c3[i] < c2[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDL3INSIDE

Three Inside Up/Down - Reversal pattern.

Example:
pattern = CDL3INSIDE(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDL3INSIDE(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            # Three Inside Up: bearish, bullish harami, bullish confirmation
            if (self._is_bearish(o1[i], c1[i]) and
                self._is_bullish(o2[i], c2[i]) and o2[i] > c1[i] and c2[i] < o1[i] and
                self._is_bullish(o3[i], c3[i]) and c3[i] > c2[i]):
                result[i] = 100.0
            # Three Inside Down: bullish, bearish harami, bearish confirmation
            elif (self._is_bullish(o1[i], c1[i]) and
                  self._is_bearish(o2[i], c2[i]) and o2[i] < c1[i] and c2[i] > o1[i] and
                  self._is_bearish(o3[i], c3[i]) and c3[i] < c2[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDL3LINESTRIKE

Three-Line Strike - Continuation pattern.

Example:
pattern = CDL3LINESTRIKE(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDL3LINESTRIKE(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 4:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -4)
    o2, h2, l2, c2 = self._get_ohlc(data, -3)
    o3, h3, l3, c3 = self._get_ohlc(data, -2)
    o4, h4, l4, c4 = self._get_ohlc(data, -1)

    n_symbols = len(o4)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i], o4[i], c4[i]])):
                continue
            # Bullish: three bearish + one bullish that engulfs all
            if (self._is_bearish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and
                self._is_bearish(o3[i], c3[i]) and c2[i] < c1[i] and c3[i] < c2[i] and
                self._is_bullish(o4[i], c4[i]) and o4[i] < c3[i] and c4[i] > o1[i]):
                result[i] = 100.0
            # Bearish: three bullish + one bearish
            elif (self._is_bullish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                  self._is_bullish(o3[i], c3[i]) and c2[i] > c1[i] and c3[i] > c2[i] and
                  self._is_bearish(o4[i], c4[i]) and o4[i] > c3[i] and c4[i] < o1[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDL3STARSINSOUTH

Three Stars In The South - Bullish reversal.

Example:
pattern = CDL3STARSINSOUTH(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDL3STARSINSOUTH(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            # Three decreasing bearish candles
            body1 = self._body_size(o1[i], c1[i])
            body2 = self._body_size(o2[i], c2[i])
            body3 = self._body_size(o3[i], c3[i])
            if (self._is_bearish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and
                self._is_bearish(o3[i], c3[i]) and body2 < body1 and body3 < body2):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLABANDONEDBABY

Abandoned Baby - Strong reversal pattern with gaps.

Example:
pattern = CDLABANDONEDBABY(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLABANDONEDBABY(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], h1[i], l1[i], c1[i], o2[i], h2[i], l2[i], c2[i], o3[i], c3[i]])):
                continue
            body2 = self._body_size(o2[i], c2[i])
            range2 = h2[i] - l2[i]
            is_doji = range2 > 0 and body2 / range2 < 0.1

            # Bullish: bearish, doji gap down, bullish gap up
            if (self._is_bearish(o1[i], c1[i]) and is_doji and h2[i] < l1[i] and
                self._is_bullish(o3[i], c3[i]) and l3[i] > h2[i]):
                result[i] = 100.0
            # Bearish: bullish, doji gap up, bearish gap down
            elif (self._is_bullish(o1[i], c1[i]) and is_doji and l2[i] > h1[i] and
                  self._is_bearish(o3[i], c3[i]) and h3[i] < l2[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLADVANCEBLOCK

Advance Block - Bearish reversal with weakening bulls.

Example:
pattern = CDLADVANCEBLOCK(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLADVANCEBLOCK(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            body1 = self._body_size(o1[i], c1[i])
            body2 = self._body_size(o2[i], c2[i])
            body3 = self._body_size(o3[i], c3[i])
            # Three bullish with decreasing bodies
            if (self._is_bullish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                self._is_bullish(o3[i], c3[i]) and body2 < body1 and body3 < body2 and
                c2[i] > c1[i] and c3[i] > c2[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLBELTHOLD

Belt-hold - Single candle reversal pattern.

Example:
pattern = CDLBELTHOLD(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLBELTHOLD(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            if range_size > 0 and body / range_size > 0.6:
                # Bullish belt hold: opens at low
                if self._is_bullish(o[i], c[i]) and abs(o[i] - l[i]) < range_size * 0.05:
                    result[i] = 100.0
                # Bearish belt hold: opens at high
                elif self._is_bearish(o[i], c[i]) and abs(o[i] - h[i]) < range_size * 0.05:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLBREAKAWAY

Breakaway - Five candle reversal pattern.

Example:
pattern = CDLBREAKAWAY(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLBREAKAWAY(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 5:
        return self._empty_output()

    n_symbols = len(data[0][-1].value)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    ohlc = [self._get_ohlc(data, -5+j) for j in range(5)]

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            o = [ohlc[j][0][i] for j in range(5)]
            c = [ohlc[j][3][i] for j in range(5)]
            if any(np.isnan(o + c)):
                continue
            # Simplified: strong move then reversal
            if (self._is_bearish(o[0], c[0]) and c[1] < c[0] and c[2] < c[1] and
                self._is_bullish(o[4], c[4]) and c[4] > c[0]):
                result[i] = 100.0
            elif (self._is_bullish(o[0], c[0]) and c[1] > c[0] and c[2] > c[1] and
                  self._is_bearish(o[4], c[4]) and c[4] < c[0]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLCLOSINGMARUBOZU

Closing Marubozu - No shadow on closing side.

Example:
pattern = CDLCLOSINGMARUBOZU(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLCLOSINGMARUBOZU(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            range_size = h[i] - l[i]
            if range_size > 0:
                if self._is_bullish(o[i], c[i]) and abs(c[i] - h[i]) < range_size * 0.02:
                    result[i] = 100.0
                elif self._is_bearish(o[i], c[i]) and abs(c[i] - l[i]) < range_size * 0.02:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLCONCEALBABYSWALL

Concealing Baby Swallow - Rare bullish reversal.

Example:
pattern = CDLCONCEALBABYSWALL(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLCONCEALBABYSWALL(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 4:
        return self._empty_output()

    n_symbols = len(data[0][-1].value)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    ohlc = [self._get_ohlc(data, -4+j) for j in range(4)]

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            o = [ohlc[j][0][i] for j in range(4)]
            h = [ohlc[j][1][i] for j in range(4)]
            l = [ohlc[j][2][i] for j in range(4)]
            c = [ohlc[j][3][i] for j in range(4)]
            if any(np.isnan(o + h + l + c)):
                continue
            # Four marubozu-like bearish, fourth engulfs
            all_bearish = all(self._is_bearish(o[j], c[j]) for j in range(4))
            if all_bearish and c[3] < c[0]:
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLCOUNTERATTACK

Counterattack - Two candle reversal with equal closes.

Example:
pattern = CDLCOUNTERATTACK(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLCOUNTERATTACK(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Closes are approximately equal
            close_diff = abs(c1[i] - c2[i])
            avg_body = (self._body_size(o1[i], c1[i]) + self._body_size(o2[i], c2[i])) / 2
            if avg_body > 0 and close_diff / avg_body < 0.1:
                if self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]):
                    result[i] = 100.0
                elif self._is_bullish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]):
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLDARKCLOUDCOVER

Dark Cloud Cover - Bearish reversal.

Example:
pattern = CDLDARKCLOUDCOVER(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLDARKCLOUDCOVER(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Bullish first, bearish second opening above and closing below midpoint
            if (self._is_bullish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and
                o2[i] > c1[i] and c2[i] < (o1[i] + c1[i]) / 2 and c2[i] > o1[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLDOJISTAR

Doji Star - Reversal pattern with doji.

Example:
pattern = CDLDOJISTAR(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLDOJISTAR(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], h1[i], l1[i], c1[i], o2[i], h2[i], l2[i], c2[i]])):
            body2 = self._body_size(o2[i], c2[i])
            range2 = h2[i] - l2[i]
            is_doji = range2 > 0 and body2 / range2 < 0.1

            if is_doji:
                if self._is_bullish(o1[i], c1[i]) and l2[i] > c1[i]:  # Gap up doji
                    result[i] = -100.0
                elif self._is_bearish(o1[i], c1[i]) and h2[i] < c1[i]:  # Gap down doji
                    result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLDRAGONFLYDOJI

Dragonfly Doji - Bullish reversal doji with long lower shadow.

Example:
pattern = CDLDRAGONFLYDOJI(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLDRAGONFLYDOJI(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            lower_shadow = min(o[i], c[i]) - l[i]
            upper_shadow = h[i] - max(o[i], c[i])

            if range_size > 0:
                is_doji = body / range_size < 0.1
                has_long_lower = lower_shadow > range_size * 0.6
                has_small_upper = upper_shadow < range_size * 0.1

                if is_doji and has_long_lower and has_small_upper:
                    result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLEVENINGDOJISTAR

Evening Doji Star - Bearish reversal with doji.

Example:
pattern = CDLEVENINGDOJISTAR(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLEVENINGDOJISTAR(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], h2[i], l2[i], c2[i], o3[i], c3[i]])):
                continue
            body2 = self._body_size(o2[i], c2[i])
            range2 = h2[i] - l2[i]
            is_doji = range2 > 0 and body2 / range2 < 0.1

            if (self._is_bullish(o1[i], c1[i]) and is_doji and l2[i] > c1[i] and
                self._is_bearish(o3[i], c3[i]) and c3[i] < (o1[i] + c1[i]) / 2):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLGAPSIDESIDEWHITE

Up/Down-gap side-by-side white lines - Continuation.

Example:
pattern = CDLGAPSIDESIDEWHITE(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLGAPSIDESIDEWHITE(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            # Two similar bullish candles after gap
            body2 = self._body_size(o2[i], c2[i])
            body3 = self._body_size(o3[i], c3[i])
            if body2 > 0 and abs(body2 - body3) / body2 < 0.2:
                if self._is_bullish(o2[i], c2[i]) and self._is_bullish(o3[i], c3[i]):
                    if l2[i] > h1[i]:  # Gap up
                        result[i] = 100.0
                    elif h2[i] < l1[i]:  # Gap down
                        result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLGRAVESTONEDOJI

Gravestone Doji - Bearish reversal doji with long upper shadow.

Example:
pattern = CDLGRAVESTONEDOJI(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLGRAVESTONEDOJI(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            upper_shadow = h[i] - max(o[i], c[i])
            lower_shadow = min(o[i], c[i]) - l[i]

            if range_size > 0:
                is_doji = body / range_size < 0.1
                has_long_upper = upper_shadow > range_size * 0.6
                has_small_lower = lower_shadow < range_size * 0.1

                if is_doji and has_long_upper and has_small_lower:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLHARAMICROSS

Harami Cross - Harami with doji second candle.

Example:
pattern = CDLHARAMICROSS(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLHARAMICROSS(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], h1[i], l1[i], c1[i], o2[i], h2[i], l2[i], c2[i]])):
            body2 = self._body_size(o2[i], c2[i])
            range2 = h2[i] - l2[i]
            is_doji = range2 > 0 and body2 / range2 < 0.1

            if is_doji:
                min1, max1 = min(o1[i], c1[i]), max(o1[i], c1[i])
                if o2[i] > min1 and o2[i] < max1 and c2[i] > min1 and c2[i] < max1:
                    if self._is_bearish(o1[i], c1[i]):
                        result[i] = 100.0
                    else:
                        result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLHIGHWAVE

High-Wave Candle - Indecision with long shadows.

Example:
pattern = CDLHIGHWAVE(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLHIGHWAVE(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            upper_shadow = h[i] - max(o[i], c[i])
            lower_shadow = min(o[i], c[i]) - l[i]

            if range_size > 0 and body > 0:
                # Small body with long shadows on both sides
                if (body / range_size < 0.3 and
                    upper_shadow > body and lower_shadow > body):
                    result[i] = 100.0  # Indecision signal

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLHIKKAKE

Hikkake Pattern - Inside bar breakout failure.

Example:
pattern = CDLHIKKAKE(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLHIKKAKE(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 4:
        return self._empty_output()

    n_symbols = len(data[0][-1].value)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    ohlc = [self._get_ohlc(data, -4+j) for j in range(4)]

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            h = [ohlc[j][1][i] for j in range(4)]
            l = [ohlc[j][2][i] for j in range(4)]
            c = [ohlc[j][3][i] for j in range(4)]
            if any(np.isnan(h + l + c)):
                continue
            # Inside bar then breakout failure
            if h[1] < h[0] and l[1] > l[0]:  # Inside bar
                if c[3] > h[0]:  # Bullish breakout
                    result[i] = 100.0
                elif c[3] < l[0]:  # Bearish breakout
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLHIKKAKEMOD

Modified Hikkake Pattern.

Example:
pattern = CDLHIKKAKEMOD(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLHIKKAKEMOD(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 5:
        return self._empty_output()

    n_symbols = len(data[0][-1].value)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    ohlc = [self._get_ohlc(data, -5+j) for j in range(5)]

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            h = [ohlc[j][1][i] for j in range(5)]
            l = [ohlc[j][2][i] for j in range(5)]
            c = [ohlc[j][3][i] for j in range(5)]
            if any(np.isnan(h + l + c)):
                continue
            # Modified version with confirmation
            if h[1] < h[0] and l[1] > l[0] and h[2] < h[1] and l[2] > l[1]:
                if c[4] > h[0]:
                    result[i] = 100.0
                elif c[4] < l[0]:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLHOMINGPIGEON

Homing Pigeon - Bullish harami-like pattern.

Example:
pattern = CDLHOMINGPIGEON(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLHOMINGPIGEON(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Two bearish candles, second inside first
            if (self._is_bearish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and
                o2[i] < o1[i] and c2[i] > c1[i]):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLIDENTICAL3CROWS

Identical Three Crows - Strong bearish continuation.

Example:
pattern = CDLIDENTICAL3CROWS(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLIDENTICAL3CROWS(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            # Three bearish, each opens at or near previous close
            if (self._is_bearish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and
                self._is_bearish(o3[i], c3[i]) and
                abs(o2[i] - c1[i]) < self._body_size(o1[i], c1[i]) * 0.1 and
                abs(o3[i] - c2[i]) < self._body_size(o2[i], c2[i]) * 0.1):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLINNECK

In-Neck Pattern - Bearish continuation.

Example:
pattern = CDLINNECK(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLINNECK(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Bearish first, bullish second closing at or slightly above first's close
            if (self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                o2[i] < c1[i] and abs(c2[i] - c1[i]) < self._body_size(o1[i], c1[i]) * 0.1):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLKICKING

Kicking - Strong reversal with marubozu gap.

Example:
pattern = CDLKICKING(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLKICKING(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], h1[i], l1[i], c1[i], o2[i], h2[i], l2[i], c2[i]])):
            body1 = self._body_size(o1[i], c1[i])
            body2 = self._body_size(o2[i], c2[i])
            range1 = h1[i] - l1[i]
            range2 = h2[i] - l2[i]

            # Both should be marubozu-like
            is_maru1 = range1 > 0 and body1 / range1 > 0.9
            is_maru2 = range2 > 0 and body2 / range2 > 0.9

            if is_maru1 and is_maru2:
                # Bullish kicking: bearish marubozu gap up to bullish marubozu
                if self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and l2[i] > h1[i]:
                    result[i] = 100.0
                # Bearish kicking: bullish marubozu gap down to bearish marubozu
                elif self._is_bullish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and h2[i] < l1[i]:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLKICKINGBYLENGTH

Kicking - bull/bear determined by the longer marubozu.

Example:
pattern = CDLKICKINGBYLENGTH(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLKICKINGBYLENGTH(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], h1[i], l1[i], c1[i], o2[i], h2[i], l2[i], c2[i]])):
            body1 = self._body_size(o1[i], c1[i])
            body2 = self._body_size(o2[i], c2[i])
            range1 = h1[i] - l1[i]
            range2 = h2[i] - l2[i]

            is_maru1 = range1 > 0 and body1 / range1 > 0.9
            is_maru2 = range2 > 0 and body2 / range2 > 0.9

            if is_maru1 and is_maru2:
                # Determine by longer body
                if body2 > body1:
                    if self._is_bullish(o2[i], c2[i]):
                        result[i] = 100.0
                    else:
                        result[i] = -100.0
                else:
                    if self._is_bullish(o1[i], c1[i]):
                        result[i] = -100.0  # Reversal expected
                    else:
                        result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLLADDERBOTTOM

Ladder Bottom - Bullish reversal.

Example:
pattern = CDLLADDERBOTTOM(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLLADDERBOTTOM(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 5:
        return self._empty_output()

    n_symbols = len(data[0][-1].value)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    ohlc = [self._get_ohlc(data, -5+j) for j in range(5)]

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            o = [ohlc[j][0][i] for j in range(5)]
            c = [ohlc[j][3][i] for j in range(5)]
            if any(np.isnan(o + c)):
                continue
            # Three bearish with lower closes, then bullish reversal
            if (self._is_bearish(o[0], c[0]) and self._is_bearish(o[1], c[1]) and
                self._is_bearish(o[2], c[2]) and c[1] < c[0] and c[2] < c[1] and
                self._is_bullish(o[4], c[4]) and c[4] > o[2]):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLLONGLEGGEDDOJI

Long Legged Doji - Doji with long shadows both sides.

Example:
pattern = CDLLONGLEGGEDDOJI(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLLONGLEGGEDDOJI(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            upper_shadow = h[i] - max(o[i], c[i])
            lower_shadow = min(o[i], c[i]) - l[i]

            if range_size > 0:
                is_doji = body / range_size < 0.1
                has_long_shadows = upper_shadow > range_size * 0.3 and lower_shadow > range_size * 0.3

                if is_doji and has_long_shadows:
                    result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLLONGLINE

Long Line Candle - Strong directional candle.

Example:
pattern = CDLLONGLINE(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLLONGLINE(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]

            if range_size > 0 and body / range_size > 0.7:
                if self._is_bullish(o[i], c[i]):
                    result[i] = 100.0
                else:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLMATCHINGLOW

Matching Low - Bullish reversal with equal lows.

Example:
pattern = CDLMATCHINGLOW(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLMATCHINGLOW(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Two bearish with matching closes
            if (self._is_bearish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and
                abs(c1[i] - c2[i]) < self._body_size(o1[i], c1[i]) * 0.05):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLMATHOLD

Mat Hold - Bullish continuation.

Example:
pattern = CDLMATHOLD(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLMATHOLD(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 5:
        return self._empty_output()

    n_symbols = len(data[0][-1].value)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    ohlc = [self._get_ohlc(data, -5+j) for j in range(5)]

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            o = [ohlc[j][0][i] for j in range(5)]
            c = [ohlc[j][3][i] for j in range(5)]
            if any(np.isnan(o + c)):
                continue
            # Long bullish, three small bearish, long bullish continuation
            if (self._is_bullish(o[0], c[0]) and
                self._is_bearish(o[1], c[1]) and self._is_bearish(o[2], c[2]) and
                self._is_bullish(o[4], c[4]) and c[4] > c[0]):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLMORNINGDOJISTAR

Morning Doji Star - Bullish reversal with doji.

Example:
pattern = CDLMORNINGDOJISTAR(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLMORNINGDOJISTAR(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], h2[i], l2[i], c2[i], o3[i], c3[i]])):
                continue
            body2 = self._body_size(o2[i], c2[i])
            range2 = h2[i] - l2[i]
            is_doji = range2 > 0 and body2 / range2 < 0.1

            if (self._is_bearish(o1[i], c1[i]) and is_doji and h2[i] < c1[i] and
                self._is_bullish(o3[i], c3[i]) and c3[i] > (o1[i] + c1[i]) / 2):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLONNECK

On-Neck Pattern - Bearish continuation.

Example:
pattern = CDLONNECK(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLONNECK(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], l1[i], c1[i], o2[i], c2[i]])):
            # Bearish first, bullish second closing at first's low
            if (self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                o2[i] < c1[i] and abs(c2[i] - l1[i]) < self._body_size(o1[i], c1[i]) * 0.1):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLPIERCING

Piercing Pattern - Bullish reversal.

Example:
pattern = CDLPIERCING(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLPIERCING(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Bearish first, bullish second opening below and closing above midpoint
            if (self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                o2[i] < c1[i] and c2[i] > (o1[i] + c1[i]) / 2 and c2[i] < o1[i]):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLRICKSHAWMAN

Rickshaw Man - Doji with equal long shadows.

Example:
pattern = CDLRICKSHAWMAN(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLRICKSHAWMAN(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            mid = (h[i] + l[i]) / 2
            body_mid = (o[i] + c[i]) / 2

            if range_size > 0:
                is_doji = body / range_size < 0.1
                is_centered = abs(body_mid - mid) < range_size * 0.1

                if is_doji and is_centered:
                    result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLRISEFALL3METHODS

Rising/Falling Three Methods - Continuation pattern.

Example:
pattern = CDLRISEFALL3METHODS(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLRISEFALL3METHODS(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 5:
        return self._empty_output()

    n_symbols = len(data[0][-1].value)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    ohlc = [self._get_ohlc(data, -5+j) for j in range(5)]

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            o = [ohlc[j][0][i] for j in range(5)]
            h = [ohlc[j][1][i] for j in range(5)]
            l = [ohlc[j][2][i] for j in range(5)]
            c = [ohlc[j][3][i] for j in range(5)]
            if any(np.isnan(o + h + l + c)):
                continue

            # Rising three methods
            if (self._is_bullish(o[0], c[0]) and
                all(self._is_bearish(o[j], c[j]) for j in [1, 2, 3]) and
                all(l[j] > l[0] for j in [1, 2, 3]) and
                self._is_bullish(o[4], c[4]) and c[4] > c[0]):
                result[i] = 100.0
            # Falling three methods
            elif (self._is_bearish(o[0], c[0]) and
                  all(self._is_bullish(o[j], c[j]) for j in [1, 2, 3]) and
                  all(h[j] < h[0] for j in [1, 2, 3]) and
                  self._is_bearish(o[4], c[4]) and c[4] < c[0]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLSEPARATINGLINES

Separating Lines - Continuation pattern.

Example:
pattern = CDLSEPARATINGLINES(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLSEPARATINGLINES(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Same open, opposite direction
            if abs(o1[i] - o2[i]) < self._body_size(o1[i], c1[i]) * 0.05:
                if self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]):
                    result[i] = 100.0
                elif self._is_bullish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]):
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLSHORTLINE

Short Line Candle - Small body candle.

Example:
pattern = CDLSHORTLINE(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLSHORTLINE(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]

            if range_size > 0 and body / range_size < 0.3 and body / range_size > 0.1:
                if self._is_bullish(o[i], c[i]):
                    result[i] = 100.0
                else:
                    result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLSPINNINGTOP

Spinning Top - Small body with shadows.

Example:
pattern = CDLSPINNINGTOP(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLSPINNINGTOP(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            upper_shadow = h[i] - max(o[i], c[i])
            lower_shadow = min(o[i], c[i]) - l[i]

            if range_size > 0 and body > 0:
                # Small body with shadows
                if (body / range_size < 0.4 and body / range_size > 0.1 and
                    upper_shadow > body * 0.3 and lower_shadow > body * 0.3):
                    result[i] = 100.0  # Indecision

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLSTALLEDPATTERN

Stalled Pattern - Bearish weakening.

Example:
pattern = CDLSTALLEDPATTERN(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLSTALLEDPATTERN(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            body1 = self._body_size(o1[i], c1[i])
            body2 = self._body_size(o2[i], c2[i])
            body3 = self._body_size(o3[i], c3[i])

            # Three bullish with decreasing bodies
            if (self._is_bullish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                self._is_bullish(o3[i], c3[i]) and body3 < body2 and body2 < body1 * 0.7):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLSTICKSANDWICH

Stick Sandwich - Bullish reversal.

Example:
pattern = CDLSTICKSANDWICH(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLSTICKSANDWICH(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            # Bearish, bullish, bearish with matching closes
            if (self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                self._is_bearish(o3[i], c3[i]) and
                abs(c1[i] - c3[i]) < self._body_size(o1[i], c1[i]) * 0.1):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLTAKURI

Takuri (Dragonfly Doji with very long lower shadow).

Example:
pattern = CDLTAKURI(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLTAKURI(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time == 0:
        return self._empty_output()

    o, h, l, c = self._get_ohlc(data)
    n_symbols = len(o)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o[i], h[i], l[i], c[i]])):
            body = self._body_size(o[i], c[i])
            range_size = h[i] - l[i]
            lower_shadow = min(o[i], c[i]) - l[i]
            upper_shadow = h[i] - max(o[i], c[i])

            if range_size > 0:
                is_doji_like = body / range_size < 0.15
                has_very_long_lower = lower_shadow > range_size * 0.7
                has_tiny_upper = upper_shadow < range_size * 0.05

                if is_doji_like and has_very_long_lower and has_tiny_upper:
                    result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLTASUKIGAP

Tasuki Gap - Continuation pattern.

Example:
pattern = CDLTASUKIGAP(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLTASUKIGAP(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], h1[i], l1[i], c1[i], o2[i], h2[i], l2[i], c2[i], o3[i], c3[i]])):
                continue
            # Upside tasuki gap
            if (self._is_bullish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                l2[i] > h1[i] and  # Gap
                self._is_bearish(o3[i], c3[i]) and o3[i] < c2[i] and c3[i] > l2[i]):
                result[i] = 100.0
            # Downside tasuki gap
            elif (self._is_bearish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and
                  h2[i] < l1[i] and  # Gap
                  self._is_bullish(o3[i], c3[i]) and o3[i] > c2[i] and c3[i] < h2[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLTHRUSTING

Thrusting Pattern - Bearish continuation.

Example:
pattern = CDLTHRUSTING(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLTHRUSTING(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 2:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -2)
    o2, h2, l2, c2 = self._get_ohlc(data, -1)

    n_symbols = len(o2)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i] and not any(np.isnan([o1[i], c1[i], o2[i], c2[i]])):
            # Bearish first, bullish second closing below midpoint
            midpoint = (o1[i] + c1[i]) / 2
            if (self._is_bearish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                o2[i] < c1[i] and c2[i] < midpoint and c2[i] > c1[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLTRISTAR

Tristar Pattern - Three doji reversal.

Example:
pattern = CDLTRISTAR(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLTRISTAR(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], h1[i], l1[i], c1[i], o2[i], h2[i], l2[i], c2[i], o3[i], h3[i], l3[i], c3[i]])):
                continue

            def is_doji(o, h, l, c):
                body = abs(c - o)
                range_size = h - l
                return range_size > 0 and body / range_size < 0.1

            if is_doji(o1[i], h1[i], l1[i], c1[i]) and is_doji(o2[i], h2[i], l2[i], c2[i]) and is_doji(o3[i], h3[i], l3[i], c3[i]):
                mid1 = (o1[i] + c1[i]) / 2
                mid2 = (o2[i] + c2[i]) / 2
                mid3 = (o3[i] + c3[i]) / 2
                # Gap pattern
                if mid2 < mid1 and mid2 < mid3:
                    result[i] = 100.0  # Bullish
                elif mid2 > mid1 and mid2 > mid3:
                    result[i] = -100.0  # Bearish

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLUNIQUE3RIVER

Unique 3 River - Bullish reversal.

Example:
pattern = CDLUNIQUE3RIVER(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLUNIQUE3RIVER(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], l1[i], c1[i], o2[i], l2[i], c2[i], o3[i], c3[i]])):
                continue
            # Long bearish, small bearish with lower low, small bullish
            if (self._is_bearish(o1[i], c1[i]) and
                self._is_bearish(o2[i], c2[i]) and l2[i] < l1[i] and
                self._body_size(o2[i], c2[i]) < self._body_size(o1[i], c1[i]) * 0.5 and
                self._is_bullish(o3[i], c3[i]) and c3[i] < c2[i]):
                result[i] = 100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLUPSIDEGAP2CROWS

Upside Gap Two Crows - Bearish reversal.

Example:
pattern = CDLUPSIDEGAP2CROWS(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLUPSIDEGAP2CROWS(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], c1[i], o2[i], c2[i], o3[i], c3[i]])):
                continue
            # Bullish, small bearish gap up, larger bearish engulfing second
            if (self._is_bullish(o1[i], c1[i]) and
                self._is_bearish(o2[i], c2[i]) and o2[i] > c1[i] and
                self._is_bearish(o3[i], c3[i]) and o3[i] > o2[i] and c3[i] < c2[i] and c3[i] > c1[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

***

## CDLXSIDEGAP3METHODS

Upside/Downside Gap Three Methods - Continuation.

Example:
pattern = CDLXSIDEGAP3METHODS(
Input("FIELD:open", timeframe="1m", lookback=5),
Input("FIELD:high", timeframe="1m", lookback=5),
Input("FIELD:low", timeframe="1m", lookback=5),
Input("FIELD:close", timeframe="1m", lookback=5),
)

**Role**: `INDICATOR` | **Ephemeral**: No

### Parameters

| Parameter | Type      | Default  | Description |
| --------- | --------- | -------- | ----------- |
| `open_`   | `'Input'` | Required |             |
| `high`    | `'Input'` | Required |             |
| `low`     | `'Input'` | Required |             |
| `close`   | `'Input'` | Required |             |

### Usage

```python theme={null}
pattern = CDLXSIDEGAP3METHODS(
    Input("FIELD:open", timeframe="1m", lookback=5),
    Input("FIELD:high", timeframe="1m", lookback=5),
    Input("FIELD:low", timeframe="1m", lookback=5),
    Input("FIELD:close", timeframe="1m", lookback=5),
)
```

### Source Code

Full `compute()` implementation — no hidden logic.

```python theme={null}
def compute(self, data, timestamp=None, context=None) -> TaggedArray:
    if not isinstance(data, list) or len(data) < 4:
        return self._empty_output()

    n_time = len(data[0])
    if n_time < 3:
        return self._empty_output()

    o1, h1, l1, c1 = self._get_ohlc(data, -3)
    o2, h2, l2, c2 = self._get_ohlc(data, -2)
    o3, h3, l3, c3 = self._get_ohlc(data, -1)

    n_symbols = len(o3)
    exists = data[3][-1].exists
    valid = data[3][-1].valid
    result = np.zeros(n_symbols)

    for i in range(n_symbols):
        if exists[i] and valid[i]:
            if any(np.isnan([o1[i], h1[i], l1[i], c1[i], o2[i], h2[i], l2[i], c2[i], o3[i], c3[i]])):
                continue
            # Upside gap three methods
            if (self._is_bullish(o1[i], c1[i]) and self._is_bullish(o2[i], c2[i]) and
                l2[i] > h1[i] and  # Gap up
                self._is_bearish(o3[i], c3[i]) and o3[i] > l2[i] and c3[i] < h1[i]):
                result[i] = 100.0
            # Downside gap three methods
            elif (self._is_bearish(o1[i], c1[i]) and self._is_bearish(o2[i], c2[i]) and
                  h2[i] < l1[i] and  # Gap down
                  self._is_bullish(o3[i], c3[i]) and o3[i] < h2[i] and c3[i] > l1[i]):
                result[i] = -100.0

    result_valid = exists & valid
    return TaggedArray(
        value=result,
        exists=exists,
        valid=result_valid,
        updated=np.ones(n_symbols, dtype=bool),
    )
```

<sub>Source: `apps/trading/operators/indicator/patterns.py`</sub>

## Related Pages

<CardGroup cols={2}>
  <Card title="Operator Protocol" icon="gear" href="/engine/operator-protocol">
    How operators implement the compute() interface
  </Card>

  <Card title="StatefulGraph" icon="diagram-project" href="/engine/stateful-graph">
    How operators compose into a DAG
  </Card>
</CardGroup>
