169 lines
5.9 KiB
Markdown
169 lines
5.9 KiB
Markdown
---
|
|
description: "API and patterns for writing custom Python indicator scripts that compute values from OHLCV data and plot live on the chart."
|
|
---
|
|
|
|
# Custom Indicator Development
|
|
|
|
Custom indicators are Python scripts saved in the `indicator` category. They compute values from OHLCV data and are plotted live on the TradingView chart alongside built-in indicators.
|
|
|
|
See [`../pandas-ta-reference.md`](../pandas-ta-reference.md) for the full catalog of built-in indicators available via `pandas_ta`.
|
|
|
|
---
|
|
|
|
## Function Signature
|
|
|
|
A custom indicator must define a **top-level function** whose name is the lowercase, snake_case form of the `name` passed to `PythonWrite`. For example, `name="VW RSI"` → function `def vw_rsi(...)`.
|
|
|
|
The function receives the OHLCV columns listed in `input_series` as positional arguments and must return either:
|
|
- A `pd.Series` (single-output indicator), or
|
|
- A `pd.DataFrame` with column names matching `output_columns` in the metadata (multi-output)
|
|
|
|
```python
|
|
import pandas as pd
|
|
import pandas_ta as ta
|
|
|
|
# Single-output: volume-weighted RSI
|
|
def vw_rsi(close: pd.Series, volume: pd.Series, length: int = 14) -> pd.Series:
|
|
rsi = ta.rsi(close, length=length)
|
|
vol_weight = volume / volume.rolling(length).mean()
|
|
return (rsi * vol_weight).rolling(3).mean()
|
|
```
|
|
|
|
```python
|
|
import pandas as pd
|
|
import pandas_ta as ta
|
|
|
|
# Multi-output: custom Bollinger Bands
|
|
def vol_bands(close: pd.Series, length: int = 20, std: float = 2.0) -> pd.DataFrame:
|
|
bb = ta.bbands(close, length=length, std=std)
|
|
return pd.DataFrame({
|
|
"upper": bb.iloc[:, 2],
|
|
"mid": bb.iloc[:, 1],
|
|
"lower": bb.iloc[:, 0],
|
|
})
|
|
```
|
|
|
|
**Always use `pandas_ta` for standard indicator calculations.** Never write manual `rolling().mean()` or `ewm()` implementations — use `ta.sma()`, `ta.ema()`, `ta.rsi()`, etc.
|
|
|
|
---
|
|
|
|
## Required Metadata
|
|
|
|
When writing a custom indicator with `PythonWrite`, supply complete metadata so the web client can build the TradingView plotter automatically:
|
|
|
|
```python
|
|
PythonWrite(
|
|
category="indicator",
|
|
name="VW RSI",
|
|
description="RSI weighted by relative volume.",
|
|
details="""## Volume-Weighted RSI
|
|
|
|
Computes RSI on close prices, scales by relative volume, applies 3-bar smoothing.
|
|
|
|
**Formula:** (rsi * (volume / volume.rolling(length).mean())).rolling(3).mean()
|
|
**Inputs:** close, volume
|
|
**Output:** single Series — smoothed volume-weighted RSI (separate pane)
|
|
**Parameters:** length (int, default 14)""",
|
|
code="""...""",
|
|
metadata={
|
|
"parameters": {
|
|
"length": {"type": "int", "default": 14, "min": 2, "max": 200, "description": "RSI period"}
|
|
},
|
|
"input_series": ["close", "volume"],
|
|
"output_columns": [
|
|
{"name": "value", "display_name": "VW-RSI", "plot": {"style": 0}}
|
|
],
|
|
"pane": "separate" # "price" = overlay on candles; "separate" = sub-pane
|
|
}
|
|
)
|
|
```
|
|
|
|
### Plot styles
|
|
|
|
| Value | Renders as |
|
|
|---|---|
|
|
| `0` | Line (default) |
|
|
| `1` | Histogram bars |
|
|
| `4` | Area (filled under line) |
|
|
| `5` | Columns (vertical bars) |
|
|
| `9` | Step line |
|
|
|
|
### Filled areas (shaded bands)
|
|
|
|
To shade between two output series (e.g. upper/lower bands), add a `filled_areas` list. The two bounding series must appear at consecutive even/odd positions in `output_columns`:
|
|
|
|
```python
|
|
"filled_areas": [
|
|
{"id": "fill", "type": "plot_plot", "series1": "upper", "series2": "lower",
|
|
"color": "#2196F3", "opacity": 0.08}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## Workflow
|
|
|
|
1. **Check for existing indicators** before writing: `PythonList(category="indicator")`. If one already exists with the same sanitized name, update it with `PythonEdit` rather than creating a duplicate.
|
|
|
|
2. **Write** with `PythonWrite(category="indicator", ...)`. The system automatically runs the script against synthetic test data to catch compile/runtime errors — no separate validation call needed.
|
|
|
|
3. **Add to workspace** with `WorkspacePatch("indicators", ...)` using `pandas_ta_name: "custom_<sanitized_name>"`. Include `custom_metadata` in the patch value so the web client can render it.
|
|
|
|
4. **Use in strategies** via `ta.custom_<sanitized_name>(...)`. See [`../strategies/strategy-development.md`](strategy-development.md) for details.
|
|
|
|
---
|
|
|
|
## Naming Conventions
|
|
|
|
The workspace `pandas_ta_name` is `"custom_"` + the sanitized indicator name. Sanitization: lowercase + spaces/hyphens → underscores. For example:
|
|
|
|
| `name` | function name | `pandas_ta_name` |
|
|
|---|---|---|
|
|
| `"VW RSI"` | `vw_rsi` | `custom_vw_rsi` |
|
|
| `"TrendFlex"` | `trendflex` | `custom_trendflex` |
|
|
| `"Vol-Bands"` | `vol_bands` | `custom_vol_bands` |
|
|
|
|
Two names that sanitize to the same value will conflict — check with `PythonList` first.
|
|
|
|
---
|
|
|
|
## Common Pitfalls
|
|
|
|
### Look-ahead bias
|
|
|
|
Never use future data in the computation. Indicator values for bar N may only depend on data available at bar N or earlier.
|
|
|
|
```python
|
|
# WRONG — uses future price
|
|
signal = close.shift(-1) > close
|
|
|
|
# CORRECT — only past data
|
|
signal = close > close.shift(1)
|
|
```
|
|
|
|
### Repainting
|
|
|
|
Indicator values for already-closed bars should not change as new bars arrive. Avoid calculations that recalculate over a sliding window that can retrospectively alter past values in non-obvious ways.
|
|
|
|
### NaN handling
|
|
|
|
Indicators need a warm-up period. The first `length - 1` values will be `NaN`. Strategies that consume custom indicators should guard with:
|
|
```python
|
|
if vw_rsi.isna().all() or len(df) < min_required:
|
|
return
|
|
```
|
|
|
|
### Overfitting
|
|
|
|
- Keep indicator logic simple and parameter-lean
|
|
- Validate on out-of-sample data, not the same window used to tune parameters
|
|
- Prefer indicators with a clear mechanical rationale over curve-fit formulas
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- [`../pandas-ta-reference.md`](../pandas-ta-reference.md) — Full catalog of built-in indicators and calling conventions
|
|
- [`../api-reference.md`](../api-reference.md) — DataAPI and ChartingAPI for research scripts
|
|
- [`../strategies/strategy-development.md`](strategy-development.md) — Using custom indicators in strategies via `ta.custom_*`
|