Для опытных: как устроены индикаторы?
Изучите исходники SMA, EMA, RSI, MACD, ATR и полос Боллинджера и создайте свой вариант.
Индикатор проще развивать, понимая происхождение значения. Какие данные входят в расчёт? Что сохраняется на следующую свечу? Что изменится с другой средней или источником цен? Ответы даёт и исходный код.
Библиотека ta PyneCore написана на Pyne code. Её Persistent, вызовы и условия используют знакомое поведение. Все исходники доступны: готовый индикатор можно проследить так же, как свой.
Эта продвинутая глава учит чтению исходников и экспериментам. Не нужно сразу понимать всю библиотеку. Начните с короткой функции и проследите вызовы, на которых она основана.
Откройте настоящую реализацию
- Откройте прежний
moving_averages.pyв PyneIDE. - В
ta.ema(close, 10)удерживайте Command в macOS или Ctrl в Windows и Linux и нажмитеema. Подойдёт и Go to Definition правой кнопкой. - Откроется установленный
ta.pyPyneCore у определенияema. - Прочитайте описание и тело. Из вызова другой функции можно перейти к её определению.

Параметры и определения вы уже открывали в главе о помощи редактора. То же действие открывает RSI, MACD и остальные функции. Полный справочник ta помогает находить функции и понимать параметры.
Код библиотеки и Edge: в начале ta.py стоит @pyne lib. Библиотека не ограничена Edge: использует и общие средства Python, внутренние помощники и дополнительные аннотации. Но посвечное исполнение и состояние будут знакомы. PyneFloat является числовой аннотацией библиотеки, na_float отсутствующим числом.
Ниже показаны расчёты библиотеки, не полные программы Edge. Собственный Edge-вариант пишите разрешёнными средствами на основе понятого алгоритма. Установленный ta.py оставьте прежним, экспериментируйте в своих файлах.
SMA: сумма за формулой
Две строки ta.sma:
length = int(length)
return lib_math.sum(source, length) / lengthСреднее равно сумме окна, делённой на размер. После преобразования длины в целое суммой занимается lib_math.sum. Состояние находится не в теле sma, а в вызванном суммировании. Проследив через math.py, вы дойдёте до _math_stateful.py.
При чтении помните: короткая функция тоже может иметь состояние. Её вызовы входят в поведение расчёта. Среднее вручную складывало то же окно своим циклом.
EMA и RMA: обновление сохранённого среднего
После прогрева EMA выполняет:
last_val = last_val + alpha * (source - last_val)
return last_vallast_val является прошлым средним, source текущим значением. Старое среднее меняется на долю разности. В EMA alpha равно 2 / (length + 1). При длине десять прибавляется около 18,18 процента разницы новой цены и старого среднего.
В исходнике alpha и last_val помечены Persistent. Вначале last_val отсутствует, поэтому функция пытается получить SMA той же длины:
if not (last_val == last_val):
last_val = sma(source, length)
return last_valПока данных для SMA мало, результат отсутствует. Получив начальное среднее, функция обновляет прошлое. not (last_val == last_val) проверяет отсутствие: отсутствующее значение не равно себе. В своих примерах вы использовали na(value).
RMA, сглаживание Уайлдера, начинается похоже, но обновляется иначе:
last_val = (last_val * (length - 1) + source) / length
return last_valПри длине десять вес нового значения одна десятая. В EMA при той же длине вес больше, поэтому реакция на один ценовой ряд различается.
Полный код также показывает обработку отсутствующего входа и упрощение для длины один. Даже одинаковая формула может дать разницу из-за начального значения, пропусков или разных свечей вызова. Порядок операций с плавающей точкой тоже важен: сохраняйте его для точного сравнения.
RSI: два отдельных сглаженных изменения
RSI разделяет рост и падение источника. Основные строки PyneCore:
rma_u = rma(builtins.max(source - prev_src, 0.0), length)
rma_d = rma(builtins.max(prev_src - source, 0.0), length)
prev_src = sourceprev_src хранит прежний допустимый источник через Persistent. При росте 100 до 103 изменение вверх 3, вниз 0. При падении 103 до 101 это 0 и 2. RMA сглаживает ряды отдельно, каждому месту вызова принадлежит своё состояние.
prev_src обновляется после вычисления разностей. Тот же порядок был в дополнительном задании с сохранением прошлого значения.
RSI получается из соотношения сглаженных изменений:
if rma_d <= 1e-10:
return 100.0
if rma_u <= 1e-10:
return 0.0
return 100 - 100 / (1 + rma_u / rma_d)Отсутствие падения даёт 100, отсутствие роста 0. Исходник учитывает очень малые значения и сначала проверяет нисходящую часть: если обе ниже порога, результат 100. Одной формулы недостаточно, чтобы узнать такие детали.
Первая допустимая цена ещё не даёт изменения. RSI сохраняет её и начинает сглаживания только со следующим значением. Прогрев определяется числом изменений. RSI не является вероятностью: 70 не означает 70-процентный шанс роста.
MACD: новый индикатор из средних
MACD берёт разность двух EMA и сглаживает её ещё одной EMA. Основные шаги без проверок отсутствия:
fast = ema(source, fastlen)
slow = ema(source, slowlen)
macd_val = fast - slow
signal = ema(macd_val, siglen)Три возвращаемых значения:
return macd_val, signal, macd_val - signalMACD-линия является разностью быстрой и медленной средних. Сигнальная линия сглаживает её. Гистограмма равна разности линий и нулю при их равенстве.
Один вызов даёт три результата для трёх переменных:
macd_line, signal_line, histogram = ta.macd(close, 12, 26, 9)Порядок важен: MACD, сигнал, гистограмма. До появления обеих ценовых средних разность отсутствует. Потом сигнальная линия проходит свой прогрев. Начальные пропуски сложного индикатора следуют и из внутренних расчётов.
ATR: ценовой скачок тоже входит в диапазон
Реализация короткая:
return rma(tr(True), length)Знакомая RMA сглаживает True Range, истинный диапазон. Он равен максимуму трёх расстояний:
- текущий максимум минус минимум;
- расстояние максимума от прошлого закрытия;
- расстояние минимума от прошлого закрытия.
При прошлом закрытии 100, текущем минимуме 108 и максимуме 110 диапазон свечи лишь 2. Истинный диапазон max(2, 10, 8), то есть 10: он учитывает скачок после закрытия.
На первой свече без прошлого закрытия tr(True) использует high - low. В исходнике прошлое закрытие читается из внутренних данных среды. ATR показывает величину движения в единицах цены, но не направление.
Полосы Боллинджера: среднее и стандартное отклонение
Центр ta.bb является SMA, границы отстоят на заданный множитель стандартного отклонения. Сначала считаются отклонение и среднее:
std_dev = stdev(series, length)
middle = sma(series, length)Для допустимого центра возврат:
std_dev *= mult
return middle, middle + std_dev, middle - std_devСтандартное отклонение описывает разброс окна вокруг среднего. При центре 100, отклонении 3 и множителе 2 верхняя граница 106, нижняя 94. Снова три значения, но иной порядок, чем у MACD: центр, верх, низ.
Свой вариант: сначала повторите оригинал
Создайте bands_workshop.py. Собственная функция собирает полосы из простых частей библиотеки. Для сравнения также вычисляется верхняя граница ta.bb.
"""
@pyne edge
"""
from pynecore.lib import close, color, input, plot, script, ta
def custom_bands(source: float, length: int, multiplier: float) -> tuple[float, float, float]:
middle = ta.sma(source, length)
deviation = ta.stdev(source, length)
upper = middle + multiplier * deviation
lower = middle - multiplier * deviation
return middle, upper, lower
@script.indicator("Bands Workshop", overlay=True)
def main(
length: int = input.int(20, title="Length", minval=2, maxval=200),
multiplier: float = input.float(2.0, title="Deviation multiplier", minval=0.1, maxval=5.0),
):
builtin_middle, builtin_upper, builtin_lower = ta.bb(close, length, multiplier)
own_middle, own_upper, own_lower = custom_bands(close, length, multiplier)
plot(builtin_upper, "Built-in upper", color=color.blue, linewidth=3)
plot(own_upper, "Custom upper", color=color.orange)
plot(own_middle, "Custom middle", color=color.green)
plot(own_lower, "Custom lower", color=color.gray)tuple[float, float, float] задаёт тип результата из трёх чисел. return middle, upper, lower возвращает тройку, три переменные перед вызовом принимают значения отдельно. builtin_middle и builtin_lower сейчас не рисуются, но позже пригодятся для проверки.
Запустите на демонстрационных данных. Верхние границы совпадают, тонкая оранжевая лежит поверх толстой синей. Наведите указатель на свечу и сравните Built-in upper и Custom upper. При длине двадцать у первых девятнадцати свечей нет полного окна.

