# Styling & Tags

Bar/wick/border coloring with 8-char hex alpha, plotshape markers, region shading, and define_tag for signal classification.

<a id="bar_color"></a>
## bar_color

```python
bar_color(colors)
```

Set candle body color per bar.

Pass an array of hex color strings, same length as bars. Use `None` for bars that should keep their default color.

Accepts 6-char (`#rrggbb`) or 8-char (`#rrggbbaa`) hex. The 8-char form encodes per-bar alpha, so you can mark a bar 30% transparent without touching the global candle opacity setting.

### Parameters

- `colors` (`array-like`, default `required`): Array of hex color strings (6- or 8-char) or None values

### Returns

None

### Example

```python
import numpy as np
from quant_charts import bar_color, ta, close

sma = ta.sma(close, 20)
# 8-char hex makes high-vol bars translucent green, default elsewhere
colors = np.where(np.array(close) > sma, "#22c55ecc", None)
bar_color(colors)
```

<a id="wick_color"></a>
## wick_color

```python
wick_color(colors)
```

Set candle wick color per bar.

Same format as `bar_color`: array of hex strings or `None`.

### Parameters

- `colors` (`array-like`, default `required`): Array of hex color strings or None values

### Returns

None

### Example

```python
wick_color(np.where(volume > avg_vol, "#ffffff", None))
```

<a id="border_color"></a>
## border_color

```python
border_color(colors)
```

Set candle border color per bar (independent from body).

Sets the border color independently from `bar_color`. With body color unchanged and a contrasting border, candles render in the TradingView "hollow candle" style. Useful for highlighting trades that hit TP vs SL, regime transitions, or volume outliers without changing the up/down body color.

### Parameters

- `colors` (`array-like`, default `required`): Array of hex color strings or None values, same length as bars. None entries use the body color (default LWC behavior).

### Returns

None

### Example

```python
import numpy as np
from quant_charts import border_color, volume_series

vol = volume_series(df)
avg = np.nanmean(vol)
# white outline on high-volume bars, default border elsewhere
border_color(np.where(vol > 2 * avg, "#ffffff", None))
```

### Notes

- 8-char hex (`#rrggbbaa`) encodes per-bar alpha, e.g. `"#ffffff80"` for 50% white.
- Pair with `bar_color()` for the hollow-candle look (body translucent, border solid).
- Mismatched array length raises `ValueError`.

<a id="set_bar_color"></a>
## set_bar_color

```python
set_bar_color(condition, color, wick?, border?)
```

Apply color where condition is True.

Convenience wrapper. Multiple calls compose (last-writer-wins per bar). Easier than building a full color array. Optional `wick` overrides the wick color and `border` overrides the candle outline color independently.

### Parameters

- `condition` (`bool array`, default `required`): Boolean array, color applied where True
- `color` (`str`, default `required`): Hex color for candle body
- `wick` (`str`, default `None`): Optional hex color for wick
- `border` (`str`, default `None`): Optional hex color for candle border / outline

### Returns

None

### Example

```python
from quant_charts import set_bar_color, ta, close

rsi = ta.rsi(close, 14)
set_bar_color(rsi > 70, "#ef4444")                              # Red when overbought
set_bar_color(rsi < 30, "#22c55e")                              # Green when oversold
set_bar_color(rsi > 70, "#ef4444", wick="#ff6666")              # Body + wick
set_bar_color(rsi > 70, "#ef4444", wick="#ff6666", border="#aa0000")  # Body + wick + border
```

<a id="plotshape"></a>
## plotshape

```python
plotshape(condition, shape?, location?, color?, size?, text?)
```

Plot shape markers on the chart where condition is True.

Draws visual markers at specific bars. Useful for marking entry/exit signals, divergences, or pattern detections.

### Parameters

- `condition` (`bool array`, default `required`): Boolean array, shapes placed where True
- `shape` (`str`, default `"triangle_up"`): triangle_up, triangle_down, circle, diamond, square, arrow_up, arrow_down, cross
- `location` (`str`, default `"above"`): `"above"` (above high), `"below"` (below low), `"at"` (at close)
- `color` (`str`, default `"#00ff00"`): Hex color string
- `size` (`str`, default `"small"`): `"small"`, `"medium"`, `"large"`
- `text` (`str`, default `None`): Optional text label next to shape

### Returns

None

### Example

```python
from quant_charts import plotshape, cross_above, cross_below

plotshape(cross_above(fast, slow),
    shape="triangle_up", color="#22c55e")
plotshape(cross_below(fast, slow),
    shape="triangle_down", location="below",
    color="#ef4444")
plotshape(vol_spike, shape="diamond",
    color="#e0af68", text="VOL")
```

<a id="draw_box"></a>
## draw_box

```python
draw_box(start_index, end_index, top, bottom, color?, opacity?, extend_right?)
```

