Suas próprias funções
Divida o código em cálculos com nome e reutilize-os em vários lugares.
Você já conhece o cálculo da amplitude de um candle: high - low. Se precisar dele em vários cálculos, pode colocá-lo em uma função própria. A chamada pode deixar claro o objetivo do cálculo, e a fórmula fica em um único lugar.
Entradas e resultado de uma função
def price_range(top: float, bottom: float) -> float:
return top - bottomdef define uma função. Seu nome é price_range. Ela espera duas entradas, chamadas top e bottom. Ambas são números que podem conter uma parte fracionária.
-> float indica o tipo do resultado. return calcula e devolve o resultado, encerrando aquela chamada de função.
O resultado de price_range(105, 100) é 5. A função não sabe se os números são preços e não escolhe um candle. Quem faz a chamada fornece os dois valores.
O corpo da função começa com quatro espaços de indentação. Neste exemplo, a definição fica ao lado de main(), no nível mais externo do arquivo. Por isso, passamos como parâmetros os dois preços necessários ao cálculo. Você também pode criar uma função dentro de main(); vamos experimentar isso adiante.
O escopo de um nome
top e bottom são parâmetros disponíveis apenas dentro de price_range. main() não pode acessá-los diretamente. Nela, você usa o resultado devolvido.
O escopo de um nome indica em qual parte do código ele está disponível. Duas funções diferentes podem usar os mesmos nomes de parâmetros sem que eles se tornem uma única variável compartilhada.
Definida no nível mais externo do arquivo, price_range não acessa automaticamente as variáveis locais de main(). Porém, pode usar nomes importados no início do arquivo, como close. Portanto, o local da definição também influencia quais nomes uma função consegue acessar.
A definição e a chamada de uma função são coisas diferentes. A definição estabelece a regra; a chamada a executa com valores concretos.
O programa completo
Crie o arquivo price_functions.py. Nele, a mesma função calcula a amplitude total do candle e a variação com sinal entre abertura e fechamento:
"""
@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)Execute com os dados de demonstração. A amplitude total não é negativa, pois a máxima do candle é pelo menos igual à mínima. Já a variação entre abertura e fechamento pode ser negativa. A função não modifica os preços recebidos; apenas devolve a diferença entre os dois números.

Neste exemplo, a função é curta e fácil de conferir à mão. Em programas maiores, você também pode dar nomes a tarefas mais complexas, mas não é obrigatório criar uma função para cada soma.
A ordem dos parâmetros
A chamada price_range(high, low) passa high para o parâmetro top e low para bottom. Se você os inverter, o sinal da subtração também será invertido.
Também é possível informar o nome do parâmetro:
full_range = price_range(top=high, bottom=low)Essa forma é mais longa, mas pode facilitar a leitura de uma chamada mais complexa. O nome fornecido precisa corresponder exatamente ao nome do parâmetro na função.
Uma função dentro de main()
Uma função definida dentro de outra também pode acessar as variáveis da função que a envolve. Isso é útil quando vários cálculos usam a mesma configuração fornecida como input.
Vamos suavizar as duas linhas do programa anterior com uma média móvel. Definiremos o comprimento da média em um único input, usado pelos dois cálculos. A função interna recebe apenas os dois preços; ela encontra o comprimento entre os parâmetros de main().
No arquivo price_functions.py, mantenha a função externa price_range e o decorador do indicador. Acrescente input e ta à importação de pynecore.lib e substitua a definição 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 é o input inteiro que você já conhece. A função interna average_difference recebe dois parâmetros: a chamada fornece os valores de top e bottom. Ela lê length na função main() que a envolve; por isso, você não precisa informar esse valor novamente nas chamadas.
A definição da função interna começa com quatro espaços, pois faz parte de main(). Seu próprio corpo, a linha de return, começa com oito espaços. O nome average_difference está disponível dentro de main(); ele não fica disponível no nível mais externo do arquivo.
price_range calcula a diferença entre os preços; ta.sma devolve a média móvel dessa diferença. Assim, a linha azul mostra a média da amplitude total dos candles; a laranja, a média da variação com sinal entre abertura e fechamento. As duas chamadas são executadas em cada candle, mantendo o cálculo das duas médias contínuo.
Execute o programa modificado e abra a tela Inputs pela engrenagem. Altere Average length de 5 para 20 e clique em Save. O gráfico aberto é recalculado automaticamente, e as duas linhas passam a usar médias de vinte candles. Você não precisou alterar as duas chamadas da função interna. Ao definir o comprimento como 1, obtém novamente as duas linhas do programa original.
Esse acesso às variáveis externas é chamado de closure. Ler o valor não significa que uma atribuição interna possa sobrescrever automaticamente a variável externa. Neste exemplo, apenas lemos o valor de length.
Se estiver lendo código traduzido de Pine Script: o PyneComp cria as funções dentro de main(). As variáveis definidas no nível principal de Pine Script também vão para dentro de main(), permitindo que as funções internas as acessem. É por isso que o código traduzido tem essa organização. Ao escrever seu próprio Pyne code, você pode usar as duas formas: uma função independente que recebe parâmetros ou uma função interna que também acessa as variáveis ao redor.
Na versão com parâmetros, todos os valores passados ficam visíveis na chamada, e é fácil usar a mesma função com outro par de preços. Uma função interna pode ser conveniente quando vários cálculos usam as mesmas variáveis criadas em main().
Uma função própria também pode ter estado
price_range calcula apenas com os valores recebidos. Ela não tem variáveis preservadas entre candles nem usa histórico. Por isso, você pode chamá-la a qualquer momento com dois valores.
Já average_difference, acima, usa estado: ta.sma, chamada dentro dela, calcula com valores anteriores. Você também pode criar variáveis Persistent ou Series diretamente em uma função própria.
Uma função contadora de eventos
Vamos contar separadamente os candles de alta e de baixa. Para isso, escreveremos uma função que recebe um valor lógico e guarda quantas vezes recebeu verdadeiro até agora.
No início do arquivo, importe Persistent:
from pynecore import PersistentEscreva a função abaixo antes de main(), no nível mais externo do arquivo:
def count_events(event: bool) -> int:
count: Persistent[int] = 0
if event:
count += 1
return countO valor inicial de count é zero. Quando event é verdadeiro, ele aumenta em um; quando é falso, preserva o valor. Nos dois casos, return devolve o total acumulado. Depois que a chamada termina, o valor da variável Persistent permanece para a próxima chamada.
Mantenha a configuração overlay=False. Substitua a definição e o corpo 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)Chamamos a mesma função em dois lugares diferentes do código. Cada local de chamada tem um estado count separado: o primeiro conta candles de alta; o segundo, candles de baixa. A primeira chamada não aumenta o contador da segunda.
Por exemplo, se os quatro primeiros candles tiverem a sequência abaixo, os valores serão:
| Candle | Relação entre abertura e fechamento | bullish_count | bearish_count |
|---|---|---|---|
| Primeiro | Fechamento mais alto | 1 | 0 |
| Segundo | Fechamento mais baixo | 1 | 1 |
| Terceiro | Os dois preços iguais | 1 | 1 |
| Quarto | Fechamento mais alto | 2 | 1 |
Execute com os dados de demonstração. A linha verde avança um passo apenas nos candles de alta; a vermelha, apenas nos de baixa. Uma nova execução reconstrói os dois contadores a partir de zero.
As duas chamadas são executadas em cada candle; a condição de incremento fica dentro da função. Em funções que usam estado, a ordem e a frequência das chamadas fazem parte do funcionamento. Se chamar uma função apenas em alguns candles, o histórico mantido dentro dela também avançará somente nessas chamadas.
Se precisar de um único contador compartilhado, chame a função uma vez e use a variável com o resultado em vários lugares. Um segundo local de chamada significa outro contador, separado.
Várias instruções em uma função
Se precisar de um resultado intermediário, você pode calculá-lo antes de return:
def midpoint(top: float, bottom: float) -> float:
total = top + bottom
return total / 2total é uma variável local. Ao terminar a chamada, ela não se torna uma variável de main(). Devolvemos da função apenas o resultado.
Tente por conta própria
Adicione uma função midpoint ao programa e crie um indicador sobreposto aos candles que desenhe seus pontos médios. Use os parâmetros top e bottom da função para receber a máxima e a mínima.
Solução do exercício
Escreva a definição de midpoint acima de main(). No decorador do indicador, use overlay=True; o corpo deve ser:
center = midpoint(high, low)
plot(center, "Midpoint", color=color.blue, linewidth=2)Você pode excluir desta versão os cálculos e desenhos das duas linhas anteriores. Remova também as importações não utilizadas.
Explique a chamada
Na linha price_range(close, open), explique qual valor vai para cada parâmetro, o que acontece na linha de return e onde main() recebe o resultado. Você também pode acompanhar esses três passos em uma chamada de função mais longa.