Размер свечи уже знаком: high - low. Если он нужен в нескольких вычислениях, вынесите в функцию. Вызов может показывать цель расчёта, а формула останется в одном месте.

Входы и результат функции

def price_range(top: float, bottom: float) -> float:
    return top - bottom

def определяет функцию price_range. Она ждёт два входа: top и bottom. Оба могут содержать дробную часть.

-> float обозначает тип результата. return вычисляет и возвращает результат, завершая вызов.

price_range(105, 100) даёт 5. Функция не знает, цены ли это, и не выбирает свечу. Оба значения передаёт вызывающий код.

Тело имеет отступ четыре пробела. Определение здесь стоит рядом с main(), на внешнем уровне файла. Поэтому две необходимые цены передаются параметрами. Функцию можно определить и внутри main(); ниже мы попробуем это.

Область видимости имени

top и bottom доступны лишь в price_range. main() не обращается к ним напрямую, а использует возвращённый результат.

Область видимости определяет, в какой части кода доступно имя. Две функции могут иметь одинаковые имена параметров, не превращая их в общую переменную.

Внешняя price_range не получает автоматически локальные переменные main(). Но может использовать импортированные в начале имена, например close. Видимость зависит и от места определения.

Определение и вызов различаются. Первое задаёт правило, второй выполняет его с конкретными значениями.

Полная программа

Создайте price_functions.py. Одна функция вычисляет полный диапазон свечи и изменение открытия к закрытию со знаком:

"""
@pyne edge
"""
from pynecore.lib import close, color, high, low, open, plot, script


def price_range(top: float, bottom: float) -> float:
    return top - bottom


@script.indicator("Price Functions", overlay=False)
def main():
    full_range = price_range(high, low)
    body_change = price_range(close, open)
    plot(full_range, "Full range", color=color.blue)
    plot(body_change, "Body change", color=color.orange)

Запустите на демонстрационных данных. Диапазон неотрицателен, поскольку максимум не ниже минимума. Изменение открытия к закрытию может быть отрицательным. Функция не меняет цены, только возвращает разность.

Два вызова price_functions.py отображают полный диапазон свечи и изменение её тела со знаком.
Одна функция вычисляет два результата из разных входов.

Функция короткая и легко проверяется вручную. В большой программе можно именовать более сложные подзадачи, но каждое отдельное сложение выносить необязательно.

Порядок параметров

price_range(high, low) передаёт high параметру top, low параметру bottom. Перестановка меняет знак разности.

Можно указать имена:

full_range = price_range(top=high, bottom=low)

Так длиннее, но сложный вызов читается легче. Имя должно точно совпадать с параметром функции.

Функция внутри main()

Вложенная функция может читать переменные окружающей функции. Это удобно, когда несколько расчётов используют общую входную настройку.

Сгладим две прежние линии средним. Зададим длину одним input для обоих расчётов. Вложенной функции передадим лишь цены; длину она найдёт в параметрах main().

В price_functions.py сохраните внешнюю price_range и декоратор индикатора. Добавьте input и ta к импорту pynecore.lib, замените определение main():

def main(
    length: int = input.int(5, title="Average length", minval=1, maxval=200),
):
    def average_difference(top: float, bottom: float) -> float:
        return ta.sma(price_range(top, bottom), length)

    average_range = average_difference(high, low)
    average_body_change = average_difference(close, open)
    plot(average_range, "Average range", color=color.blue)
    plot(average_body_change, "Average body change", color=color.orange)

length является знакомым целочисленным input. Вложенная average_difference получает top и bottom из вызова. length она читает из окружающей main(), повторно передавать его не нужно.

Определение вложенной функции начинается с четырёх пробелов как часть main(). Её собственная строка return с восьми. Имя average_difference доступно внутри main(), но не на внешнем уровне.

price_range находит разность, ta.sma возвращает её среднее. Синяя линия показывает средний полный диапазон, оранжевая среднее изменение тела со знаком. Оба вызова выполняются на каждой свече и непрерывно считают средние.

Запустите изменённую программу, откройте Inputs шестерёнкой. Измените Average length с 5 на 20 и нажмите Save. Открытый график пересчитается автоматически, обе линии станут средними по двадцати свечам. Менять два вызова не понадобилось. Длина 1 возвращает линии оригинала.

Такой доступ к внешним переменным называется замыканием, или closure. Чтение не означает, что внутреннее присваивание автоматически перезапишет внешнюю переменную. Здесь мы лишь читаем length.

В варианте с параметрами вызов показывает все переданные значения, функцию легко применить к другим ценам. Вложенная удобна, когда несколько расчётов опираются на общие переменные main().

Собственная функция тоже может иметь состояние

price_range использует только полученные значения. У неё нет сохраняемых между свечами переменных и истории. Её можно вызвать с двумя значениями в любой момент.

average_difference уже имеет состояние: вызванная ta.sma считает по предыдущим данным. В собственной функции можно напрямую создать Persistent или Series.

Функция счётчика событий

Посчитаем отдельно растущие и падающие свечи. Функция получит логическое значение и запомнит, сколько раз получила истину.

Импортируйте Persistent в начале:

from pynecore import Persistent

Напишите функцию перед main() на внешнем уровне:

def count_events(event: bool) -> int:
    count: Persistent[int] = 0
    if event:
        count += 1
    return count

count начинается с нуля. При истинном event растёт на один, при ложном сохраняется. return возвращает итог в обоих случаях. После вызова Persistent сохраняет значение для следующего.

Оставьте overlay=False, замените прежнюю main():

def main():
    bullish_count = count_events(close > open)
    bearish_count = count_events(close < open)
    plot(bullish_count, "Bullish count", color=color.green)
    plot(bearish_count, "Bearish count", color=color.red)

Одна функция вызывается из двух мест кода. Каждому принадлежит отдельное состояние count: первое считает растущие свечи, второе падающие. Первый вызов не увеличивает второй счётчик.

Например, первые четыре свечи дают:

СвечаОткрытие и закрытиеbullish_countbearish_count
ПерваяЗакрытие выше10
ВтораяЗакрытие ниже11
ТретьяЦены равны11
ЧетвёртаяЗакрытие выше21

Запустите на демонстрационных данных. Зелёная линия растёт только на растущей свече, красная на падающей. Новый запуск строит оба счётчика с нуля.

Оба вызова выполняются на каждой свече, условие прироста находится внутри. У функции с состоянием порядок и частота вызовов являются частью поведения. Если вызывать её на части свечей, история продвигается лишь при этих вызовах.

Для общего счётчика вызовите функцию один раз и используйте переменную результата в нескольких местах. Второе место вызова означает новый отдельный счётчик.

Несколько инструкций в функции

Промежуточный результат можно вычислить до return:

def midpoint(top: float, bottom: float) -> float:
    total = top + bottom
    return total / 2

total является локальной переменной. По окончании вызова она не становится переменной main(). Из функции возвращён только результат.

Попробуйте самостоятельно

Добавьте midpoint и создайте индикатор поверх свечей, рисующий середины. Параметры top и bottom получают максимум и минимум.

Решение

Поместите определение midpoint выше main(), задайте overlay=True, тело:

center = midpoint(high, low)
plot(center, "Midpoint", color=color.blue, linewidth=2)

Расчёты и рисование прежних двух линий можно удалить из этого варианта main(). Удалите и неиспользуемые импорты.

Объясните вызов

Для price_range(close, open) объясните передачу значений параметрам, действие return и место получения результата в main(). Эти три шага можно проследить и в более длинном вызове.