Draw a single rectangle at exact bar indices and exact price bounds.

Unlike `box()` which colors regions where a boolean condition is True, `draw_box()` places one rectangle at specific coordinates. Use for precise control: FVG (fair value gap) zones, order blocks, marked-up support / resistance bands, archived value-area boxes.

### Parameters

- `start_index` (`int`, default `required`): Bar index where the box starts
- `end_index` (`int`, default `required`): Bar index where the box ends. Ignored when extend_right=True.
- `top` (`float`, default `required`): Price level for the top edge
- `bottom` (`float`, default `required`): Price level for the bottom edge
- `color` (`str`, default `"#7aa2f7"`): Hex color string
- `opacity` (`int`, default `20`): Opacity 0-100
- `extend_right` (`bool`, default `False`): If True, the box extends to the right edge of the chart regardless of end_index.

### Returns

None

### Example

```python
from quant_charts import draw_box

# Mark a fair value gap zone between bars 100 and 140
draw_box(100, 140, top=4250.50, bottom=4248.75, color="#7aa2f7", opacity=15)

# Open-ended order block that extends right until invalidated
draw_box(200, 0, top=4255.0, bottom=4253.0, color="#f7768e", opacity=20, extend_right=True)
```

<a id="bgcolor"></a>
## bgcolor

```python
bgcolor(condition, color?, opacity?)
```

Draw full-height colored background on bars where condition is True.

Creates colored bands behind candles for session highlighting, zone marking, or any condition-based background.

Opacity is 0-100 (default 20). Color is a hex string.

### Parameters

- `condition` (`array-like`, default `required`): Boolean array, True bars get the background
- `color` (`str`, default `"#7aa2f7"`): Hex color string
- `opacity` (`int`, default `20`): Opacity 0-100

### Returns

None

### Example

```python
from quant_charts import indicator, bgcolor, hour, minute

@indicator("Session Colors", overlay=True)
class SessionColors:
    def calculate(self, df):
        asian = (hour >= 19) | (hour < 1)
        european = (hour >= 1) & (hour < 9)
        american = (hour >= 9) & (hour < 16)

        bgcolor(asian, color="#FF5722", opacity=10)
        bgcolor(european, color="#4CAF50", opacity=10)
        bgcolor(american, color="#2196F3", opacity=10)
        return {}
```

<a id="box"></a>
## box

```python
box(start_condition, end_condition, color?, opacity?, top?, bottom?)
```

Draw rectangular regions from start to end bars.

Each True in start_condition begins a box. The next True in end_condition closes it.

If top/bottom are None, the box spans full chart height. Set them to price levels for bounded boxes.

### Parameters

- `start_condition` (`array-like`, default `required`): Boolean, True starts a box
- `end_condition` (`array-like`, default `required`): Boolean, True ends a box
- `color` (`str`, default `"#7aa2f7"`): Hex color string
- `opacity` (`int`, default `20`): Opacity 0-100
- `top` (`float`, default `None`): Price level for top edge (None = chart top)
- `bottom` (`float`, default `None`): Price level for bottom edge (None = chart bottom)

### Returns

None

### Example

```python
from quant_charts import indicator, box, hour, minute

@indicator("Session Box", overlay=True)
class SessionBox:
    def calculate(self, df):
        session_open = (hour == 9) & (minute == 30)
        session_close = (hour == 16) & (minute == 0)
        box(session_open, session_close, color="#7aa2f7", opacity=15)
        return {}
```

<a id="define_tag"></a>
## define_tag

```python
define_tag(name, description, label?, color?)
```

Declare a tag with display metadata for the UI.

Tags are boolean arrays that mark conditions on each bar. Strategies use them to filter trades (e.g., only trade during "morning" bars). `define_tag()` sets the label, description, and color shown in the tag dropdown.

You can also skip `define_tag()`. Returning a dict from `calculate()` auto-creates tags from the dict keys.

Tags drive **preventive** trade filtering in the analyzer: with a tag selected, the Rust engine receives a `trading_mask` and refuses to open trades where the tag is False on that bar.

### Parameters

- `name` (`str`, default `required`): Tag identifier (matches return dict key)
- `description` (`str`, default `required`): Tooltip description shown in UI
- `label` (`str`, default `name`): Short display label
- `color` (`str`, default `auto`): Hex color for the tag badge

### Returns

None

### Example

```python
from quant_charts import indicator, input, plot, ta, define_tag, close

@indicator("RSI", overlay=False)
class RSI:
    period = input.int(14, "Period")

    def calculate(self, df):
        rsi = ta.rsi(close, self.period)
        plot(rsi, "RSI", color="#9C27B0")

        define_tag("overbought", f"RSI > 70", color="#DC2626")
        define_tag("oversold", f"RSI < 30", color="#16A34A")

        return {
            "overbought": rsi > 70,
            "oversold": rsi < 30,
        }
```