Для точной проверки откройте значения через Open plot output CSV. У своего варианта проверяйте первую допустимую точку и последующие. Внешне одинаковые линии могут слегка различаться.
Попробуйте самостоятельно: центр EMA
Измените только центр custom_bands: EMA вместо SMA. Отклонение и готовый ta.bb оставьте для сравнения.
Предскажите: будет ли верхняя граница совпадать всегда? Что произойдёт после резкого изменения цены?
Решение
Первая строка расчёта:
middle = ta.ema(source, length)Границы теперь измеряются от EMA, расчёт ta.stdev прежний. Центр и границы обычно сдвигаются относительно оригинала. Разность верхних значений равна разности EMA и SMA, поскольку к ним добавляются одинаковые отклонение и множитель.
Это ваш экспериментальный вариант. Изменение линии само не говорит об улучшении торгового правила. Это проверяется отдельной стратегией на тех же данных и настройках.
Продолжайте изучать библиотеку
Выберите индикатор и опишите вход, сохраняемые данные и результат своими словами. Идите по внутренним вызовам до понимания расчёта. Для RSI цепочка rsi → rma → sma ведёт от разностей к сглаживанию и начальному значению.
Читайте ветви отсутствий, прогрева и граничных случаев. Меняйте сначала одну деталь и сравнивайте с оригиналом. Так намеренное изменение отделяется от ошибки.
Исходники остальных функций доступны тем же нажатием с Command или Ctrl. На длинный индикатор нужно больше времени, но подход прежний: входы, память, расчёт, результат.