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
This commit is contained in:
2026-04-28 15:05:15 -04:00
parent d41fcd0499
commit 47471b7700
184 changed files with 9044 additions and 170 deletions

View File

@@ -0,0 +1,168 @@
---
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`](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 [`strategy-development`](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`](pandas-ta-reference.md) — Full catalog of built-in indicators and calling conventions
- [`api-reference`](api-reference.md) — DataAPI and ChartingAPI for research scripts
- [`strategy-development`](strategy-development.md) — Using custom indicators in strategies via `ta.custom_*`