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:
168
gateway/knowledge/indicator-development.md
Normal file
168
gateway/knowledge/indicator-development.md
Normal 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_*`
|
||||
Reference in New Issue
Block a user