### Notes

- Tags returned from `calculate()` are boolean arrays. `True` marks bars where the condition holds.
- Tags enable preventive trade filtering in the backtester. Only allow trades during tagged periods.
- If you skip `define_tag()`, tags are auto-generated from the return dict keys with default colors.

<a id="block_entries"></a>
## block_entries

```python
block_entriesreturn {..., 'block_entries': bool_array}
```

Per-bar entry gate returned from calculate(). A truthy value blocks NEW entries on that bar.

Return a `block_entries` boolean array (same length as the data) alongside your signals. On bars where it is truthy, the engine refuses to open a new position; bars that are absent or falsy are allowed. Open positions and exit signals are unaffected. This is the array-shaped replacement for the old `disable_entries` action: gate entries declaratively instead of dispatching a runtime action.

For stop-modification patterns, return the SL/TP you want directly in `sl_long`/`tp_long`/`sl_short`/`tp_short` (the engine ratchets them favorably so a trailing stop just works), or build them with the `breakeven_when` / `shift_levels` helpers. To close a position, set `exit_long`/`exit_short`.

### Parameters

- `block_entries` (`bool[]`, default `absent (all allowed)`): Truthy bar = block new entries; absent/falsy = allowed

### Returns

part of the calculate() return dict

### Example

```python
from quant_charts import strategy, use_indicator, cross_above, cross_below

@strategy(name="Gated MA", overlay=True)
class GatedMA:
    def calculate(self, df):
        fast = use_indicator('sma', period=10)
        slow = use_indicator('sma', period=30)
        atr  = use_indicator('atr', period=14)

        # block entries when volatility is too low to be worth trading
        low_vol = atr < atr.rolling(50).mean() * 0.5

        return {
            'entry_long': cross_above(fast, slow),
            'exit_long':  cross_below(fast, slow),
            'block_entries': low_vol,
        }
```

### Notes

- `block_entries` only gates NEW entries. It never force-closes an open position and never suppresses exits.
- Replaces the removed `disable_entries` trigger. For breakeven/tick-shift stops use `breakeven_when()` / `shift_levels()`.
- Rust strategies use the symmetric `.with_trading_mask(vec)` builder where `true` = allowed.

<a id="breakeven_when"></a>
## breakeven_when

```python
breakeven_when(entries, entry_price, tag, offset_ticks=0, tick_size=0.25)
```

Build an SL-shaped array that snaps the stop to the entry price (plus an optional tick offset) on every bar where `tag` is True.

Authoring-time helper. Forward-fills the most recent entry price since the last `entries` signal, then writes `entry_price ± offset_ticks*tick_size` wherever `tag` is True (NaN elsewhere). Return the result as `sl_long` (or `sl_short`). It cannot see engine-side fills/exits, but the favorable-only SL ratchet makes a stale breakeven harmless (a value that would loosen the stop is rejected). Replaces the old `set_sl_breakeven` trigger.

### Parameters

- `entries` (`bool[]`, default `required`): Entry signal array (where positions open)
- `entry_price` (`float[]`, default `required`): Price series to read the entry price from (e.g. df["close"])
- `tag` (`bool[]`, default `required`): Bars where the stop should move to breakeven
- `offset_ticks` (`int`, default `0`): Ticks above/below entry (signed)
- `tick_size` (`float`, default `0.25`): Instrument tick size

### Returns

np.ndarray (sl-shaped, NaN where inactive)

### Example

```python
from quant_charts import breakeven_when

out['sl_long'] = breakeven_when(entry_long, df['close'], regime_flip, offset_ticks=2)
```

### Notes

- Best-effort replacement for the stateful `set_sl_breakeven`; documented limitation: no live fill/exit feedback.

<a id="shift_levels"></a>
## shift_levels

```python
shift_levels(levels, tag, ticks, tick_size=0.25)
```

Return a copy of an SL/TP array shifted by `ticks*tick_size` from each True in `tag` onward (sticky-forward).

Authoring-time helper. From each True in `tag`, every non-NaN value in `levels` is shifted by `ticks*tick_size` (negative tightens). The shift is sticky-forward, mirroring the old `shift_sl_ticks`/`shift_tp_ticks` triggers which moved the stop until the position closed.

### Parameters

- `levels` (`float[]`, default `required`): SL or TP array to transform
- `tag` (`bool[]`, default `required`): Bars from which the shift arms
- `ticks` (`int`, default `required`): Signed tick delta to apply
- `tick_size` (`float`, default `0.25`): Instrument tick size

### Returns

np.ndarray (copy of `levels`)

### Example

```python
from quant_charts import shift_levels

out['sl_long'] = shift_levels(sl_long, tighten_now, ticks=-4)
```

### Notes

- Replaces the removed `shift_sl_ticks` / `shift_tp_ticks` triggers.
