# Decorators

Top-level annotations that turn a Python file into an indicator, strategy, script, or day-start hoist.

<a id="indicator"></a>
## @indicator

```python
@indicator(name, overlay?, description?, data_mode?, timeframe?)
```

Mark a Python class as a chart indicator.

The `@indicator` decorator transforms a Python class into a chart indicator that receives price data and draws visual output. The class must implement a `calculate(self, df)` method that receives the chart DataFrame.

Call [`plot()`](https://quantchartsllc.com/docs/python/py-plotting.md#plot), [`hline()`](https://quantchartsllc.com/docs/python/py-plotting.md#hline), or [`fill()`](https://quantchartsllc.com/docs/python/py-plotting.md#fill) inside `calculate()` to draw on the chart. Access price data via the global series: [`close`](https://quantchartsllc.com/docs/python/py-data.md#close), [`open`](https://quantchartsllc.com/docs/python/py-data.md#open), [`high`](https://quantchartsllc.com/docs/python/py-data.md#high), [`low`](https://quantchartsllc.com/docs/python/py-data.md#low), [`volume`](https://quantchartsllc.com/docs/python/py-data.md#volume).

Python indicators run on OHLC bars. For TBBO tick-level indicators, use a Rust .rs file with the [`#[indicator]`](https://quantchartsllc.com/docs/rust/rust-indicator.md#indicator) macro instead.

### Parameters

- `name` (`str`, default `required`): Display name shown in the indicator panel
- `overlay` (`bool`, default `False`): If `True`, draws on price chart. If `False`, separate pane
- `description` (`str`, default `None`): Optional tooltip description
- `data_mode` (`str`, default `'ohlc'`): Always `'ohlc'` for Python indicators. Use Rust for tick.
- `timeframe` ([`Timeframe`](https://quantchartsllc.com/docs/python/py-logging.md#timeframe), default `None`): Target timeframe for the indicator

### Returns

None. Use `plot()` inside `calculate()` to draw output.

### Example

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

@indicator("SMA", overlay=True)
class SMA:
    period = input.int(20, "Period", min=1, max=500)
    color = input.color("#2962FF", "Color")

    def calculate(self, df):
        result = close.rolling(self.period).mean()
        plot(result, f"SMA({self.period})", color=self.color)
```

### Notes

- `calculate()` is called every time the chart data updates.
- Use `input.*` class variables to create user-configurable parameters.
- Multiple `plot()` calls create multiple series on the same indicator.

<a id="strategy"></a>
## @strategy

```python
@strategy(name, overlay?, timeframe?, description?, data_mode?, uses_sltp?, emit_sltp?, required_columns?)
```

Mark a Python class as a backtestable trading strategy.

The `@strategy` decorator creates a trading strategy that returns entry/exit signals for backtesting. The class must implement `calculate(self, df)` which returns a dict of boolean signal Series.

The backtester uses these signals to simulate trades and compute equity curves, win rates, drawdowns, and other performance metrics.

Python strategies run on OHLC bars only. For tick-level TBBO strategies use a Rust .rs file.

### Parameters

- `name` (`str`, default `required`): Display name shown in the strategy panel
- `overlay` (`bool`, default `True`): If `True`, draws on the price chart
- `timeframe` (`Timeframe`, default `None`): Default execution timeframe
- `description` (`str`, default `None`): Optional tooltip description
- `data_mode` (`str`, default `'ohlc'`): Always `'ohlc'` for Python strategies
- `uses_sltp` (`bool`, default `False`): If `True`, the strategy returns `sl_long`/`tp_long`/`sl_short`/`tp_short` arrays
- `emit_sltp` (`str`, default `'entry_only'`): `'entry_only'` (set once at entry) or `'per_tick'` (re-evaluated each bar)
- `required_columns` (`list[str]`, default `auto`): Columns the strategy reads. SHM only ships these to workers.

### Returns

A dict with keys: `"entry_long"`, `"exit_long"`, `"entry_short"`, `"exit_short"`. With `uses_sltp=True`, also `"sl_long"`, `"tp_long"`, `"sl_short"`, `"tp_short"`.

### Example

```python
from quant_charts import strategy, input, indicators, cross_above, cross_below, Timeframe

@strategy("MA Cross", overlay=True, timeframe=Timeframe.M1)
class MACross:
    fast = input.int(10, "Fast", min=1, max=100)
    slow = input.int(20, "Slow", min=2, max=200)

    def calculate(self, df):
        fast_ma = indicators.sma(period=self.fast, color="#26A69A")
        slow_ma = indicators.sma(period=self.slow, color="#EF5350")

        return {
            'entry_long': cross_above(fast_ma, slow_ma),
            'exit_long': cross_below(fast_ma, slow_ma),
            'entry_short': cross_below(fast_ma, slow_ma),
            'exit_short': cross_above(fast_ma, slow_ma),
        }
```

### Notes

- Use `indicators.sma()` / `indicators.ema()` to add visual overlays alongside your strategy.
- Signal Series must be boolean. Use [`cross_above()`](https://quantchartsllc.com/docs/python/py-signals.md#cross_above) / [`cross_below()`](https://quantchartsllc.com/docs/python/py-signals.md#cross_below) for crossovers.
- Conflicting signals on the same bar: exit is resolved before entry.
- Hoist param-independent work into a `@day_start` method for fast optimization sweeps.

<a id="day_start"></a>
## @day_start

```python
@day_start(method)
```

Mark a method to run once per day per worker, before the parameter sweep.

Hoists parameter-independent computation out of the per-combo `calculate()` body. The decorated method runs once per trading day per worker process; results stored on `self` are visible inside `calculate()` for every parameter combination.

Use this for any heavy work that does not depend on swept params: full-series ta calls with literal periods, day-level summary stats, regime detection, cached indicator outputs.

### Parameters

- `method` (`callable`, default `required`): Method on the strategy/indicator class with signature `(self, df) -> None`

### Returns

None. Store hoisted arrays as `self.<name>` for use inside `calculate()`.

### Example

```python
from quant_charts import strategy, day_start, input, ta, close, cross_above, cross_below

@strategy("Hoisted EMA Cross")
class HoistedCross:
    fast = input.int(10, "Fast", min=2, max=50)
    slow = input.int(30, "Slow", min=5, max=200)

    @day_start
    def prep(self, df):
        # hoisted: param-independent, runs once per day not once per combo
        self.atr = ta.atr(df['high'], df['low'], df['close'], 14)

    def calculate(self, df):
        fast_ma = close.rolling(self.fast).mean()
        slow_ma = close.rolling(self.slow).mean()
        return {
            'entry_long': cross_above(fast_ma, slow_ma),
            'exit_long':  cross_below(fast_ma, slow_ma),
            'entry_short': cross_below(fast_ma, slow_ma),
            'exit_short':  cross_above(fast_ma, slow_ma),
        }
```

### Notes

- Critical for fast optimization sweeps: a 100-combo sweep with hoisted ATR runs the ATR once instead of 100 times.
- Anything assigned to `self.*` inside `@day_start` is visible in `calculate()`.
- Calls inside `@day_start` should use literal periods, not swept parameters.

<a id="script"></a>
## @script

```python
@script(name, description?, version?)
```

Mark a Python class as a utility script for data analysis. Used in .py files only (not notebooks).

The `@script` decorator creates a standalone script that runs once when executed. Unlike indicators and strategies, scripts do not draw on the chart. Instead they process data, export files, or produce analysis output.

**Note:** Scripts are standalone `.py` files executed from the file explorer. For notebook-based analysis, use regular Python cells instead.

Scripts have access to `script_helpers` for exporting data, reading chart state, and accessing indicator values.

### Parameters

- `name` (`str`, default `required`): Display name for the script
- `description` (`str`, default `None`): Optional description
- `version` (`str`, default `None`): Optional version string

### Returns

Optional dict, displayed in the output panel.

### Example

```python
from quant_charts import script, input
from quant_charts.script_helpers import get_chart_data, export_csv

@script("Data Export")
class DataExporter:
    filename = input.string("export.csv", "Filename")

    def execute(self, df):
        df.to_csv(self.filename)
        return {"status": "success", "rows": len(df)}
```

### Output

```text
>>> Output
{'status': 'success', 'rows': 14523}
```

### Notes

- Scripts implement `execute(self, df)` instead of `calculate(self, df)`.
- Import from `quant_charts.script_helpers` for data access and export.
