Tutorial introductorio del lenguaje PINE de FMZ Quant

El autor:- ¿ Por qué?, Creado: 2022-09-23 15:23:34, Actualizado: 2024-02-27 16:47:41

Entonces vemos que en las tres líneas dibujadas a, b y c, la línea b es un BAR más lenta que la línea a, y la línea c es un BAR más lenta que la línea b. La línea c es 2 BAR más lenta que la línea a.

Podemos tirar del gráfico a la izquierda y observar que en la primera línea K, ambos valores de b y c son nulos (na). Esto se debe a que cuando el script se ejecuta en la primera línea K BAR, no existe cuando se hace referencia al valor histórico de uno o dos períodos adelante, que no existe. Por lo tanto, debemos tener cuidado al escribir estrategias para verificar si hacer referencia a los datos históricos dará como resultado valores nulos. Si se usa el valor nulo con descuido, causará una serie de diferencias de cálculo, e incluso puede afectar el BAR en tiempo real. Por lo general, usaremos funciones integradasna, nzEn la actualidad, la mayoría de los países de la Unión Europea han adoptado el código de conducta.nz, ```na`` en nuestros videos anteriores, ¿recuerdas qué capítulo es?) tratar el caso de los valores nulos, por ejemplo:

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 es una forma de manejar posibles referencias a valores nulos (na).

Prioridad del operador

Hemos aprendido muchos operadores en el lenguaje de Pine. Estos operadores forman expresiones a través de varias combinaciones con operandos. Entonces, ¿cuál es la prioridad de estas operaciones al evaluar en expresiones? Al igual que la aritmética que aprendimos en la escuela, la multiplicación y la división se calculan primero, seguida de suma y resta. Lo mismo ocurre con las expresiones en el lenguaje de Pine.

Prioridad Operadores
9 []
8 +-ynoten el operador unario
7 */%
6 +-en el operador binario
5 ><>=<=
4 ==!=
3 and
2 or
1 ?:

Las expresiones de alta prioridad se calculan primero, y si las prioridades son las mismas, se evalúa de izquierda a derecha.()para envolver la expresión para obligar a la parte a ser evaluada primero.

Variables

Declaración variable

Ya hemos estudiado el concepto de marker antes, que se utiliza como el nombre de una variable, es decir, una variable es un marcador que tiene un valor.

  • Modo de declaración: La primera cosa que se escribe al declarar una variable es el modo de declaración.

    1. Usa la palabra clavevar.
    2. Usa la palabra clavevarip.
    3. No escriba nada.

    ElvaryvaripLas palabras clave han sido estudiadas en nuestro capítulo anterior sobreAssignment OperatorsSi no se escribe nada para el modo de declaración de variables, como la instrucción:i = 1, como también hemos mencionado antes, tal variable declarada y asignada se ejecuta en cada K-línea BAR.

  • Tipo de producto El lenguaje Pine en FMZ no es estricto con respecto a los tipos, y generalmente se puede omitir. Sin embargo, para ser compatible con la estrategia de scripting en Trading View, las variables también se pueden declarar con tipos.

    int i = 0 
    float f = 1.1
    

    Los requisitos de tipo en Trading View son bastante estrictos y se notificará un error si se utiliza el siguiente código en Trading View:

    baseLine0 = na          // compile time error!
    
  • Marcador Los marcadores son nombres de variables. El nombre de los marcadores se ha mencionado en capítulos anteriores, por lo que puede revisarlo aquí:https://www.fmz.com/bbs-topic/9637#markers

En resumen, declarar una variable puede escribirse como:

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

El operador de asignación se utiliza aquí:=asigna un valor a una variable cuando se declara. Cuando se asigna el valor puede ser una cadena, número, expresión, llamada de función,if, for, while, oswitchy otras estructuras (estas palabras clave estructurales y el uso de declaraciones se explicarán en detalle en cursos posteriores.

Aquí nos centramos en la función de entrada, que es una función que vamos a utilizar con frecuencia al diseñar y escribir estrategias.

Función de entrada:

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

La función de entrada en FMZ es algo diferente a la de Trading View, pero esta función se utiliza como la entrada de asignación de parámetros de estrategia.

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)

La función de entrada se utiliza a menudo para asignar valores a las variables al declararlas. La función de entrada en FMZ dibuja controles para establecer parámetros de estrategia automáticamente en la interfaz de estrategia de FMZ. Los controles compatibles en FMZ actualmente incluyen cajas de entrada numéricas, cajas de entrada de texto, cajas desplegables y casillas de verificación booleanas. Y puede establecer la agrupación de parámetros de estrategia, establecer el mensaje de texto de solicitud de parámetros y otras funciones.

img

Introducimos varios parámetros principales de la función de entrada:

  • defval: el valor predeterminado para las opciones de parámetros de estrategia establecidas por la función de entrada, que admite variables integradas en el lenguaje Pine, valores numéricos y cadenas.
  • Título: Nombre del parámetro de la estrategia que se muestra en la interfaz de la estrategia durante la negociación en vivo/backtesting.
  • Consejo de herramientas: la información de la consejo de herramientas para los parámetros de estrategia, cuando el ratón pasa el cursor sobre el parámetro de estrategia, se mostrará la información de texto de la configuración del parámetro.
  • grupo: nombre del grupo de parámetros de estrategia, que puede utilizarse para los parámetros de estrategia.

Además de la declaración y asignación de variables individuales, también hay una forma de declarar un grupo de variables y asignarlas en el lenguaje Pine:

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

El más común es cuando usamos elta.macdla función para calcular el indicador MACD, ya que el indicador MACD es un indicador de varias líneas, se calculan tres conjuntos de datos.

[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 dibujar el gráfico MACD usando el código anterior fácilmente. No sólo las funciones integradas pueden regresar a múltiples variables, sino también las funciones personalizadas escritas pueden regresar a múltiples datos.

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)

El método de escritura de usar si y otras estructuras como asignaciones de variables múltiples también es similar a la función personalizada anterior, y puede probarlo si está interesado.

[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)

Estructura de las condiciones

Algunas funciones no se pueden escribir en el bloque de código local de la rama condicional, incluidas principalmente las siguientes funciones:

barcolor (), llenar (), línea (), indicador (), gráfico (), vela de gráfico (), gráfico (), gráfico (), gráfico (), gráfico ())

Trading View compilará con errores, FMZ no es tan restrictivo, pero se recomienda seguir las especificaciones de Trading View.

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

si la declaración

Ejemplo de explicación:

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)

Punto clave: Expresiones utilizadas para juicios que devuelven valores booleanos. Nótese la hendidura. Puede haber como máximo otra rama. Si todas las expresiones de rama no son ciertas y no hay otra rama, devuelva na.

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

Declaración de cambio

La instrucción de switch es también una instrucción estructurada por ramas, que se utiliza para diseñar diferentes rutas que se ejecutarán de acuerdo con ciertas condiciones.

  1. La instrucción de switch, como la instrucción if, también puede devolver un valor.
  2. A diferencia de las instrucciones de switch en otros lenguajes, cuando se ejecuta una construcción de switch, solo se ejecuta un bloque local de su código, por lo que la instrucción break es innecesaria (es decir, no hay necesidad de escribir palabras clave como break).
  3. Cada rama del switch puede escribir un bloque de código local, la última línea de este bloque de código local es el valor de retorno (puede ser un tuplo de valores).
  4. La expresión en la estructura del interruptor determina la posición puede escribir una cadena, variable, expresión o llamada de función.
  5. switch permite especificar un valor de retorno que actúa como el valor predeterminado a utilizar cuando no hay otro caso en la estructura para ejecutar.

Hay dos formas de cambio, vamos a ver los ejemplos uno por uno para entender su uso.

  1. A. El trabajo.switchcon expresiones - ejemplo de explicación:
// 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)

Aprendimos la función de entrada antes, aquí continuamos aprendiendo dos funciones similares a la entrada:input.string, input.int functions. input.stringse utiliza para devolver una cadena, y elinput.intEn el ejemplo, hay un nuevo uso de laoptionsel parámetro.optionsParámetro puede ser pasado una serie de valores opcionales, tales comooptions=["EMA", "SMA", "RMA", "WMA"]yoptions=[5, 10, 20]De esta manera, los controles en la interfaz de estrategia no necesitan ingresar valores específicos, pero los controles se convierten en cuadros desplegables para seleccionar estas opciones proporcionadas en el parámetro de opciones.

El valor de la variable func es una cadena, y la variable func se utiliza como la expresión para el switch (que puede ser una variable, llamada de función o expresión) para determinar qué rama en el switch se ejecuta.runtime.error("error")la función se ejecutará, haciendo que la estrategia lance una excepción y se detenga.

En nuestro código de prueba anterior, después de la última línea de runtime.error en el bloque de código de rama predeterminado de switch, no agregamos código como [na, na] para ser compatible con el valor de retorno. Este problema debe considerarse en Trading View. Si el tipo es inconsistente, se reportará un error. Pero en FMZ, ya que el tipo no es estrictamente requerido, este código de compatibilidad puede omitirse. Por lo tanto, no hay necesidad de considerar la compatibilidad de tipo del valor de retorno de if y switch branches en 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)

No se reportará ningún error en FMZ, pero se reportará un error en la vista de negociación porque el tipo devuelto por la rama if es inconsistente.

  1. switchsin expresiones

A continuación, vamos a ver otra manera de utilizarswitch, es decir, sin expresiones.

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 en el ejemplo de código de prueba, el switch coincidirá con la ejecución del bloque de código local que es verdad en la condición de rama. En general, las condiciones de rama después de una instrucción de switch deben ser mutuamente exclusivas. Es decir, arriba y abajo en el ejemplo no pueden ser verdaderos al mismo tiempo. Debido a que el switch solo puede ejecutar el bloque de código local de una rama, si está interesado, puede reemplazar esta línea en el código:up = close > open // up = close < openEn el caso de la aplicación de la función de comutación, la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación de la función de comutación.switchcon expresiones", la rama de ejecución es determinista y no se cambiará durante la operación de la estrategia).

Estructura del bucle

para la declaración

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

La instrucción for es muy sencilla de usar, el bucle for puede finalmente devolver un valor (o múltiples valores, en la forma de [a, b, c]). Al igual que la variable asignada a la posición valor de retorno en el pseudocódigo anterior. La instrucción for es seguida por una variable count utilizada para controlar el número de bucles, referirse a otros valores, etc. A la variable count se le asigna el conto inicial antes de que comience el bucle, luego se incrementa de acuerdo con la configuración longitud de paso, y el bucle se detiene cuando la variable count es mayor que el conto final.

ElbreakPalabra clave utilizada en el bucle for: el bucle se detiene cuando elbreakla declaración se ejecuta. ElcontinuePalabra clave utilizada en el bucle for: Cuando elcontinuela instrucción se ejecuta, el bucle ignorará el código despuéscontinuey ejecuta la siguiente ronda del bucle directamente. la instrucción for devuelve el valor de retorno de la última ejecución del bucle. y devuelve null si no se ejecuta código.

Luego demostramos con un ejemplo simple:

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... en la declaración

Elfor ... inLa declaración tiene dos formas, las ilustraremos en el siguiente pseudocodo.

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 la principal diferencia entre las dos formas es el contenido que sigue a la palabra clave for, una es usar una variable como una variable que se refiere a los elementos de la matriz, la otra es usar una estructura que contiene variables de índice, tuplas de variables de elementos de matriz como referencias. Para otras reglas de valor de retorno, como el uso de break, continue, etc., son consistentes con para bucles. También ilustramos el uso con un ejemplo simple.

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")

Cuando necesite usar el índice, use la gramáticafor [i, ele] in testArray.

Aplicación de los bucles

Podemos utilizar las funciones incorporadas proporcionadas en el lenguaje Pine para completar algunos de los cálculos lógicos del bucle, ya sea escritos utilizando la estructura del bucle directamente o procesados utilizando las funciones incorporadas.

  1. Calcular el valor medio

Cuando se diseñe con una estructura de bucle:

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)

El ejemplo utiliza un bucle for para calcular la suma y luego calcular el valor medio.

Calcular la media móvil directamente utilizando la función integrada:

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

Utilice la función integradata.smaEl cálculo de la media móvil se realiza directamente mediante el cálculo del indicador de la media móvil. Obviamente, es más simple utilizar la función incorporada para calcular la media móvil.

  1. Resumen

Todavía usamos el ejemplo anterior para ilustrar.

Cuando se diseñe con una estructura de bucle:

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 la suma de todos los elementos de una matriz, podemos usar un bucle para procesarlo, o usar la función incorporadaarray.sumpara calcular. Calcular la suma directamente utilizando la función integrada:

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 que los datos calculados es exactamente el mismo que se muestra en el gráfico utilizando gráfico.

Entonces, ¿por qué diseñar bucles cuando podemos hacer todo esto con funciones incorporadas?

  1. Para algunas operaciones y cálculos de matrices.
  2. Para revisar el historial, por ejemplo, para averiguar cuántos puntos altos pasados son más altos que el punto alto del BAR actual.
  3. Cuando las funciones integradas del lenguaje Pine no pueden completar cálculos para BARs pasados.

mientras que el estado

Elwhilela instrucción mantiene el código en la sección del bucle ejecutándose hasta que la condición de juicio en la estructura while sea 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

Otras reglas de while son similares a las del bucle for. La última línea del bloque de código local del cuerpo del bucle es el valor de retorno, que puede devolver múltiples valores. Ejecuta el bucle cuando la condición loop es verdadera, y detiene el bucle cuando la condición es falsa.

Seguiremos utilizando el ejemplo de cálculo de medias móviles para la demostración:

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 el bucle mientras también es muy simple de usar, y también es posible diseñar alguna lógica de cálculo que no puede ser reemplazado por las funciones incorporadas, tales como el cálculo factorial:

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

Las matrices

La definición de matrices en el lenguaje Pine es similar a la de otros lenguajes de programación. Las matrices Pines son matrices unidimensionales. Por lo general, se utilizan para almacenar una serie continua de datos. Los datos individuales almacenados en la matriz se llaman el elemento de la matriz, y los tipos de estos elementos pueden ser: entero, punto flotante, cadena, valor de color, valor booleano. El lenguaje Pine en FMZ no es muy estricto sobre tipos, e incluso puede almacenar cadenas y números en una matriz al mismo tiempo.[]para referirse a un elemento en la matriz, necesitamos utilizar las funcionesarray.get()yarray.set()El orden de índice de los elementos en la matriz es que el índice del primer elemento de la matriz es 0, y el índice del siguiente elemento se incrementa en 1.

Lo ilustramos con un código simple:

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 una matriz

Utilizaciónarray<int> a, float[] bpara declarar una matriz o simplemente declarar una variable que se puede asignar a una matriz, por ejemplo:

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")

Las variables de matriz se inicializan utilizando elarray.newyarray.fromTambién hay muchas funciones relacionadas con tipos similares aarray.newen la lengua de los pinos:array.new_int(), array.new_bool(), array.new_color(), array.new_string(), etc.

La palabra clave var también funciona con el modo de declaración de matriz. Las matrices declaradas con la palabra clave var se inicializan solo en la primera BAR.

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")

Se puede ver que los cambios de la matriz a se han determinado continuamente y no se han restablecido. La matriz b se inicializa en cada BAR.barstate.islastes cierto, todavía hay sólo un elemento impreso con un valor de 0.

Leer y escribir elementos en una matriz

Use array.get para obtener el elemento en la posición de índice especificada en la matriz, y use array.set para modificar el elemento en la posición de índice especificada en la matriz.

El primer parámetro de array.get es la matriz a procesar, y el segundo parámetro es el índice especificado. El primer parámetro de array.set es la matriz a procesar, el segundo parámetro es el índice especificado, y el tercer parámetro es el elemento a escribir.

Usamos el siguiente ejemplo simple para ilustrarlo:

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")

El ejemplo inicializa el color base verde, declara e inicializa una matriz para almacenar colores, y luego asigna diferentes valores de transparencia a los colores (usando elcolor.newEl nivel de color se calcula calculando la distancia del BAR actual desde el valor máximo de alto en 100 períodos de vista. Cuanto más cerca esté la distancia del valor máximo de alto en los últimos 100 ciclos de vista, mayor será el rango y más oscuro (menor transparencia) el valor de color correspondiente.

Iterar a través de elementos de matriz

Cómo iterar a través de una matriz, podemos usar las instrucciones for/for in/while que hemos aprendido 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")

Estos tres métodos de travesía tienen los mismos resultados de ejecución.

Las matrices se pueden declarar en el ámbito global de un script, o en el ámbito local de una función o si rama.

Referencias de datos históricos

Para el uso de elementos en matrices, las siguientes formas son equivalentes. Podemos ver por el siguiente ejemplo que dos conjuntos de líneas se dibujan en el gráfico, dos en cada conjunto, y las dos líneas en cada conjunto tienen exactamente el mismo 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)

Funciones para añadir y eliminar matrices

  1. Funciones relacionadas con la operación de suma de matrices:

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

  1. Funciones relacionadas con la operación de borrado de matriz:

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

Usamos el siguiente ejemplo para probar estas funciones de operación de adición y eliminación 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")

Aplicación de adiciones, eliminaciones: matrices como colas

Podemos construir una estructura de datos queue usando matrices y algunas funciones de adición y eliminación de matrices. Las colas se pueden usar para calcular el promedio móvil de los precios de las ticks.

Una cola es una estructura que se utiliza a menudo en el campo de la programación, las características de una cola son:

El elemento que entra en la cola primero, sale de la cola primero.

De esta manera, se asegura de que los datos en la cola son los datos más recientes, y que la longitud de la cola no se expandirá indefinidamente.

En el siguiente ejemplo, utilizamos una estructura de cola para registrar el precio de cada tick, calcular el precio promedio móvil en el nivel de tick, y luego compararlo con el promedio móvil en el nivel de la línea 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")

Tenga en cuenta que al declarar la matriz a, especificamos el modo de declaración y utilizar la palabra clavevariantDe esta manera, cada cambio de precio se registrará en la matriz a.

Funciones de cálculo y operación de matriz comúnmente utilizadas

Calcular las funciones de correlación:

array.avg()Calcula el valor medio de todos los elementos de una matriz,array.min()Calcula el elemento más pequeño de una matriz,array.max()Calcula el elemento más pequeño de una matriz,array.stdev()Calcula la desviación estándar de todos los elementos de una matriz,array.sum()Calcula la desviación estándar de todos los elementos de una matriz.

Funciones relacionadas con el funcionamiento:array.concat()para fusionar o concatenar dos matrices.array.copy()para copiar la matriz.array.joinpara concatenar todos los elementos de una matriz en una cadena.array.sort()para ordenar por orden ascendente o descendente.array.reverse()para invertir la matriz.array.slice()para cortar la matriz.array.includes()para juzgar el elemento.array.indexof()Si no se encuentra el valor, se devolverá -1.array.lastindexof()para encontrar la última aparición del valor.

Ejemplos de pruebas de funciones relacionadas con el cálculo de matrices:

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 son funciones de cálculo de matriz comúnmente utilizadas.

Ejemplos de funciones relacionadas con el funcionamiento:

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")

Funciones

Funciones personalizadas

En general, las siguientes reglas se aplican a las funciones personalizadas en el lenguaje Pine:

  1. Todas las funciones están definidas en el alcance global del script.
  2. No se permite que las funciones se llamen a sí mismas en su propio código (recursión).
  3. En principio, todas las funciones de dibujo integradas en el lenguaje PINE (barcolor(), fill(), hline(), plot(), plotbar(), plotcandle()) no se puede llamar en funciones personalizadas.
  4. Las funciones se pueden escribir como una sola línea o varias líneas. El valor de retorno de la última instrucción es el valor de retorno de la función actual, que se puede devolver en forma de túpulos.

También hemos utilizado las funciones personalizadas muchas veces en nuestros tutoriales anteriores, como las diseñadas como una sola línea:

barIsUp() => close > open

Si el BAR actual es una recta positiva cuando la función devuelve.

Funciones personalizadas diseñadas para ser múltiples líneas:

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)

Usamos una función personalizada para realizar una función de cálculo medio sma.

Además, dos ejemplos de funciones personalizadas que podemos devolver:

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)

Una función puede calcular la línea rápida, la línea lenta y dos promedios EMA.

Funciones incorporadas

Las funciones integradas se pueden encontrar fácilmente en elDocumento de guión FMZ PINE.

Clasificación de las funciones integradas en el lenguaje Pine:

  1. Función de procesamiento de cadenasstr. series.
  2. La función de procesamiento de valor de colorcolor. series.
  3. Función de entrada de parámetrosinput. series.
  4. Función de cálculo del indicadorta. series.
  5. Función de dibujoplot. series.
  6. Función de manejo de matrizarray. series.
  7. Funciones relacionadas con el comerciostrategy. series.
  8. Funciones relacionadas con las operaciones matemáticasmath. series.
  9. Otras funciones (manipulación del tiempo, funciones de dibujo de series no gráficas,request.funciones de serie, funciones de manipulación de tipos, etc.)

Funciones comerciales

Elstrategy.serie de funciones son funciones que a menudo utilizamos en el diseño de estrategias, y estas funciones están estrechamente relacionadas con la ejecución de operaciones comerciales cuando la estrategia se ejecuta específicamente.


  1. strategy.entry

strategy.entryfunción es una función más importante cuando escribimos una estrategia para colocar un pedido, varios parámetros importantes para la función son:id, direction, qty, when, etc.

Parámetros:

  • id: se puede entender que se da un nombre a una posición de negociación para hacer referencia.
  • direction: Si la dirección de la orden es larga (comprar), pasar en la variable incorporadastrategy.long, y si desea ir corto (vender), pasar en la variablestrategy.short.
  • qty: Especificar el importe de las órdenes a realizar, si no se pasa este parámetro, se utilizará el importe predeterminado de las órdenes.
  • when: Condición de ejecución, puede especificar este parámetro para controlar si se activa o no esta operación de orden actual.
  • limit: Especificar el precio límite de la orden.
  • stopPrecio de suspensión de pérdida.

Los detalles específicos de la ejecución delstrategy.entryLa función se controla por los parámetros cuando elstrategyfunción se llama, y también puede ser controlado por el [Pine Language Trade-Class Library Template Arguments](https://www.fmz.com/bbs-topic/9293#template-argumentos-of-pine-language-trade-class-library) configuración de control, argumentos de la plantilla de biblioteca de clase comercial de lenguaje Pine controlan más detalles de la transacción, puede consultar la documentación vinculada para obtener detalles.

Nos centramos en el elpyramiding, default_qty_valuelos parámetros en elstrategyUtilizamos el siguiente código para la prueba:

/*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)

La parte al comienzo del código/* backtest... */es un ajuste de backtest, que se utiliza para registrar el tiempo de ajuste de backtest y otra información en ese momento para la depuración, no el código de inicio.

En el código:strategy(title = "open long example", pyramiding = 3, default_qty_value=0.1, overlay=true), cuando especificamos elpyramidingParámetro como 3, fijamos el número máximo de operaciones en la misma dirección a 3.strategy.entryLas operaciones de orden en el ejemplo no se ejecuta.default_qty_valueParámetro para ser 0.1, estostrategy.entryoperación con IDlong1tiene un tamaño de orden predeterminado de 0,1.strategy.entryllamada de la función cuando especificamos eldirectioncomostrategy.long, por lo que las órdenes de prueba backtest son todos los pedidos de compra.

Tenga en cuenta que la operación de ordenstrategy.entry("long3", ...en el código se llama dos veces, para el mismo ID:long3, el primerostrategy.entryla operación de orden no se llenó, y la segunda llamada a lastrategy.entryEn el caso de los ordenes de ordenes con el ID long3, el número de ordenes de ordenes de ordenes de ordenes de ordenes de ordenes de ordenes de ordenes de ordenes de ordenes de ordenes de ordenes de ordenes.strategy.entryla función para realizar órdenes de acuerdo con el ID long3, entonces las posiciones de orden se acumularán en el ID long3.


  1. strategy.close

Elstrategy.closeLa función se utiliza para cerrar la posición de entrada con el ID de identificación especificado. Los principales parámetros son:id, when, qty, qty_percent.

Parámetros:

  • id: El ID de entrada que debe cerrarse es el ID que especificamos cuando abrimos una posición utilizando una función de orden de entrada, comostrategy.entry.
  • when: Condiciones de ejecución.
  • qty: Número de posiciones cerradas.
  • qty_percent: Porcentaje de posiciones cerradas.

Vamos a familiarizarnos con los detalles del uso de esta función a través de un ejemplo: El/*backtest ... */en el código está la información de configuración paraFMZ.COMbacktest, puede borrarlo y establecer el mercado, variedad, rango de tiempo y otra información que necesita para probar.

/*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

La estrategia de prueba muestra tres entradas largas consecutivas con el ID de entrada long1 y luego utiliza diferentes parámetros de lastrategy.closeLa función para establecer los diferentes resultados de la prueba de retroceso al cerrar una posición.strategy.closela función no tiene parámetros para especificar el precio de la orden para cerrar la posición, esta función se utiliza principalmente para cerrar la posición inmediatamente al precio de mercado actual.


  1. strategy.close_all

La funciónstrategy.close_allse utiliza para cerrar todas las posiciones actuales, porque las posiciones de lenguaje de guión Pine sólo puede tener una dirección, es decir, si hay una señal activada en la dirección opuesta a la posición actual cerrará la posición actual y luego abrirlo de acuerdo con el disparador de la señal.strategy.close_allcerrará todas las posiciones en la dirección actual cuando se llame.strategy.close_allsu función es:when.

Parámetros:

  • when: Condiciones de ejecución.

Vamos a utilizar un ejemplo 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 

El código de ensayo comienza con un número de posición de 0 (es decir,strategy.position_size==0Es cierto), por lo que cuando las condiciones establecidas por el cuando parámetro se cumplen, sólo elstrategy.entryse ejecuta la función de entrada con ID long. Después de mantener una posición larga,strategy.position_sizees mayor que 0, entonces la función de entrada con ID short puede ejecutarse, ya que la posición larga actual se mantiene, esta señal de reversión de corto en este momento dará lugar al cierre de la posición larga y luego abrir la posición corta en la dirección opuesta.strategy.position_size < 0, es decir, cuando se mantiene una posición corta, todas las posiciones en la dirección actual de mantenimiento se cerrarán.enableStop := true. Detener la ejecución de la estrategia para que el registro pueda ser observado.

Se puede encontrar que la funciónstrategy.close_allno tiene parámetros para especificar el precio de cierre de la orden, esta función se utiliza principalmente para cerrar inmediatamente la posición al precio de mercado actual.


  1. strategy.exit

Elstrategy.exitLa función de cierre de una posición de entrada se utiliza para cerrar una posición de entrada.strategy.closeystrategy.close_allLas funciones de cierre de una posición inmediatamente al precio de mercado actual.strategy.exitla función cerrará la posición de acuerdo con los parámetros.

Parámetros:

  • id: El identificador de orden de la orden de la condición de cierre actual.
  • from_entrySe utiliza para especificar el ID de entrada de la posición que se va a cerrar.
  • qty: Número de posiciones cerradas.
  • qty_percent: Porcentaje de posiciones cerradas, rango: 0 ~ 100.
  • profit: Objetivo de utilidad, expresado en puntos.
  • lossSe trata de la suma de las pérdidas de los activos de la entidad.
  • limit: Objetivo de utilidad, especificado por precio.
  • stop: Objetivo de pérdida por parada, especificado por precio.
  • when: Condiciones de ejecución.

Utilice una estrategia de prueba para comprender el uso de los 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

Se utiliza el modelo de precios en tiempo real para backtest, la estrategia de prueba comienza con 3 operaciones de entrada (strategy.entryfunción), ylong1se establece intencionadamente conlimitParámetro con un precio de orden pendiente de 1, por lo que no se puede llenar.strategy.exitEn el caso de las posiciones de cierre, el precio de cierre es el mismo que el precio de cierre de las posiciones de cierre.strategy.exitLa función también tiene parámetros de parada de trail más complejos:trail_price, trail_points, trail_offsetTambién se puede probar en este ejemplo para aprender su uso.


  1. strategy.cancel

Elstrategy.cancelEstas funciones se utilizan para cancelar/detener todas las órdenes pendientes.strategy.order, strategy.entry , strategy.exitLos parámetros principales de esta función son:id, when.

Parámetros:

  • id: La identificación de admisión se cancela.
  • when: Condiciones de ejecución.

Esta función es fácil de entender, y se utiliza para cancelar las órdenes de entrada que no se cumplen.

/*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

Más.