Tutorial de Introdução à Língua PINE do FMZ Quant

Autora:Lydia., Criado: 2022-09-23 15:23:34, Atualizado: 2024-02-27 16:47:41

Então vemos que nas três linhas desenhadas a, b e c, a linha b é um BAR mais lenta que a linha a, e a linha c é um BAR mais lenta que a linha b. A linha c é 2 BAR mais lenta que a linha a.

Podemos puxar o gráfico para a extrema esquerda e observar que na primeira linha K, ambos os valores de b e c são nulos (na). Isso ocorre porque, quando o script é executado na primeira linha K BAR, ele não existe quando se refere ao valor histórico de um ou dois períodos para frente, o que não existe. Portanto, precisamos ter cuidado ao escrever estratégias para verificar se a referência a dados históricos resultará em valores nulos. Se o valor nulo for usado descuidadamente, isso causará uma série de diferenças de cálculo e pode até afetar o BAR em tempo real.na, nzA partir de agora, o sistema de gestão dos recursos humanos será mais eficaz.nz, ```na`` nos nossos vídeos anteriores, você se lembra de que capítulo é?) tratar do caso de valores nulos, por exemplo:

close > nz(close[1], open)    // When referencing the historical value of the previous BAR of the close built-in variable, if it does not exist, the open built-in variable is used

Esta é uma forma de lidar com possíveis referências a valores nulos (na).

Prioridade do operador

Aprendemos muitos operadores na linguagem de Pine. Estes operadores formam expressões através de várias combinações com operandos. Então qual é a prioridade dessas operações ao avaliar em expressões? Assim como a aritmética que aprendemos na escola, a multiplicação e a divisão são calculadas primeiro, seguidas de adição e subtração. O mesmo vale para expressões na linguagem de Pine.

Prioridade Operadores
9 []
8 +-enotno operador unário
7 */%
6 +-no operador binário
5 ><>=<=
4 ==!=
3 and
2 or
1 ?:

Expresões de alta prioridade são calculadas primeiro, e se as prioridades são as mesmas, é avaliado da esquerda para a direita.()para enrolar a expressão para forçar a parte a ser avaliada primeiro.

Variaveis

Declaração variável

Nós já estudamos o conceito de marker antes, que é usado como o nome de uma variável, ou seja, uma variável é um marcador que possui um valor.

  • Modo de declaração: A primeira coisa a escrever ao declarar uma variável é o modo de declaração.

    1. Use a palavra-chavevar.
    2. Use a palavra-chavevarip.
    3. Não escreva nada.

    Ovarevarippalavras-chave foram realmente estudados em nosso capítulo anterior sobreAssignment OperatorsSe nada for escrito para o modo de declaração de variável, como a instrução:i = 1, como também mencionamos anteriormente, tal variável declarada e atribuída é executada em cada K-line BAR.

  • Tipo A linguagem Pine no FMZ não é rigorosa sobre tipos, e geralmente pode ser omitida. No entanto, para ser compatível com a estratégia de script no Trading View, as variáveis também podem ser declaradas com tipos. Por exemplo:

    int i = 0 
    float f = 1.1
    

    Os requisitos de tipo no Trading View são bastante rigorosos e um erro será notificado se o seguinte código for utilizado no Trading View:

    baseLine0 = na          // compile time error!
    
  • Marcador Os marcadores são nomes de variáveis. O nome de marcadores foi mencionado em capítulos anteriores, então você pode revisá-lo aqui:https://www.fmz.com/bbs-topic/9637#markers

Em resumo, declarar uma variável pode ser escrito como:

// [<declaration_mode>] [<type>] <marker> = value 
   declaration mode             type     marker      = value

O operador de atribuição é usado aqui:=atribui um valor a uma variável quando ela é declarada.if, for, while, ouswitche outras estruturas (estas palavras-chave estruturais e uso de declarações serão explicados em detalhes nos cursos subsequentes.

Aqui nos concentramos na função de entrada, que é uma função que vamos usar com frequência ao projetar e escrever estratégias.

Função de entrada:

input function, parameters: defval、title、tooltip、inline、group

A função de entrada no FMZ é um pouco diferente da do Trading View, mas esta função é usada como a entrada de atribuição de parâmetros de estratégia.

param1 = input(10, title="name of param1", tooltip="description for param1", group="group name A")
param2 = input("close", title="name of param2", tooltip="description for param2", group="group name A")
param3 = input(color.red, title="name of param3", tooltip="description for param3", group="group name B")
param4 = input(close, title="name of param4", tooltip="description for param4", group="group name B")
param5 = input(true, title="name of param5", tooltip="description for param5", group="group name C")

ma = ta.ema(param4, param1)
plot(ma, title=param2, color=param3, overlay=param5)

A função de entrada é frequentemente usada para atribuir valores a variáveis ao declará-las. A função de entrada no FMZ desenha controles para definir parâmetros de estratégia automaticamente na interface de estratégia do FMZ. Os controles suportados no FMZ atualmente incluem caixas de entrada numérica, caixas de entrada de texto, caixas suspensivas e caixas de seleção booleanas. E você pode definir o agrupamento de parâmetros de estratégia, definir a mensagem de texto de prompt de parâmetro e outras funções.

img

Introduzimos vários parâmetros principais da função de entrada:

  • defval: O valor padrão para as opções de parâmetros de estratégia definidas pela função de entrada, suportando variáveis, valores numéricos e strings integrados na linguagem Pine.
  • Título: O nome do parâmetro da estratégia exibido na interface da estratégia durante a negociação em tempo real/backtesting.
  • Tooltip: A informação de tooltip para parâmetros de estratégia, quando o mouse passa sobre o parâmetro de estratégia, a informação de texto da configuração do parâmetro será exibida.
  • Grupo: Nome do grupo de parâmetros de estratégia, que pode ser utilizado para parâmetros de estratégia.

Além da declaração e atribuição de variáveis individuais, há também uma maneira de declarar um grupo de variáveis e atribuí-los na linguagem Pine:

[Variable A, Variable B, Variable C] = function or structure, such as ```if```, ```for```, ```while``` or ```switch```

O mais comum é quando usamos ota.macdA função para calcular o indicador MACD, uma vez que o indicador MACD é um indicador de várias linhas, três conjuntos de dados são calculados.

[dif,dea,column] = ta.macd(close, 12, 26, 9)

plot(dif, title="dif")
plot(dea, title="dea")
plot(column, title="column", style=plot.style_histogram)

Podemos desenhar o gráfico MACD usando o código acima facilmente. Não só as funções embutidas podem retornar a várias variáveis, mas também as funções personalizadas escritas podem retornar a vários dados.

twoEMA(data, fastPeriod, slowPeriod) =>
    fast = ta.ema(data, fastPeriod)
    slow = ta.ema(data, slowPeriod)
    [fast, slow]

[ema10, ema20] = twoEMA(close, 10, 20)
plot(ema10, title="ema10", overlay=true)
plot(ema20, title="ema20", overlay=true)

O método de escrita de usar se e outras estruturas como atribuições de múltiplas variáveis também é semelhante à função personalizada acima, e você pode experimentá-lo se estiver interessado.

[ema10, ema20] = if true
    fast = ta.ema(close, 10)
    slow = ta.ema(close, 20)
    [fast, slow]

plot(ema10, title="ema10", color=color.fuchsia, overlay=true)
plot(ema20, title="ema20", color=color.aqua, overlay=true)

Estrutura da condição

Algumas funções não podem ser escritas no bloco de código local do ramo condicional, incluindo principalmente as seguintes funções:

barcolor ((), fill ((), hline ((), indicator ((), plot ((), plotcandle ((), plotchar ((), plotshape (()

O Trading View irá compilar com erros, FMZ não é tão restritivo, mas é recomendado seguir as especificações do Trading View.

strategy("test", overlay=true)
if close > open 
    plot(close, title="close")
else 
    plot(open, title="open")

se declaração

Exemplo de explicação:

var lineColor = na

n = if bar_index > 10 and bar_index <= 20
    lineColor := color.green
else if bar_index > 20 and bar_index <= 30
    lineColor := color.blue
else if bar_index > 30 and bar_index <= 40
    lineColor := color.orange
else if bar_index > 40
    lineColor := color.black
else 
    lineColor := color.red
    
plot(close, title="close", color=n, linewidth=5, overlay=true)
plotchar(true, title="bar_index", char=str.tostring(bar_index), location=location.abovebar, color=color.red, overlay=true)

Ponto-chave: Expressões usadas para julgamentos que retornam valores booleanos. Observe a indentada. Pode haver no máximo mais um ramo. Se todas as expressões de ramo não forem verdadeiras e não houver outro ramo, retorne na.

x = if close > open
    close
plot(x, title="x")

declaração de mudança

A instrução switch é também uma instrução estruturada por ramificação, que é usada para projetar diferentes caminhos a serem executados de acordo com certas condições.

  1. A instrução switch, como a instrução if, também pode retornar um valor.
  2. Ao contrário das instruções do switch em outras linguagens, quando uma construção do switch é executada, apenas um bloco local de seu código é executado, então a instrução break é desnecessária (ou seja, não há necessidade de escrever palavras-chave como break).
  3. Cada ramo do switch pode escrever um bloco de código local, a última linha deste bloco de código local é o valor de retorno (pode ser um tuple de valores). Retorna na se nenhum dos blocos de código local ramificados foram executados.
  4. A expressão na estrutura do interruptor determina a posição pode escrever uma cadeia, variável, expressão ou chamada de função.
  5. comutação permite especificar um valor de retorno que atua como o valor padrão a ser usado quando não há outro caso na estrutura para executar.

Existem duas formas de comutação, vamos olhar para os exemplos um por um para entender o seu uso.

  1. Aswitchcom expressões - exemplo de explicação:
// input.string: defval, title, options, tooltip
func = input.string("EMA", title="indicator name", tooltip="select the name of the indicator function to be used", options=["EMA", "SMA", "RMA", "WMA"])

// input.int: defval, title, options, tooltip
// param1 = input.int(10, title="period parameter")
fastPeriod = input.int(10, title="fastPeriod parameter", options=[5, 10, 20])
slowPeriod = input.int(20, title="slowPeriod parameter", options=[20, 25, 30])

data = input(close, title="data", tooltip="select the closing price, opening price, highest price...")
fastColor = color.red
slowColor = color.red

[fast, slow] = switch func
    "EMA" =>
        fastLine = ta.ema(data, fastPeriod)
        slowLine = ta.ema(data, slowPeriod)
        fastColor := color.red
        slowColor := color.red
        [fastLine, slowLine]
    "SMA" =>
        fastLine = ta.sma(data, fastPeriod)
        slowLine = ta.sma(data, slowPeriod)
        fastColor := color.green
        slowColor := color.green
        [fastLine, slowLine]
    "RMA" =>
        fastLine = ta.rma(data, fastPeriod)
        slowLine = ta.rma(data, slowPeriod)
        fastColor := color.blue
        slowColor := color.blue
        [fastLine, slowLine]
    =>
        runtime.error("error")
        
plot(fast, title="fast" + fastPeriod, color=fastColor, overlay=true)
plot(slow, title="slow" + slowPeriod, color=slowColor, overlay=true)

Aprendemos a função de entrada antes, aqui continuamos a aprender duas funções semelhantes à entrada:input.string, input.int functions. input.stringé usado para retornar uma cadeia, e oinput.intfunção é usada para retornar um valor inteiro. No exemplo, há um novo uso dooptionsParâmetro.optionsParâmetro pode ser passado uma matriz de valores opcionais, tais comooptions=["EMA", "SMA", "RMA", "WMA"]eoptions=[5, 10, 20]Neste exemplo (observe que um é tipo de string, o outro é tipo numérico). Desta forma, os controles na interface de estratégia não precisam inserir valores específicos, mas os controles se tornam caixas suspensivas para selecionar essas opções fornecidas no parâmetro de opções.

O valor da variável func é uma cadeia de caracteres, e a variável func é usada como expressão para switch (que pode ser uma variável, chamada de função ou expressão) para determinar qual ramo no switch é executado.runtime.error("error")função será executada, fazendo com que a estratégia lance uma exceção e pare.

Em nosso código de teste acima, após a última linha de runtime.error no bloco de código de ramificação padrão do switch, não adicionamos código como [na, na] para ser compatível com o valor de retorno. Este problema precisa ser considerado na Visualização de Negociação. Se o tipo for inconsistente, um erro será relatado. Mas no FMZ, como o tipo não é estritamente necessário, esse código de compatibilidade pode ser omitido. Portanto, não há necessidade de considerar a compatibilidade de tipo do valor de retorno de if e switch branches no FMZ.

strategy("test", overlay=true)
x = if close > open
    close
else
    "open"
plotchar(true, title="x", char=str.tostring(x), location=location.abovebar, color=color.red)

Nenhum erro será relatado no FMZ, mas um erro será relatado na vista de negociação.

  1. switchsem expressões

Em seguida, vamos olhar para outra maneira de usarswitch, isto é, sem expressões.

up = close > open     // up = close < open 
down = close < open 
var upOfCount = 0 
var downOfCount = 0 

msgColor = switch
    up  => 
        upOfCount += 1 
        color.green 
    down => 
        downOfCount += 1
        color.red

plotchar(up, title="up", char=str.tostring(upOfCount), location=location.abovebar, color=msgColor, overlay=true)
plotchar(down, title="down", char=str.tostring(downOfCount), location=location.belowbar, color=msgColor, overlay=true)

Como podemos ver no exemplo de código de teste, o switch corresponderá à execução do bloco de código local que é verdadeiro na condição de ramificação. Em geral, as condições de ramificação após uma instrução de switch devem ser mutuamente exclusivas. Ou seja, para cima e para baixo no exemplo não podem ser verdadeiras ao mesmo tempo. Como o switch só pode executar o bloco de código local de um ramo, se você estiver interessado, você pode substituir esta linha no código:up = close > open // up = close < openVocê verá que o ramo do switch só pode executar o primeiro ramo. Além disso, é necessário prestar atenção para não escrever chamadas de função no ramo do switch tanto quanto possível, a função não pode ser chamada em cada BAR pode causar alguns problemas de cálculo de dados (a menos que no exemplo de "switchcom expressões", o ramo de execução é determinista e não será alterado durante a operação da estratégia).

Estrutura do Loop

para declaração

return value = for count = start count to final count by step length
    statement                                            // Note: There can be break and continue in the statement
    statement                                            // Note: The last statement is the return value

A instrução for é muito simples de usar, o loop for pode finalmente retornar um valor (ou múltiplos valores, na forma de [a, b, c]). Como a variável atribuída à posição return value no pseudocode acima. A instrução for é seguida por uma variável count usada para controlar o número de loops, referir-se a outros valores, etc. A variável count é atribuída a contagem inicial antes do início do loop, em seguida, aumenta de acordo com a configuração de comprimento de passo e o loop pára quando a variável count é maior que a contagem final.

ObreakPalavra-chave utilizada no loop for: o loop pára quando obreakA declaração é executada. OcontinuePalavra-chave utilizada no loop for:continuese a instrução for executada, o loop ignorará o código apóscontinueA instrução for retorna o valor de retorno da última execução do loop e retorna null se nenhum código for executado.

Então vamos demonstrar com um exemplo simples:

ret = for i = 0 to 10       // We can increase the keyword by to modify the step length, FMZ does not support reverse loops such as i = 10 to 0 for now
    // We can add condition settings, use continue to skip, use break to jump out
    runtime.log("i:", i)
    i                       // If this line is not written, it will return null because there is no variable to return
    
runtime.log("ret:", ret)
runtime.error("stop")

para... em declaração

Ofor ... inA declaração tem duas formas, vamos ilustrá-los no seguinte pseudocódigo.

return value = for array element in array 
    statement                        // Note: There can be break and continue in the statement
    statement                        // Note: The last statement is the return value
Return value = for [index variable, array element corresponding to index variable] in array
    statement                        // Note: There can be break and continue in the statement
    statement                        // Note: The last statement is the return value 

Podemos ver que a principal diferença entre as duas formas é o conteúdo que segue a palavra-chave for, uma é usar uma variável como uma variável que se refere aos elementos da matriz, a outra é usar uma estrutura contendo variáveis de índice, tuplas de variáveis de elementos de matriz como referências.

testArray = array.from(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
for ele in testArray            // Modify it to the form of [i, ele]: for [i, ele] in testArray, runtime.log("ele:", ele, ", i:", i)
    runtime.log("ele:", ele)

runtime.error("stop")

Quando ele precisa usar o índice, use a gramáticafor [i, ele] in testArray.

Aplicação de circuitos

Podemos usar as funções embutidas fornecidas na linguagem Pine para completar alguns dos cálculos lógicos do loop, ou escritos usando a estrutura do loop diretamente ou processados usando as funções embutidas.

  1. Calcular o valor médio

No caso de projetos com estrutura de circuito:

length = 5
var a = array.new(length)
array.push(a, close)

if array.size(a) >= length
	array.remove(a, 0)
	
sum = 0 	
for ele in a
    sum += ele 

avg = sum / length
plot(avg, title="avg", overlay=true)

O exemplo usa um loop for para calcular a soma e, em seguida, calcular o valor médio.

Calcular a média móvel directamente utilizando a função integrada:

plot(ta.sma(close, length), title="ta.sma", overlay=true)

Utilize a função integradata.smaO cálculo da média móvel pode ser feito diretamente para calcular o indicador da média móvel. Obviamente, é mais simples usar a função incorporada para calcular a média móvel. Ao comparar no gráfico, pode-se ver que os resultados calculados são exatamente os mesmos.

  1. Resumo

Ainda usamos o exemplo acima para ilustrar.

No caso de projetos com estrutura de circuito:

length = 5
var a = array.new(length)
array.push(a, close)

if array.size(a) >= length
	array.remove(a, 0)
	
sum = 0 	
for ele in a
    sum += ele 

avg = sum / length
plot(avg, title="avg", overlay=true)
plot(ta.sma(close, length), title="ta.sma", overlay=true)

Para calcular a soma de todos os elementos em uma matriz, podemos usar um loop para processá-lo, ou usar a função embutidaarray.sumPara calcular. Calcular a soma directamente utilizando a função embutida:

length = 5
var a = array.new(length)
array.push(a, close)

if array.size(a) >= length
	array.remove(a, 0)
	
plot(array.sum(a) / length, title="avg", overlay=true)
plot(ta.sma(close, length), title="ta.sma", overlay=true)

Podemos ver os dados calculados é exatamente o mesmo que é exibido no gráfico usando gráfico.

Então, por que projetar loops quando podemos fazer tudo isso com funções embutidas?

  1. Para algumas operações e cálculos para matrizes.
  2. Para rever o histórico, por exemplo, para descobrir quantos pontos altos passados são maiores que o ponto alto do BAR atual.
  3. Quando as funções embutidas da linguagem Pine não podem completar cálculos para BARs passados.

enquanto statemnet

OwhileA instrução mantém o código na seção do loop executando até que a condição de julgamento na estrutura while seja falsa.

return value = while judgment condition
    statement                    // Note: There can be break and continue in the statement
    statement                    // Note: The last statement is the return value

Outras regras do while são semelhantes às do loop for. A última linha do bloco de código local do corpo do loop é o valor de retorno, que pode retornar múltiplos valores. Execute o loop quando a condição loop é verdadeira e pare o loop quando a condição é falsa. As instruções break e continue também podem ser usadas no corpo do loop.

Continuaremos a usar o exemplo de cálculo de médias móveis para demonstração:

length = 10

sma(data, length) => 
    i = 0 
    sum = 0 
    while i < 10 
        sum += data[i]
        i += 1
        sum / length

plot(sma(close, length), title="sma", overlay=true)
plot(ta.sma(close, length), title="ta.sma", overlay=true)

Podemos ver que o while loop também é muito simples de usar, e também é possível projetar alguma lógica de cálculo que não pode ser substituída pelas funções embutidas, como calcular fatorial:

counter = 5
fact = 1

ret = while counter > 0
    fact := fact * counter
    counter := counter - 1
    fact

plot(ret, title="ret")  // ret = 5 * 4 * 3 * 2 * 1

Arrays

A definição de matrizes na linguagem Pine é semelhante à de outras linguagens de programação. As matrizes Pines são matrizes unidimensionais. Normalmente são usadas para armazenar uma série contínua de dados. Os dados individuais armazenados na matriz são chamados de elemento da matriz, e os tipos desses elementos podem ser: inteiro, ponto flutuante, cadeia, valor de cor, valor booleano. A linguagem Pine no FMZ não é muito rigorosa sobre tipos, e pode até armazenar cadeias e números em uma matriz ao mesmo tempo.[]para se referir a um elemento na matriz, precisamos usar as funçõesarray.get()earray.set()A ordem de índice dos elementos da matriz é que o índice do primeiro elemento da matriz é 0, e o índice do próximo elemento é incrementado por 1.

Ilustramo-lo com um código simples:

var a = array.from(0)
if bar_index == 0 
    runtime.log("current value a on BAR:", a, ", a on the last BAR, i.e. the value of a[1]:", a[1])
else if bar_index == 1 
    array.push(a, bar_index)
    runtime.log("current value a on BAR:", a, ", a on the last BAR, i.e. the value of a[1]:", a[1])
else if bar_index == 2
    array.push(a, bar_index)
    runtime.log("current value a on BAR:", a, ", a on the last BAR, i.e. the value of a[1]:", a[1], ", a on the last second BAR, i.e. the value of a[2]:", a[2])
else if bar_index == 3 
    array.push(a, bar_index)
    runtime.log("current value a on BAR:", a, ", a on the last BAR, i.e. the value of a[1]:", a[1], ", a on the last second BAR, i.e. the value of a[2]:", a[2], ", a on the last third BAR, i.e. the value of a[3]:", a[3])
else if bar_index == 4 
    // Obtain elements by index using array.get, modify elements by index using array.set
    runtime.log("Before array modification:", array.get(a, 0), array.get(a, 1), array.get(a, 2), array.get(a, 3))
    array.set(a, 1, 999)
    runtime.log("After array modification:", array.get(a, 0), array.get(a, 1), array.get(a, 2), array.get(a, 3))

Declarar uma matriz

Utilizaçãoarray<int> a, float[] bpara declarar uma matriz ou apenas declarar uma variável que pode ser atribuída a uma matriz, por exemplo:

array<int> a = array.new(3, bar_index)
float[] b = array.new(3, close)
c = array.from("hello", "fmz", "!")
runtime.log("a:", a)
runtime.log("b:", b)
runtime.log("c:", c)
runtime.error("stop")

As variáveis de matriz são inicializadas usando oarray.newearray.fromHá também muitas funções relacionadas com tipos semelhantes aarray.newna língua do pinheiro:array.new_int(), array.new_bool(), array.new_color(), array.new_string(), etc.

A palavra-chave var também funciona com o modo de declaração de matriz.

var a = array.from(0)
b = array.from(0)

if bar_index == 1
    array.push(a, bar_index)
    array.push(b, bar_index)
else if bar_index == 2 
    array.push(a, bar_index)
    array.push(b, bar_index)
else if barstate.islast
    runtime.log("a:", a)
    runtime.log("b:", b)
    runtime.error("stop")

Pode-se ver que as alterações da matriz a foram continuamente determinadas e não foram reiniciadas.barstate.islasté verdade, ainda há apenas um elemento impresso com um valor de 0.

Ler e escrever elementos em uma matriz

Use array.get para obter o elemento na posição de índice especificada na matriz e use array.set para modificar o elemento na posição de índice especificada na matriz.

O primeiro parâmetro de array.get é a matriz a ser processada, e o segundo parâmetro é o índice especificado. O primeiro parâmetro para array.set é a matriz a ser processada, o segundo parâmetro é o índice especificado e o terceiro parâmetro é o elemento a ser escrito.

Usamos o seguinte exemplo simples para ilustrar:

lookbackInput = input.int(100)
FILL_COLOR = color.green

var fillColors = array.new(5)
if barstate.isfirst
    array.set(fillColors, 0, color.new(FILL_COLOR, 70))
    array.set(fillColors, 1, color.new(FILL_COLOR, 75))
    array.set(fillColors, 2, color.new(FILL_COLOR, 80))
    array.set(fillColors, 3, color.new(FILL_COLOR, 85))
    array.set(fillColors, 4, color.new(FILL_COLOR, 90))

lastHiBar = - ta.highestbars(high, lookbackInput)
fillNo = math.min(lastHiBar / (lookbackInput / 5), 4)

bgcolor(array.get(fillColors, int(fillNo)), overlay=true)
plot(lastHiBar, title="lastHiBar")
plot(fillNo, title="fillNo")

O exemplo inicializa a cor de base verde, declara e inicializa uma matriz para armazenar cores, e então atribui diferentes valores de transparência para as cores (usando ocolor.newO nível de cor é calculado pela distância do BAR atual do valor máximo de alto em 100 períodos de lookback. Quanto mais próxima a distância do valor máximo de HIGH nos últimos 100 ciclos de lookback, maior a classificação e mais escuro (menor transparência) o valor de cor correspondente.

Iterar através de elementos de matriz

Como iterar através de uma matriz, podemos usar as instruções for/for in/while que aprendemos antes.

a = array.from(1, 2, 3, 4, 5, 6)

for i = 0 to (array.size(a) == 0 ? na : array.size(a) - 1)
    array.set(a, i, i)
    
runtime.log(a)
runtime.error("stop")
a = array.from(1, 2, 3, 4, 5, 6)

i = 0
while i < array.size(a)
    array.set(a, i, i)
    i += 1

runtime.log(a)
runtime.error("stop")
a = array.from(1, 2, 3, 4, 5, 6)

for [i, ele] in a 
    array.set(a, i, i)

runtime.log(a)
runtime.error("stop")

Estes três métodos de travessia têm os mesmos resultados de execução.

As matrizes podem ser declaradas no escopo global de um script, ou no escopo local de uma função ou se for ramificada.

Referências de dados históricos

Para o uso de elementos em matrizes, as seguintes maneiras são equivalentes. Podemos ver pelo exemplo a seguir que dois conjuntos de linhas são desenhados no gráfico, dois em cada conjunto, e as duas linhas em cada conjunto têm exatamente o mesmo valor.

a = array.new_float(1)
array.set(a, 0, close)
closeA1 = array.get(a, 0)[1]
closeB1 = close[1]
plot(closeA1, "closeA1", color.red, 6)
plot(closeB1, "closeB1", color.black, 2)

ma1 = ta.sma(array.get(a, 0), 20)
ma2 = ta.sma(close, 20)
plot(ma1, "ma1", color.aqua, 6)
plot(ma2, "ma2", color.black, 2)

Funções de adição e exclusão de matrizes

  1. Funções relacionadas com a adição de matrizes:

array.unshift(), array.insert(), array.push().

  1. Funções relacionadas com a operação de eliminação de matrizes:

array.remove(), array.shift(), array.pop(), array.clear().

Usamos o seguinte exemplo para testar essas funções de adição e exclusão de operações de matriz.

a = array.from("A", "B", "C")
ret = array.unshift(a, "X")
runtime.log("array a:", a, ", ret:", ret)

ret := array.insert(a, 1, "Y")
runtime.log("array a:", a, ", ret:", ret)

ret := array.push(a, "D")
runtime.log("array a:", a, ", ret:", ret)

ret := array.remove(a, 2)
runtime.log("array a:", a, ", ret:", ret)

ret := array.shift(a)
runtime.log("array a:", a, ", ret:", ret)

ret := array.pop(a)
runtime.log("array a:", a, ", ret:", ret)

ret := array.clear(a)
runtime.log("array a:", a, ", ret:", ret)

runtime.error("stop")

Aplicação de adições, exclusões: matrizes como filas

Podemos construir uma estrutura de dados queue usando matrizes e algumas funções de adição e exclusão de matrizes. As filas podem ser usadas para calcular a média móvel dos preços de ticks. Alguém pode perguntar, Por que devemos construir uma estrutura de fila?

Uma fila é uma estrutura que é frequentemente usada no campo da programação, as características de uma fila são:

O elemento que entra na fila primeiro, sai da fila primeiro.

Desta forma, ele garante que os dados na fila são os dados mais recentes, e que o comprimento da fila não se expandirá indefinidamente.

No exemplo a seguir, usamos uma estrutura de fila para registrar o preço de cada tick, calcular o preço médio móvel no nível do tick e, em seguida, compará-lo com a média móvel no nível da linha K de 1 minuto.

strategy("test", overlay=true)

varip a = array.new_float(0)
var length = 10

if not barstate.ishistory
    array.push(a, close)

    if array.size(a) > length
        array.shift(a)

sum = 0.0
for [index, ele] in a 
    sum += ele

avgPrice = array.size(a) == length ? sum / length : na

plot(avgPrice, title="avgPrice")
plot(ta.sma(close, length), title="ta.sma")

Observe que ao declarar matriz a, especificamos o modo de declaração e usar a palavra-chavevariantDesta forma, cada mudança de preço será registrada na matriz a.

Funções de cálculo e operação de matriz comumente utilizadas

Calcular funções de correlação:

array.avg()Calcula o valor médio de todos os elementos de uma matriz,array.min()Calcula o elemento mais pequeno de uma matriz,array.max()Calcula o elemento mais pequeno de uma matriz,array.stdev()Calcula o desvio-padrão de todos os elementos de uma matriz,array.sum()Calcula o desvio padrão de todos os elementos de uma matriz.

Funções relacionadas com a operação:array.concat()para fundir ou concatenar duas matrizes.array.copy()para copiar a matriz.array.joinPara concatenates todos os elementos de uma matriz em uma cadeia.array.sort()para ordenar por ordem ascendente ou descendente.array.reverse()para inverter a matriz.array.slice()para cortar a matriz.array.includes()para julgar o elemento.array.indexof()Para retornar ao índice da primeira ocorrência do valor passado como parâmetro.array.lastindexof()para encontrar a última ocorrência do valor.

Exemplos de teste de funções relacionadas com o cálculo de matriz:

a = array.from(3, 2, 1, 4, 5, 6, 7, 8, 9)

runtime.log("Arithmetic average of the array a:", array.avg(a))
runtime.log("The minimum element in the array a:", array.min(a))
runtime.log("The maximum element in the array a:", array.max(a))
runtime.log("Standard deviation in array a:", array.stdev(a))
runtime.log("Sum of all elements of the array a:", array.sum(a))
runtime.error("stop")

Estas são funções de cálculo de matriz comumente usadas.

Exemplos de funções relacionadas com a operação:

a = array.from(1, 2, 3, 4, 5, 6)
b = array.from(11, 2, 13, 4, 15, 6)

runtime.log("array a: ", a, ", array b: ", b)
runtime.log("array a, array b is concatenated with:", array.concat(a, b))
c = array.copy(b)

runtime.log("Copy an array b and assign it to the variable c, variable c:", c)

runtime.log("use array.join to process the array c, add the symbol + to the middle of each element, concatenating all elements results in a string:", array.join(c, "+"))
runtime.log("Sort the array b, in order from smallest to largest, using the parameter order.ascending:", array.sort(b, order.ascending))     // array.sort function modifies the original array
runtime.log("Sort the array b, in order from largest to smallest, using the parameter order.descending:", array.sort(b, order.descending))   // array.sort function modifies the original array

runtime.log("array a:", a, ", array b:", b)
array.reverse(a)   // This function modifies the original array
runtime.log("reverse the order of all elements in the array a, after reversing, the array a is:", a)    

runtime.log("Intercept array a, index 0~index 3, and follow the rule of left-closed and right-open interval:", array.slice(a, 0, 3))
runtime.log("Search for element 11 in array b:", array.includes(b, 11))
runtime.log("Search for element 100 in array a:", array.includes(a, 100))
runtime.log("Connect array a and array b, and search the index position of the first occurrence of element 2:", array.indexof(array.concat(a, b), 2), " , observe array.concat(a, b):", array.concat(a, b))
runtime.log("Connect array a and array b, and search the index position of the last occurrence of element 6:", array.lastindexof(array.concat(a, b), 6), " , observe array.concat(a, b):", array.concat(a, b))

runtime.error("stop")

Funções

Funções personalizadas

A linguagem Pine pode ser projetada com funções personalizadas. Em geral, as seguintes regras são aplicadas às funções personalizadas na linguagem Pine:

  1. Todas as funções são definidas no escopo global do script. Uma função não pode ser declarada dentro de outra função.
  2. As funções não têm permissão para se chamarem em seu próprio código (recursividade).
  3. Em princípio, todas as funções de desenho integradas na linguagem PINE (barcolor(), fill(), hline(), plot(), plotbar(), plotcandle()) não pode ser chamado em funções personalizadas.
  4. As funções podem ser escritas como uma única linha ou várias linhas. O valor de retorno da última instrução é o valor de retorno da função atual, que pode ser devolvido em forma de tupla.

Nós também usamos as funções personalizadas por muitas vezes em nossos tutoriais anteriores, tais como aqueles projetados como uma única linha:

barIsUp() => close > open

Se o BAR atual é uma reta positiva quando a função retorna.

Funções personalizadas concebidas para ser múltiplas linhas:

sma(data, length) => 
    i = 0 
    sum = 0 
    while i < 10 
        sum += data[i]
        i += 1
        sum / length

plot(sma(close, length), title="sma", overlay=true)
plot(ta.sma(close, length), title="ta.sma", overlay=true)

Nós usamos uma função personalizada para realizar uma função de cálculo média sma.

Além disso, dois exemplos de funções personalizadas que podemos retornar:

twoEMA(data, fastPeriod, slowPeriod) =>
    fast = ta.ema(data, fastPeriod)
    slow = ta.ema(data, slowPeriod)
    [fast, slow]

[ema10, ema20] = twoEMA(close, 10, 20)
plot(ema10, title="ema10", overlay=true)
plot(ema20, title="ema20", overlay=true)

Uma função pode calcular a linha rápida, linha lenta e duas médias EMA.

Funções integradas

As funções integradas podem ser facilmente encontradas noDocumento de script do FMZ PINE.

Classificação de funções embutidas na língua Pine:

  1. Função de processamento de stringstr. series.
  2. A função de processamento de valor de corcolor. series.
  3. Função de entrada de parâmetrosinput. series.
  4. Função de cálculo do indicadorta. series.
  5. Função de desenhoplot. series.
  6. Função de manipulação de matrizarray. series.
  7. Funções relacionadas com o comérciostrategy. series.
  8. Funções relacionadas com operações matemáticasmath. series.
  9. Outras funções (manipulação do tempo, funções de desenho de séries não gráficas,request.Funções de série, funções de manipulação de tipos, etc.)

Funções comerciais

Ostrategy.série de funções são funções que usamos frequentemente no design de estratégias, e essas funções estão intimamente relacionadas com a execução de operações de negociação quando a estratégia está sendo executada especificamente.


  1. strategy.entry

strategy.entryfunção é uma função mais importante quando escrevemos uma estratégia para colocar um pedido, vários parâmetros importantes para a função são:id, direction, qty, when, etc.

Parâmetros:

  • idO identificador pode ser utilizado para cancelar, modificar ordens e fechar posições.
  • direction: Se a direcção da ordem for longa (comprar), passar na variável incorporadastrategy.long, e se você quiser ficar curto (vender), passe na variávelstrategy.short.
  • qty: Especificar o montante das ordens a realizar, se este parâmetro não for transmitido, será utilizado o montante por defeito das ordens.
  • when: Condição de execução, pode especificar este parâmetro para controlar se esta operação de ordem actual é desencadeada ou não.
  • limit: Especificar o preço limite da ordem.
  • stopPreço stop loss.

Os pormenores específicos da execução dostrategy.entryA função é controlada pelas definições dos parâmetros quando ostrategyfunção é chamada, e também pode ser controlada pelo [Pine Language Trade-Class Library Template Arguments](https://www.fmz.com/bbs-topic/9293#template-- arguments-of-pine-language-trade-class-library) configuração de controle, - argumentos do modelo de biblioteca de classe de negócios da linguagem Pine controlam mais detalhes da transação, você pode verificar a documentação vinculada para obter detalhes.

Concentramos-nos nopyramiding, default_qty_valueParâmetros nostrategyUtilizamos o seguinte código para testar:

/*backtest
start: 2022-07-03 00:00:00
end: 2022-07-09 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/

strategy(title = "open long example", pyramiding = 3, default_qty_value=0.1, overlay=true)

ema10 = ta.ema(close, 10)

findOrderIdx(idx) =>
    if strategy.opentrades == 0 
        false 
    else 
        ret = false 
        for i = 0 to strategy.opentrades - 1 
            if strategy.opentrades.entry_id(i) == idx
                ret := true 
                break
        ret 
        

if not findOrderIdx("long1")
    strategy.entry("long1", strategy.long)

if not findOrderIdx("long2")
    strategy.entry("long2", strategy.long, 0.2, when = close > ema10)

if not findOrderIdx("long3")
    strategy.entry("long3", strategy.long, 0.2, limit = low[1])
    strategy.entry("long3", strategy.long, 0.3, limit = low[1])

if not findOrderIdx("long4")
    strategy.entry("long4", strategy.long, 0.2)

plot(ema10, title="ema10", color=color.red)

A parte no início do código/* backtest... */é uma configuração de backtest, que é usada para registrar o tempo de configuração de backtest e outras informações nesse momento para depuração, não o código de inicialização.

No código:strategy(title = "open long example", pyramiding = 3, default_qty_value=0.1, overlay=true), quando especificamos opyramidingO parâmetro como 3, definimos o número máximo de negócios na mesma direção para 3.strategy.entryComo nós também especificamos odefault_qty_valueParâmetro para ser 0,1, estestrategy.entryoperação com IDlong1tem um tamanho de ordem por defeito de 0,1.strategy.entrychamada função quando especificamos odirectioncomostrategy.long, então as ordens de teste backtest são todas ordens de compra.

Observe que a operação de ordemstrategy.entry("long3", ...no código é chamado duas vezes, para o mesmo ID:long3, o primeirostrategy.entryA operação de encomenda não foi preenchida, e a segunda chamada para ostrategy.entryA função principal da função foi modificar a ordem para este ID (os dados mostrados no teste de backtest também mostram que a quantidade de ordem para este limite de ordem foi modificada para 0,3).strategy.entryFunção para colocar ordens de acordo com o ID long3, em seguida, as posições de ordem serão acumuladas no ID long3.


  1. strategy.close

Ostrategy.closeA função é utilizada para fechar a posição de entrada com o ID de identificação especificado. Os principais parâmetros são:id, when, qty, qty_percent.

Parâmetros:

  • id: O ID de entrada que precisa ser fechado é o ID que especificamos quando abrimos uma posição usando uma função de ordem de entrada, comostrategy.entry.
  • when: Condições de execução.
  • qty: Número de posições fechadas.
  • qty_percent: Percentagem de posições fechadas.

Vamos familiarizar-nos com os pormenores da utilização desta função através de um exemplo: O/*backtest ... */no código está a informação de configuração paraFMZ.COMbacktest, você pode excluí-lo e definir o mercado, variedade, faixa de tempo e outras informações que você precisa para testar.

/*backtest
start: 2022-07-03 00:00:00
end: 2022-07-09 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/

strategy("close Demo", pyramiding=3)

var enableStop = false 
if enableStop
    runtime.error("stop")

strategy.entry("long1", strategy.long, 0.2)
if strategy.opentrades >= 3 
    strategy.close("long1")                   // Multiple entry orders, no qty parameters specified, close all
    // strategy.close()                          // Without specifying the id parameter, the current position will be closed
    // strategy.close("long2")                   // If a non-existent id is specified then nothing is done
    // strategy.close("long1", qty=0.15)         // Specify qty parameters to close a position
    // strategy.close("long1", qty_percent=50)   // qty_percent is set to 50 to close 50% of the positions marked by long1
    // strategy.close("long1", qty_percent=80, when=close<open)  // Specify the parameter when, change it to close>open and it won't trigger
    enableStop := true

A estratégia de ensaio mostra três entradas longas consecutivas com o ID de entrada long1 e, em seguida, utiliza diferentes parâmetros dostrategy.closeA função para definir os diferentes resultados do backtest ao fechar uma posição.strategy.closeA função não tem parâmetros para especificar o preço da ordem para fechar a posição, esta função é utilizada principalmente para fechar a posição imediatamente ao preço de mercado corrente.


  1. strategy.close_all

A funçãostrategy.close_allé usado para fechar todas as posições atuais, porque as posições da linguagem de script Pine só pode ter uma direção, ou seja, se houver um sinal desencadeado na direção oposta da posição atual vai fechar a posição atual e, em seguida, abri-lo de acordo com o gatilho de sinal.strategy.close_allserá fechado todas as posições na direção atual quando é chamado.strategy.close_allfunção é:when.

Parâmetros:

  • when: Condições de execução.

Vamos usar um exemplo para observar:

/*backtest
start: 2022-07-03 00:00:00
end: 2022-07-09 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/

strategy("closeAll Demo")

var enableStop = false 
if enableStop
    runtime.error("stop")

strategy.entry("long", strategy.long, 0.2, when=strategy.position_size==0 and close>open)
strategy.entry("short", strategy.short, 0.3, when=strategy.position_size>0 and close<open)

if strategy.position_size < 0 
    strategy.close_all()
    enableStop := true 

O código de ensaio começa com um número de posição de 0 (ou seja,strategy.position_size==0é verdade), então quando as condições definidas pelo quando parâmetro são atendidos, apenas ostrategy.entryA função de entrada com ID long é executada.strategy.position_sizeé maior que 0, então a função de entrada com ID short pode ser executada, uma vez que a posição longa atual é mantida, este sinal de reversão de curto prazo neste momento resultará no fechamento da posição longa e, em seguida, abrindo a posição curta na direção oposta.strategy.position_size < 0, ou seja, quando se mantém uma posição curta, todas as posições na direcção de detenção corrente serão fechadas.enableStop := true. Pára a execução da estratégia para que o log possa ser observado.

Pode-se encontrar que a funçãostrategy.close_allNão tem parâmetros para especificar o preço de encerramento da ordem, esta função é utilizada principalmente para fechar imediatamente a posição ao preço de mercado corrente.


  1. strategy.exit

Ostrategy.exitA função é utilizada para fechar uma posição de entrada.strategy.closeestrategy.close_allO preço de mercado é o preço de venda de uma posição.strategy.exitA função fechará a posição de acordo com as definições dos parâmetros.

Parâmetros:

  • idO número de ordem de encerramento da ordem.
  • from_entryO valor da posição deve ser calculado em conformidade com o modelo de referência.
  • qty: Número de posições fechadas.
  • qty_percent: Percentagem de posições fechadas, intervalo: 0 ~ 100.
  • profit: Objetivo de lucro, expresso em pontos.
  • lossO valor da posição em risco deve ser calculado em função da posição em risco.
  • limit: Objetivo de lucro, especificado por preço.
  • stopO valor da posição em risco deve ser calculado em função da posição em risco.
  • when: Condições de execução.

Usar uma estratégia de teste para compreender o uso dos parâmetros.

/*backtest
start: 2022-07-03 00:00:00
end: 2022-07-09 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
args: [["RunMode",1,358374],["ZPrecision",0,358374]]
*/

strategy("strategy.exit Demo", pyramiding=3)

varip isExit = false 

findOrderIdx(idx) =>
    ret = -1 
    if strategy.opentrades == 0 
        ret
    else 
        for i = 0 to strategy.opentrades - 1 
            if strategy.opentrades.entry_id(i) == idx
                ret := i 
                break
        ret

strategy.entry("long1", strategy.long, 0.1, limit=1, when=findOrderIdx("long1") < 0)
strategy.entry("long2", strategy.long, 0.2, when=findOrderIdx("long2") < 0)
strategy.entry("long3", strategy.long, 0.3, when=findOrderIdx("long3") < 0)

if not isExit and strategy.opentrades > 0
    // strategy.exit("exitAll")          // If only one id parameter is specified, the exit order is invalid, and the parameters profit, limit, loss, stop and other exit conditions also need to be set at least one, otherwise it is also invalid
    strategy.exit("exit1", "long1", profit=50)                    // Since the long1 entry order is not filled, the exit order with ID exit1 is also on hold until the corresponding entry order is filled before exit1 is placed
    strategy.exit("exit2", "long2", qty=0.1, profit=100)          // Specify the parameter qty to close 0.1 positions in the position with ID long2
    strategy.exit("exit3", "long3", qty_percent=50, limit=strategy.opentrades.entry_price(findOrderIdx("long3")) + 1000)   // Specify the parameter qty_percent to close 50% of the positions in the position with ID long3
    isExit := true 

if bar_index == 0 
    runtime.log("The price per point:", syminfo.mintick)    // The price per point is related to the "Pricing Currency Precision" parameter setting on the Pine language template parameters

Utilizamos o modelo de preços em tempo real para backtest, a estratégia de teste começa com 3 operações de entrada (strategy.entryfunção), elong1é definido intencionalmente comlimitParâmetro com um preço de ordem pendente de 1, para que ele não possa ser preenchido.strategy.exitO valor de uma operação de stop-loss é o valor de uma operação de stop-loss, que é a operação de uma operação de stop-loss, que é a operação de uma operação de stop-loss.strategy.exitA função também tem parâmetros mais complexos de trailing stop:trail_price, trail_points, trail_offsettambém pode ser testado neste exemplo para aprender o seu uso.


  1. strategy.cancel

Ostrategy.cancelEstas funções são utilizadas para cancelar/parar todas as ordens pendentes.strategy.order, strategy.entry , strategy.exitOs principais parâmetros desta função são:id, when.

Parâmetros:

  • idA identificação de admissão deve ser cancelada.
  • when: Condições de execução.

Esta função é fácil de entender e é usada para cancelar ordens de entrada que não são preenchidas.

/*backtest
start: 2022-07-03 00:00:00
end: 2022-07-09 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/

strategy("strategy.cancel Demo", pyramiding=3)

var isStop = false 
if isStop 
    runtime.error("stop")

strategy.entry("long1", strategy.long, 0.1, limit=1)
strategy.entry("long2", strategy.long, 0.2, limit=2)
strategy.entry("long3", strategy.long, 0

Mais.