Funciones propias
Divide el código en cálculos con nombre y reutilízalos en varios lugares.
Ya conoces el cálculo del tamaño de una vela: high - low. Si lo necesitas en varios cálculos, puedes ponerlo en una función. La llamada puede revelar su propósito y la fórmula queda en un solo lugar.
Las entradas y el resultado de una función
def price_range(top: float, bottom: float) -> float:
return top - bottomdef define una función llamada price_range. Espera dos entradas, top y bottom. Ambas son números que admiten parte fraccionaria.
-> float indica el tipo del resultado. return calcula y devuelve el resultado y termina esa llamada a la función.
price_range(105, 100) da 5. La función no sabe si estos valores son precios ni elige una vela. Quien la llama proporciona los dos valores.
El cuerpo empieza con cuatro espacios de sangría. Aquí la definición está junto a main(), en el nivel exterior del archivo. Por eso pasamos como parámetros los dos precios que necesita. También puedes crear una función dentro de main(); lo probaremos más abajo.
El ámbito de un nombre
Los parámetros top y bottom solo están disponibles dentro de price_range. main() no puede referirse a ellos directamente. Allí utilizas el resultado devuelto.
El ámbito de un nombre es la parte del código donde está disponible. Dos funciones distintas pueden usar el mismo nombre de parámetro sin convertir sus parámetros en una única variable compartida.
price_range, definida en el nivel exterior, no accede automáticamente a las variables locales de main(). Sí puede usar los nombres importados al principio del archivo, como close. El lugar donde defines la función influye, por tanto, en la visibilidad.
La definición y la llamada tampoco son lo mismo. La definición establece la regla; la llamada la ejecuta con valores concretos.
El programa completo
Crea price_functions.py. La misma función calcula el rango completo de la vela y la variación con signo entre apertura y cierre:
"""
@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)Ejecútalo con los datos de demostración. El rango completo no es negativo porque el máximo es al menos igual al mínimo. La variación entre apertura y cierre sí puede ser negativa. La función no modifica los precios: devuelve su diferencia.

Aquí la función es corta y fácil de comprobar a mano. En un programa mayor puedes dar nombre a tareas más complejas, pero no tienes que convertir cada suma en una función aparte.
El orden de los parámetros
price_range(high, low) pasa high a top y low a bottom. Si los intercambias, también inviertes el signo de la resta.
Puedes indicar los nombres de los parámetros:
full_range = price_range(top=high, bottom=low)Es más largo, pero puede facilitar la lectura de una llamada compleja. El nombre debe coincidir exactamente con el del parámetro de la función.
Una función dentro de main()
Una función definida dentro de otra puede acceder a las variables de la función que la contiene. Esto resulta útil cuando varios cálculos comparten un ajuste definido como input.
Suavicemos las dos líneas anteriores con una media móvil. Definimos su longitud en una sola entrada y la usamos para ambos cálculos. Solo pasamos los dos precios a la función interior; esta encuentra la longitud entre los parámetros de main().
En price_functions.py, conserva la función exterior price_range y el decorador del indicador. Añade input y ta a la importación de pynecore.lib y sustituye la definición de main() por:
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 es la entrada entera que ya conoces. La función interior average_difference recibe dos parámetros: la llamada proporciona top y bottom. Lee length de la función main() que la rodea, así que no hace falta pasarlo de nuevo.
La definición interior empieza con cuatro espacios porque pertenece a main(). Su propio cuerpo, la línea return, empieza con ocho. average_difference está disponible dentro de main(), no en el nivel exterior del archivo.
price_range calcula la diferencia entre los precios y ta.sma devuelve su media móvil. La línea azul muestra la media del rango completo; la naranja, la media de la variación con signo entre apertura y cierre. Ambas llamadas se ejecutan en cada vela, por lo que las medias se calculan de forma continua.
Ejecuta el programa modificado y abre Inputs con el engranaje. Cambia Average length de 5 a 20 y pulsa Save. El gráfico abierto se recalcula automáticamente y ambas líneas pasan a medias de veinte velas. No has tenido que cambiar las dos llamadas interiores. Con longitud 1 recuperas las líneas del programa original.
Este acceso a variables del entorno se llama clausura, o closure. Leer un valor no significa que una asignación interior sobrescriba automáticamente la variable exterior. En este ejemplo solo leemos length.
Si lees código traducido de Pine Script: PyneComp crea las funciones dentro de main(). Las variables del nivel principal de Pine Script también pasan al interior de main(), de modo que las funciones interiores pueden acceder a ellas. Por eso el código traducido tiene esa disposición. Al escribir Pyne code puedes usar ambos enfoques: una función independiente que recibe parámetros o una interior que accede también a las variables que la rodean.
En la versión con parámetros, la llamada muestra todos los valores pasados y resulta fácil reutilizar la función con otros precios. Una función interior puede ser cómoda cuando varios cálculos se basan en las mismas variables de main().
Una función propia también puede tener estado
price_range solo calcula con los valores recibidos. No conserva variables entre velas ni utiliza historial. Por eso puedes llamarla con dos valores en cualquier momento.
En cambio, average_difference utiliza estado porque su llamada a ta.sma calcula a partir de valores anteriores. También puedes crear variables Persistent o Series directamente en una función propia.
Una función que cuenta eventos
Contaremos por separado las velas alcistas y bajistas. Escribiremos una función que recibe un valor lógico y recuerda cuántas veces ha recibido verdadero.
Importa Persistent al principio del archivo:
from pynecore import PersistentEscribe esta función antes de main(), en el nivel exterior:
def count_events(event: bool) -> int:
count: Persistent[int] = 0
if event:
count += 1
return countcount empieza en cero. Si event es verdadero, aumenta uno; si es falso, conserva su valor. return devuelve el total acumulado en ambos casos. Al terminar la llamada, la variable Persistent conserva su valor para la siguiente.
Mantén overlay=False. Sustituye la definición y el cuerpo anteriores de main() por:
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)Llamamos a la misma función desde dos lugares distintos del código. Cada lugar tiene su propio estado count: el primero cuenta velas alcistas y el segundo bajistas. La primera llamada no aumenta el contador de la segunda.
Por ejemplo, si las cuatro primeras velas siguen este orden, obtienes:
| Vela | Relación entre apertura y cierre | bullish_count | bearish_count |
|---|---|---|---|
| Primera | El cierre es mayor | 1 | 0 |
| Segunda | El cierre es menor | 1 | 1 |
| Tercera | Ambos precios son iguales | 1 | 1 |
| Cuarta | El cierre es mayor | 2 | 1 |
Ejecútalo con los datos de demostración. La línea verde sube solo en velas alcistas y la roja solo en bajistas. Una nueva ejecución reconstruye ambos contadores desde cero.
Las llamadas se ejecutan en cada vela; la condición para incrementar está dentro de la función. En una función con estado, el orden y la frecuencia de las llamadas forman parte del funcionamiento. Si solo la llamas en algunas velas, su historial avanza únicamente en esas llamadas.
Si necesitas un único contador compartido, llama una vez a la función y utiliza la variable del resultado en varios sitios. Un segundo lugar de llamada implica otro contador separado.
Varias instrucciones en una función
Si necesitas un resultado intermedio, puedes calcularlo antes de return:
def midpoint(top: float, bottom: float) -> float:
total = top + bottom
return total / 2total es una variable local. Al terminar la llamada no se convierte en una variable de main(). Solo devolvemos el resultado de la función.
Pruébalo por tu cuenta
Añade una función midpoint y crea un indicador superpuesto que dibuje el punto medio de las velas. Usa top y bottom para recibir máximo y mínimo.
Solución del ejercicio
Escribe la definición anterior de midpoint antes de main(). Pon overlay=True en el decorador y utiliza este cuerpo:
center = midpoint(high, low)
plot(center, "Midpoint", color=color.blue, linewidth=2)Puedes eliminar los cálculos y dibujos de las dos líneas anteriores en esta versión. Elimina también las importaciones que ya no uses.
Explica la llamada
Para price_range(close, open), explica qué valor va a cada parámetro, qué ocurre en return y dónde recibe main() el resultado. Puedes seguir estos tres pasos también con llamadas más largas.