Разработка кастомных индикаторов теханализа
Кастомный индикатор — это визуализация собственной торговой логики, которую нет в стандартном наборе TradingView или MetaTrader. Это может быть модификация известного индикатора, composite индикатор из нескольких факторов, или принципиально новая метрика. Разберём как это правильно строить.
Анатомия торгового индикатора
Индикатор принимает OHLCV данные, производит вычисления и возвращает серии значений для отображения. Технически — это чистая функция от данных.
from dataclasses import dataclass
import pandas as pd
import numpy as np
@dataclass
class IndicatorOutput:
values: pd.Series
signal_line: pd.Series = None
histogram: pd.Series = None
upper_band: pd.Series = None
lower_band: pd.Series = None
signals: pd.Series = None # BUY/SELL маркеры
Пример: Hull Moving Average
HMA быстрее реагирует на изменение тренда и меньше лагает чем EMA. Расчёт:
def hull_ma(close: pd.Series, period: int = 20) -> pd.Series:
"""
HMA = WMA(2 * WMA(close, period/2) - WMA(close, period), sqrt(period))
"""
half_period = int(period / 2)
sqrt_period = int(np.sqrt(period))
wma_half = close.ewm(span=half_period, adjust=False).mean()
wma_full = close.ewm(span=period, adjust=False).mean()
raw_hma = 2 * wma_half - wma_full
hma = raw_hma.ewm(span=sqrt_period, adjust=False).mean()
return hma
Пример: Composite Momentum Score
Объединяем несколько моментум индикаторов в один нормализованный score:
def composite_momentum_score(df: pd.DataFrame) -> pd.Series:
"""
Composite score от -100 до +100.
Положительный = моментум вверх, отрицательный = вниз.
"""
# RSI нормализуем к [-1, 1]
rsi = (df['close'].diff(1).apply(lambda x: max(x, 0)).rolling(14).mean() /
df['close'].diff(1).abs().rolling(14).mean()) * 2 - 1
# Rate of Change нормализованный
roc_14 = df['close'].pct_change(14)
roc_z = (roc_14 - roc_14.rolling(100).mean()) / roc_14.rolling(100).std()
roc_norm = roc_z.clip(-2, 2) / 2 # нормализуем к [-1, 1]
# Williams %R нормализованный
highest_high = df['high'].rolling(14).max()
lowest_low = df['low'].rolling(14).min()
williams_r = ((highest_high - df['close']) / (highest_high - lowest_low) - 0.5) * -2
# Взвешенная комбинация
score = (rsi * 0.35 + roc_norm * 0.40 + williams_r * 0.25) * 100
return score.round(1)
Реализация в Pine Script (TradingView)
//@version=5
indicator("Composite Momentum Score", shorttitle="CMS", overlay=false)
rsi_length = input.int(14, "RSI Length")
roc_length = input.int(14, "ROC Length")
norm_window = input.int(100, "Normalization Window")
// RSI нормализованный
gain = math.max(ta.change(close), 0)
loss = math.abs(math.min(ta.change(close), 0))
avg_gain = ta.rma(gain, rsi_length)
avg_loss = ta.rma(loss, rsi_length)
rs = avg_gain / avg_loss
rsi_norm = (100 / (1 + rs) - 50) / 50 * -1 // к [-1, 1], перевёрнуто
// ROC нормализованный
roc = (close - close[roc_length]) / close[roc_length]
roc_mean = ta.sma(roc, norm_window)
roc_std = ta.stdev(roc, norm_window)
roc_z = (roc - roc_mean) / roc_std
roc_norm = math.max(-1, math.min(1, roc_z / 2))
// Composite Score
score = (rsi_norm * 0.35 + roc_norm * 0.40) * 100
// Визуализация
hline(0, color=color.gray, linewidth=1)
hline(50, color=color.new(color.green, 70), linewidth=1)
hline(-50, color=color.new(color.red, 70), linewidth=1)
score_color = score > 0 ? color.new(color.green, 30) : color.new(color.red, 30)
plot(score, "CMS", color=score_color, linewidth=2)
Пример: Order Flow Imbalance Indicator
Дисбаланс между bid и ask объёмом — опережающий индикатор движения цены:
def order_flow_imbalance(df: pd.DataFrame, window: int = 10) -> pd.Series:
"""
Использует OHLCV данные как приближение order flow.
Более точно — с tick data, но и так даёт полезный сигнал.
"""
# Приближение buy/sell volume из тела свечи
candle_range = df['high'] - df['low']
candle_range = candle_range.replace(0, np.nan)
# Часть объёма пропорциональна положению close в диапазоне
close_position = (df['close'] - df['low']) / candle_range
buy_vol_approx = df['volume'] * close_position
sell_vol_approx = df['volume'] * (1 - close_position)
# OFI = (buy_vol - sell_vol) / total_vol
ofi = (buy_vol_approx - sell_vol_approx) / df['volume']
ofi_smooth = ofi.rolling(window).mean()
return ofi_smooth * 100 # в процентах
Публикация на TradingView
Кастомные индикаторы можно публиковать в TradingView Pine Script library:
- Написать на Pine Script v5
- Добавить описание и примеры использования
- Publish as Public Script (бесплатно) или Protected (скрытый код)
- Пользователи добавляют через поиск индикаторов
Для коммерческих индикаторов — TradingView не предоставляет монетизацию напрямую. Альтернатива: продажа через собственный сайт (Gumroad, Stripe) с предоставлением invite-only access.
Кастомный индикатор с уникальной логикой и качественной документацией — это актив. Популярные индикаторы на TradingView собирают десятки тысяч подписчиков и служат мощным каналом привлечения клиентов для торговых продуктов.







