Files
ai/gateway/knowledge/indicator-development.md
Tim Olson 47471b7700 Expand model tag support: add GLM-5.1, simplify Anthropic IDs, scan tags anywhere in message
- Flink update_bars debouncing
- update_bars subscription idempotency bugfix
- Price decimal correction bugfix of previous commit
- Add GLM-5.1 model tag alongside renamed GLM-5
- Use short Anthropic model IDs (sonnet/haiku/opus) instead of full version strings
- Allow @tags anywhere in message content, not just at start
- Return hasOtherContent flag instead of trimmed rest string
- Only trigger greeting stream when tag has no other content
- Update workspace knowledge base references to platform/workspace and platform/shapes
- Hierarchical knowledge base catalog
- 151 Trading Strategies knowledge base articles
- Shapes knowledge base article
- MutateShapes tool instead of workspace patch
2026-04-28 15:05:15 -04:00

5.9 KiB

description
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 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)
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()
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:

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:

"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 strategy-development 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.

# 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:

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