The resource loading... loading...

Inventors quantified the PINE language introductory tutorial

Author: Inventors quantify - small dreams, Created: 2022-05-30 16:23:43, Updated: 2022-09-28 17:10:21

Examples:

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)

Note: Expressions used for judging return Boolean values. Note that there can only be one else branch. All branching expressions are false and return na without an else branch.

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

Since the expression after the if statement is false when the K-line BAR is a diagonal line, i.e. close < open, the local code block of if is not executed. This time, too, there is noelse branch, and the if statement returns na. x is assigned na. This point cannot be plotted on the graph, which we can also observe by backscaling the graph.

switch statements

A switch statement is also a branch-structured statement designed to execute different paths depending on certain conditions.

The switch statement returns the same value as the if statement. Unlike the switch statements in other languages, when executing the switch structure, only one local block of its code is executed, so the break declaration is unnecessary (i.e. no keyword such as break is needed). 3. Each branch of the switch can write a local code block, the last line of which is the return value (which can be an element of a value). If no branch is executed, the local code block returns na. 4, Expressions in a switch structure can be written as strings, variables, expressions, or function calls. 5. Switch allows the specification of a return value that is used as the default when no other conditions in the structure are executed.

The switch is divided into two forms, and we'll look at examples one by one to understand how to use it.

1 with an expressionswitchThis is the case for the following:

// input.string: defval, title, options, tooltip
func = input.string("EMA", title="指标名称", tooltip="选择要使用的指标函数名称", options=["EMA", "SMA", "RMA", "WMA"])

// input.int: defval, title, options, tooltip
// param1 = input.int(10, title="周期参数")
fastPeriod = input.int(10, title="快线周期参数", options=[5, 10, 20])
slowPeriod = input.int(20, title="慢线周期参数", options=[20, 25, 30])

data = input(close, title="数据", tooltip="选择使用收盘价、开盘价、最高价...")
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)

We learned about input functions earlier, and here we continue to learn about two functions that are similar to input:input.stringinput.intThe function ≠ ∞input.stringIt is used to return strings.input.intThe function is used to return an integer value. In this example, a new value is added.optionsThe use of parametersoptionsParameters can be passed to an array of selectable values.options=["EMA", "SMA", "RMA", "WMA"]andoptions=[5, 10, 20](Note that one is a string type and one is a numeric type). The controls on the policy interface do not need to enter specific values, but instead become a drop-down box to select the options provided in the options parameter.

The value of the variable func is a string, and the variable func is used as an expression of the switch (which can be a variable, function call, expression) to determine which branch in the executed switch. If the variable func cannot match the expression on any branch in the switch (i.e. is equal), the default branch code block is executed, which is executed.runtime.error("error")The function causes the policy to throw an exceptional stop.

In our test code above, after the last line of the default branch code block runtime.error in Switch, we did not add code like [na, na] to return the compatible value, which needs to be considered in the trading view if the type is inconsistent. But in FMZ, this type of compatibility code can be omitted because there is no strict type requirement.

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)

It does not return an error in FMZ, but in trading view.

2, without an expression.switch

Let's see.switchAnother way to use the word is to write it without expressions.

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)

The test code example shows that the switch matches the execution of the branch to the true local code block. Generally speaking, the branch condition after the switch statement must be mutually exclusive. That is, the up and down in the example cannot be true at the same time. Since the switch can only execute one branch of the local code block, it is interesting to put this line in the code:up = close > open // up = close < openInstead of the content in the comment, check back to observe the results. It will be found that the switch branch can only execute the first branch. In addition, it is necessary to take care not to write function calls in the branch of the switch, the function cannot be called on each BAR, which can cause some data computation problems (unless, for example, "with an expressionswitchIn the example, the execution branch is defined and will not be changed during the policy run.)

Circular structure

for statements

返回值 = for 计数 = 起始计数 to 最终计数 by 步长
    语句                                            // 注释:语句里可以有break,continue
    语句                                            // 注释:最后一条语句为返回值

The use of the for statement is very simple, the for loop can eventually return a single value ((or return multiple values, in the form of [a, b, c]); as in the pseudo-code above, the variable assigned to the "return value" position. The for statement is followed by a "count" variable to control the number of cycles, refer to other values, etc. The "count" variable is assigned the "initial count" before the start of the loop, and then passed according to the "growth" setting, and the loop stops when the number of "count" is greater than the "final variable".

For use in cyclingbreakKeyword: when executedbreakAfter the sentence, the cycle stops. For use in cyclingcontinueKeyword: when executedcontinueAfter the sentence, the loop is ignored.continueThe following code, directly executes the next loop. The for statement returns the value returned when the last loop was executed. If no code is executed, it returns a blank value.

Here's a simple example:

ret = for i = 0 to 10       // 可以增加by关键字修改步长,暂时FMZ不支持 i = 10 to 0 这样的反向循环
    // 可以增加条件设置,使用continue跳过,break跳出
    runtime.log("i:", i)
    i                       // 如果这行不写,就返回空值,因为没有可返回的变量
    
runtime.log("ret:", ret)
runtime.error("stop")

For... in sentences

for ... inThere are two forms of statements, as shown by the following pseudo-codes:

返回值 = for 数组元素 in 数组 
    语句                        // 注释:语句里可以有break,continue
    语句                        // 注释:最后一条语句为返回值
返回值 = for [索引变量, 索引变量对应的数组元素] in 数组
    语句                        // 注释:语句里可以有break,continue
    语句                        // 注释:最后一条语句为返回值 

The main difference between the two forms can be seen in the content that follows after the for keyword, one is the use of a variable as a reference to the array element. One is the use of a structure that contains an index variable, an array element variable to reference. The other is the use of return value rules, rules such as break, continue and for loop consistency.

testArray = array.from(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
for ele in testArray            // 修改成 [i, ele]的形式:for [i, ele] in testArray , runtime.log("ele:", ele, ", i:", i)
    runtime.log("ele:", ele)

runtime.error("stop")

Use it when you need to use an index.for [i, ele] in testArrayI'm not sure what to say.

for circular applications

When some looping logic calculations can be performed using the built-in functions provided by the Pine language, the looping structure can be written directly, or the built-in function processing can be used. Here are two examples.

1, the computed mean

When designing circular structures:

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)

The for loop is used in the example, and then the mean is calculated.

Calculate a straight line directly using the built-in function:

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

Direct use of built-in functionsta.smaIn the case of the calculation of equilateral indicators, it is obviously simpler to calculate the equilateral using the built-in function.

2 and sum

I'm not sure if this is a good idea, but I'm going to use the example above to illustrate.

When designing circular structures:

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)

For calculating the sum of all elements of an array that can be handled with a loop, a built-in function can also be used.array.sumI'm going to calculate. Calculate summation directly using the built-in function:

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)

You can see that the calculated data is perfectly consistent on the graph using the plot diagram.

So if you can do all these things with built-in functions, why design a loop? 1, some operations for arrays, calculations. 2, look back in history, for example, to find out how many past highs are higher than the highs of the current BAR. Since the highs of the current BAR are known only on the BAR running the script, a loop is needed to go back in time and analyze the past BAR. 3, where the built-in function using the Pine language cannot complete the calculation of the past BAR.

while statement

whileThe statement keeps the code of the looping part executing until the judgment condition in the while structure is false.

返回值 = while 判断条件
    语句                    // 注释:语句里可以有break,continue
    语句                    // 注释:最后一条语句为返回值

The other rules for while are similar to for loops, where the last line of the local code block of the loop is the return value, which can return multiple values. The break, continue statements can also be used in the loop.

I'm still demonstrating this with the example of a straight line:

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)

It can be seen that while looping is also very simple to use, it is also possible to design some computational logic that cannot be replaced by built-in functions, such as computational multiplication:

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

An array in Pine is a one-dimensional array; it is typically used to store a continuous set of data. An array in which individual data is stored is called an array element. The types of these elements can be: integer, floating point, string, color value, Boolean value. The Pine language on FMZ does not require a very strict type, and can even store both string and number in an array at the same time.[]It needs to be used.array.get()andarray.set()The index sequence of the elements in the array is 0 for the index of the first element of the array, and 1 for the next element.

We're going to use a simple code to illustrate:

var a = array.from(0)
if bar_index == 0 
    runtime.log("当前BAR上的a值:", a, ", 上1根BAR上的a,即a[1]值:", a[1])
else if bar_index == 1 
    array.push(a, bar_index)
    runtime.log("当前BAR上的a值:", a, ", 上1根BAR上的a,即a[1]值:", a[1])
else if bar_index == 2
    array.push(a, bar_index)
    runtime.log("当前BAR上的a值:", a, ", 上1根BAR上的a,即a[1]值:", a[1], ", 向前数2根BAR上的a,即a[2]值:", a[2])
else if bar_index == 3 
    array.push(a, bar_index)
    runtime.log("当前BAR上的a值:", a, ", 上1根BAR上的a,即a[1]值:", a[1], ", 向前数2根BAR上的a,即a[2]值:", a[2], ", 向前数3根BAR上的a,即a[3]值:", a[3])
else if bar_index == 4 
    // 使用array.get 按索引获取元素,使用array.set按索引修改元素
    runtime.log("数组修改前:", array.get(a, 0), array.get(a, 1), array.get(a, 2), array.get(a, 3))
    array.set(a, 1, 999)
    runtime.log("数组修改后:", array.get(a, 0), array.get(a, 1), array.get(a, 2), array.get(a, 3))

Declared array

Usearray<int> afloat[] bArrays that declare an array or declare only one variable can be assigned to an array, for example:

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

Initialization of array variables in general usearray.newandarray.from函数。Pine语言中还有很多和类型相关的与array.new类似的函数:array.new_int()array.new_bool()array.new_color()array.new_string()And so on and so forth.

The var keyword also works with the declaration pattern of the array, where the array using the var keyword declaration is initialized only on the first BAR. We see this with an example:

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

It can be seen that the changes in the array a are continuously determined and not rearranged. The array b is initialized at each BAR.barstate.islastThere is still only one element, the value 0′, when printing in real time.

Read, write to, and delete elements from arrays

Use array.get to retrieve an element that specifies an index position in an array, and use array.set to modify an element that specifies an index position in an array.

The first parameter of array.get is the array to be processed, and the second parameter is the specified index. The first parameter of array.set is the array to be processed, the second parameter is the specified index, and the third parameter is the element to be written.

To illustrate this, use the following simple example:

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

This example initializes the base color green, declares and initializes an array to store the color, and then assigns a different transparency to the color value (using the color.new function); calculates the color rank by calculating the current BAR distance to the maximum high value in 100 review cycles; the closer the distance to the maximum high value in the most recent 100 review cycles, the higher the rank, the deeper the corresponding color value (low transparency); many similar strategies use this way to represent the level of the price in N current review cycles.

Go through the elements of the array

How to traverse an array can be done using the for/for in/while statements we learned before.

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

All three routing methods have the same results.

Arrays can be declared within the global scope of the script, or within the local scope of the function or if branch

Historical references

For the use of elements in an array, the following method is equivalent, we can see from the following example that two sets of lines are plotted on the graph, two lines in each group, and the two line values in each group are exactly the same.

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)

Adding arrays, removing operands

1, the addition operation of the array:

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

2, the deletion function of the array:

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

We use the following example to test the addition, subtraction and operation functions of these arrays.

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

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

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

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

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

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

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

runtime.error("stop")

Adding and removing applications: Arrays as queues

Using arrays, and some addition and subtraction functions, we can construct a "queue" data structure. A queue can be used to calculate the moving average of a tick price, and some students may ask: Why do we construct a queue structure?

A queue is a structure commonly used in programming, characterized by:

The first element to enter the queue, the first to exit the queue.

This ensures that the data in the queue is up-to-date and that the length of the queue does not inflate indefinitely ("Infinite inflation code can only be written at noon, because it will cause problems early or late").

In the following example, we use a queue structure to record the price per tick, calculate the moving average price at the tick level, and then compare it to the moving average observed at the 1 minute K line level.

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

Note that when declaring an array a we specify the declaration pattern using the keywordvaripThus, each price change is recorded in a matrix.

Commonly used arithmetic, operation functions

Calculate the related functions:

array.avg()Find the average of all the elements in the array.array.min()The smallest element in the array.array.max()The largest element in the array,array.stdev()The standard deviation of all the elements in the array.array.sum()The sum of all the elements in the functional group.

Functions related to operation:array.concat()Combine or connect two arrays.array.copy()This is a simple example of a duplicate array.array.joinConnects all the elements in the array to a single string.array.sort()Ordering in ascending or descending order.array.reverse()The inverse matrix.array.slice()The matrix is sliced.array.includes()Judging the elements.array.indexof()Returns the index in which the value of the input parameter first appeared. If the value cannot be found, it returns −1.array.lastindexof()Find the last value to appear.

Test examples of arithmetic-computing related functions:

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

runtime.log("数组a的算数平均:", array.avg(a))
runtime.log("数组a中的最小元素:", array.min(a))
runtime.log("数组a中的最大元素:", array.max(a))
runtime.log("数组a中的标准差:", array.stdev(a))
runtime.log("数组a的所有元素总和:", array.sum(a))
runtime.error("stop")

These are the most commonly used arithmetic computation functions.

Examples of operations on related functions:

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

runtime.log("数组a:", a, ", 数组b:", b)
runtime.log("数组a,数组b连接在一起:", array.concat(a, b))
c = array.copy(b)

runtime.log("复制一个数组b,赋值给变量c,变量c:", c)

runtime.log("使用array.join处理数组c,给每个元素中间增加符号+,连接所有元素结果为字符串:", array.join(c, "+"))
runtime.log("排序数组b,按从小到大顺序,使用参数order.ascending:", array.sort(b, order.ascending))     // array.sort函数修改原数组
runtime.log("排序数组b,按从大到小顺序,使用参数order.descending:", array.sort(b, order.descending))   // array.sort函数修改原数组

runtime.log("数组a:", a, ", 数组b:", b)
array.reverse(a)   // 此函数修改原数组
runtime.log("反转数组a中的所有元素顺序,反转之后数组a为:", a)    

runtime.log("截取数组a,索引0 ~ 索引3,遵循左闭右开区间规则:", array.slice(a, 0, 3))
runtime.log("在数组b中搜索元素11:", array.includes(b, 11))
runtime.log("在数组a中搜索元素100:", array.includes(a, 100))
runtime.log("将数组a和数组b连接,搜索其中第一次出现元素2的索引位置:", array.indexof(array.concat(a, b), 2), " , 参考观察 array.concat(a, b):", array.concat(a, b))
runtime.log("将数组a和数组b连接,搜索其中最后一次出现元素6的索引位置:", array.lastindexof(array.concat(a, b), 6), " , 参考观察 array.concat(a, b):", array.concat(a, b))

runtime.error("stop")

Function

Custom functions

The Pine language can design custom functions, and in general, the custom functions of the Pine language have the following rules:

1, all functions are defined within the scope of the script. 2, does not allow functions to call themselves in their own code (recursive). 3, in principle all PINE languages have built-in drawing functionsbarcolor()、 fill()、 hline()、plot()、 plotbar()、 plotcandle()) cannot be called within a custom function. 4, the function can be written as single-line, multi-line. The return value of the last statement is the current function's return value, which can be returned in array form.

In previous tutorials we have also used custom functions several times, for example designing a custom function as a single line:

barIsUp() => close > open

This function returns whether the current BAR is a sunbeam.

Designed as a multi-line custom function:

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)

A function that we implemented ourselves using a custom function to compute a SMA mean line.

Also, an example of a custom function with two variables can be returned:

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)

A function can calculate a fast line, a slow line, two EMA even-line indicators.

Built-in functions

The built-in functions can be convenientlyFMZ PINE Script documentation is provided.I'm not sure what you mean.

The Pine language has built-in functions:

1, the string processing functionstr.The series. 2, the color-value processing functioncolor.The series. 3, the parameter input functioninput.The series. 4, the indicator computation functionta.The series. 5, the graph functionplot.The series. 6 is an arithmetic processing functionarray.The series. 7 Transaction-related functionsstrategy.The series. 8 Mathematical operations and related functionsmath.The series. 9, other functions ((time processing, non-plot series drawing functions,request.This is a list of functions that can be used to define a function (series functions, type processing functions, etc.).

The transaction function

strategy.Serial functions are functions that we use frequently in our design strategies, and they are closely related to the execution of transaction operations when the strategy is specifically executed.


1、strategy.entry

strategy.entryA function is a subordinate function that is more important when we write a policy, and it is more important for several parameters:id, direction, qty, whenAnd so on and so forth.

Parameters are:

  • id: can be understood to refer to a name for a trading position. The id can refer to the cancellation, modification of orders, or placement.
  • direction: if the order direction is to do more (buy) this parameter is passedstrategy.longThis built-in variable is passed if you want to empty (sell).strategy.shortThis variable.
  • qty: Specify the order quantity, if this parameter is not passed, the default order quantity is used.
  • when: execution condition, which can be specified to control whether the current subroutine operation is triggered or not.
  • limit: specify the order limit price.
  • stopThe price of the product is very low.

strategy.entryThe specific execution details of the function are givenstrategyThe parameters set when the function is called can also be controlled by"Pine language exchange library template parameters"More details on the transaction controls, set controls, and template parameters of the Pine language transaction library can be found in the documentation at the link.

Here's a little bit about the key points.strategyIn the function,pyramidingdefault_qty_valueParameters. Use the following code to test:

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

The beginning of the code/*backtest ... */The package part is set for retesting, to record information such as the time of retesting at the time, for easy debugging, not policy code.

In the code:strategy(title = "open long example", pyramiding = 3, default_qty_value=0.1, overlay=true)When we specifypyramidingWhen the parameter is 3, we set the same direction to trade up to three times. So four times in the example.strategy.entryOne of the following operations was not executed.default_qty_valueThe parameter is 0.1, so the ID is going to be 1 tonne this time.strategy.entryThe sub-order operation's sub-order quantity is set to 0.1 by default.strategy.entryWe're going to call the function.directionequal tostrategy.longIn addition to the above, there are also other benefits of using the internet to make money.

Note the code.strategy.entry("long3", ...The following operation is called twice, for the same ID: long3.strategy.entryThe following operation was not completed, second callstrategy.entryThe function is used to modify the order for this ID (the data displayed during retesting can also show that the order quantity under this limit order was modified to 0.3). Another case, for example, if the first ID is for the order for the long 3 tons, continue to use it according to this ordered ID.strategy.entryIf the function places an order, then the order positions will be accumulated on the ID long 3s.


2、strategy.close

strategy.closeThe function is used to specify the ID of the entry holding position of the placement. The main parameters are:idwhenqtyqty_percent

Parameters are:

  • idWe use the same ID for the login:strategy.entryThe ID specified at the time of opening of the entry-submission function.
  • whenThis is the first time I have seen this.
  • qtyThe number of balances.
  • qty_percentThis is the first time that the company has been able to do so.

To familiarize yourself with the details of how this function is used, let's look at an example: In the code/*backtest ... */是FMZ.COM国际站回测时的配置信息,可以删掉,设置自己需要测试的市场、品种、时间范围等信息。

/*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")                   // 多个入场订单,不指定qty参数,全部平仓
    // strategy.close()                          // 不指定id参数,会平掉当前的持仓
    // strategy.close("long2")                   // 如果指定一个不存在的id则什么都不操作
    // strategy.close("long1", qty=0.15)         // 指定qty参数平仓
    // strategy.close("long1", qty_percent=50)   // qty_percent设置50即为平掉long1标识仓位的50%持仓
    // strategy.close("long1", qty_percent=80, when=close<open)  // 指定when参数,修改为close>open就不触发了
    enableStop := true

The test strategy demonstrates that multiple entries are started three times in a row, each with an entry ID of 1long, and then used.strategy.closeDifferent results are returned when different parameters of the function are set to par.strategy.closeThis function has no parameters to specify the price of the order to be settled, and is mainly used to settle immediately at the current market price.


3、strategy.close_all

strategy.close_allThe function is used to flatten out all current holdings, since the Pine script can only flatten out in one direction, i.e. if there is a signal trigger opposite to the current holdings, the current holdings will be flattened and the position will be triggered according to the signal.strategy.close_allWhen called, it will flatten all holdings in the current direction.strategy.close_allThe main parameters of the function are:when

Parameters are:

  • whenThis is the first time I have seen this.

Let's use an example to see:

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

When the test code starts, the hold is 0 (i.e.strategy.position_size==0True), so when the condition set by the when parameter is satisfied, the ID is executed only as a long string.strategy.entryInput function. After holding multiple positionsstrategy.position_sizeIf the ID is greater than 0, then the entry function for the shortened hash is likely to be executed. Since the current holds a multi-head position, this empty reverse signal that occurs at this time will cause the multi-head hold to be flattened and then reverse open.strategy.position_size < 0When holding the empty position, the current hold direction is flattened; and markenableStop := true◦ Stop the policy from running so that the logs can be viewed.

You can findstrategy.close_allThis function has no parameters to specify the price of the order to be settled, and is mainly used to settle immediately at the current market price.


4、strategy.exit

strategy.exitThe function is used for the placement operation of the entry holdings, which is different from the functionstrategy.closeandstrategy.close_allThe function is immediately settled at the current market price.strategy.exitThe function will plan the placement according to the parameter settings.

Parameters are:

  • id: Order ID for the current list of settlement conditions.
  • from_entry: Used to specify the login ID of the transaction to be settled.
  • qtyThe number of balances.
  • qty_percent: percentage of equity, range: 0 ~ 100.
  • profitThe following is a list of some of the most common types of fraud:
  • lossStop-loss target, expressed in points.
  • limitThis is the first time that the project has been approved by the European Commission.
  • stopStop loss target, price specified.
  • whenThis is the first time I have seen this.

Use a testing strategy to understand the use of parameters.

/*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")          // 如果仅仅指定一个id参数,则该退场订单无效,参数profit, limit, loss, stop等出场条件也至少需要设置一个,否则也无效
    strategy.exit("exit1", "long1", profit=50)                    // 由于long1入场订单没有成交,因此ID为exit1的出场订单也处于暂待状态,直到对应的入场订单成交才会放置exit1
    strategy.exit("exit2", "long2", qty=0.1, profit=100)          // 指定参数qty,平掉ID为long2的持仓中0.1个持仓
    strategy.exit("exit3", "long3", qty_percent=50, limit=strategy.opentrades.entry_price(findOrderIdx("long3")) + 1000)   // 指定参数qty_percent,平掉ID为long3的持仓中50%的持仓
    isExit := true 

if bar_index == 0 
    runtime.log("每点价格为:", syminfo.mintick)    // 每点价格和Pine语言模板参数上「定价货币精度」参数设置有关

Using a real-time price model retest test, this test strategy starts by performing three entry operations.strategy.entryfunction), the long1 function is deliberately setlimitParameter, the order price is 1 which makes it impossible to transact. Then test the condition exit functionstrategy.exit⇒ Stop-loss operations are used by point number, stop-loss operations by price, stop-loss operations by percentage, and stop-loss operations by number of positions.strategy.exitThe function also has more complex tracking stop loss parameters:trail_pricetrail_pointstrail_offsetYou can also try learning how to use it in this example.


5、strategy.cancel

strategy.cancelFunctions used to cancel/deactivate all pending commands; these functionsstrategy.order, strategy.entry , strategy.exitThe main parameters of this function are:idwhen

Parameters are:

  • idThe following is a link to the post:
  • whenThis is the first time I have seen this.

This function is well understood to be used to cancel uncompleted login commands.

/*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.3, limit=3)

if not barstate.ishistory and close < open 
    strategy.cancel("long1")
    strategy.cancel("long2")
    strategy.cancel("long3")
    isStop := true 

6、strategy.cancel_all

strategy.cancel_allFunctions andstrategy.cancelFunctions similar to ⇒ cancel/deactivate all preload commands ⇒ can be specifiedwhenParameters are.

Parameters are:

  • whenThis is the first time I have seen this.
/*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.3, limit=3)

if not barstate.ishistory and close < open 
    strategy.cancel_all()
    isStop := true 

7、strategy.order

strategy.orderFunctions, parameters, and so on are almost identical to functions.strategy.entryConsistency is the difference betweenstrategy.orderThe function is notstrategyof the functionpyramidingParameter setting affects, there is no limit on the number of times it is used.

Parameters are:

  • id: can be understood to refer to a name for a trading position. The id can refer to the cancellation, modification of orders, or placement.
  • direction: if the order direction is to do more (buy) this parameter is passedstrategy.longThis built-in variable is passed if you want to empty (sell).strategy.shortThis variable.
  • qty: Specify the order quantity, if this parameter is not passed, the default order quantity is used.
  • when: execution condition, which can be specified to control whether the current subroutine operation is triggered or not.
  • limit: specify the order limit price.
  • stopThe price of the product is very low.

We usestrategy.orderThere's no limit to this property, combined withstrategy.exitConditional exit function ─ construct a script similar to a grid transaction ─ example is very simple, just for learning:

/*backtest
start: 2021-03-01 00:00:00
end: 2022-08-30 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"ETH_USDT"}]
args: [["ZPrecision",0,358374]]
*/

varip beginPrice = -1

if not barstate.ishistory
    if beginPrice == -1 or (math.abs(close - beginPrice) > 1000 and strategy.opentrades == 0) 
        beginPrice := close
    
    for i = 0 to 20
        strategy.order("buy"+i, strategy.long, 0.01, limit=beginPrice-i*200, when=(beginPrice-i*200)<close)
        strategy.exit("coverBuy"+i, "buy"+i, qty=0.01, profit=200)
        
        strategy.order("sell"+i, strategy.short, 0.01, limit=beginPrice+i*200, when=(beginPrice+i*200)>close)
        strategy.exit("coverSell"+i, "sell"+i, qty=0.01, profit=200)

Strategic examples

The strategy examples in this tutorial are for teaching strategies and guiding strategy design ideas only and do not make any trading guidelines or suggestions.

The strategy of the supertrend indicator

strategy("supertrend", overlay=true)

[supertrend, direction] = ta.supertrend(input(5, "factor"), input.int(10, "atrPeriod"))

plot(direction < 0 ? supertrend : na, "Up direction", color = color.green, style=plot.style_linebr)
plot(direction > 0 ? supertrend : na, "Down direction", color = color.red, style=plot.style_linebr)

if direction < 0
    if supertrend > supertrend[2]
        strategy.entry("entry long", strategy.long)
    else if strategy.position_size < 0
        strategy.close_all()
else if direction > 0
    if supertrend < supertrend[3]
        strategy.entry("entry short", strategy.short)
    else if strategy.position_size > 0
        strategy.close_all()

It is very simple to write a trend strategy using the Pine language, here we design a simple trend tracking strategy with a super trend indicator. Let's analyze the source code of this strategy together.

The first thing to do is to start using the strategy code.strategyThe function does some simple settings:strategy("supertrend", overlay=true), just set a policy header for the supertrend header.overlayThe parameter istrueThe first thing we do when we design a Pine strategy or learn a Pine strategy script is to look at the strategy interface parameter design, and we look at the source code for the "Supertrending Indicator Strategy", which contains some of the things we learned in previous courses.inputFunction

[supertrend, direction] = ta.supertrend(input(5, “factor”), input.int(10, “atrPeriod”))

inputFunction calls are used directly asta.supertrendThe parameters of the indicator function are used to calculate the supertrend indicator. Among them:

  • input(5, “factor”)
  • input.int(10, “atrPeriod”)

The function defaults to two parameter controls in the Pine language policy interface, as follows:

img

So you can see that the default on the control isinputFunctions andinputThe series function ((here isinput.intThe first parameter of the strategy can be set in the policy interface using these two functions.ta.supertrendThe parameter of the function. The supertrend indicator function calculates a price datasupertrendand one-way data.directionAnd then use it.plotFunction graphs, note that when graphing, the direction of the graph is drawn according to the supertrend indicator, and only the current direction is drawn; whendirectionThe current market trend is upward at -1.directionThe current market is trending downwards at 1 o'clock. So we can see that the current market is trending downwards.plotJudging when drawing a function graphdirectionGreater than, less than 0 ∞

The followingif ... else ifLogic is the judgment of the transaction signal, when the expressiondirection < 0Real-time indicates that the current market is in an uptrend stage, at which point if the price data in the super trend indicator is not available, the market will be in a downtrend.supertrendThe price of the supertrend indicator above the previous 2 BARs (i.e.supertrend[2],还记得历史操作符引用某个变量历史数据吧Remember, if there is a current holding, then a reverse downward single function call will first flatten the previous holding and then open the position according to the current trading direction.supertrend > supertrend[2]The conditions have not been met, as long as it is at this time.strategy.position_size < 0This is the same as holding the empty position.strategy.close_all()Function execution, performing all of the balances.

direction > 0It is the same when you are in the downtrend phase, if you have multiple holdings and all of them are even, then the conditions are met.supertrend < supertrend[3]I'm trying to figure out why I'm setting it to "Do Not Track".[3]What about the price data of the super trend indicator on the first third of the BAR? This may be the intention of the strategists, after all, some markets, such as the contract trading market, have a slightly higher risk of doing nothing than doing more.

For theta.supertrendThe indicator, is it something that some of your classmates are interested in, how do you tell if the current trend is up or down?

In fact, this indicator can also be implemented in the form of a custom function in the Pine language:

pine_supertrend(factor, atrPeriod) =>
	src = hl2
	atr = ta.atr(atrPeriod)
	upperBand = src + factor * atr
	lowerBand = src - factor * atr
	prevLowerBand = nz(lowerBand[1])
	prevUpperBand = nz(upperBand[1])

	lowerBand := lowerBand > prevLowerBand or close[1] < prevLowerBand ? lowerBand : prevLowerBand
	upperBand := upperBand < prevUpperBand or close[1] > prevUpperBand ? upperBand : prevUpperBand
	int direction = na
	float superTrend = na
	prevSuperTrend = superTrend[1]
	if na(atr[1])
		direction := 1
	else if prevSuperTrend == prevUpperBand
		direction := close > upperBand ? -1 : 1
	else
		direction := close < lowerBand ? 1 : -1
	superTrend := direction == -1 ? lowerBand : upperBand
	[superTrend, direction]

This is a custom function with built-in functions.ta.supertrendThe same algorithm, of course, and the same indicator data are calculated. From this custom function algorithm we can see that the built-in supertrend indicator computation used by Pine ishl2The built-in variable ((highest price, lowest price plus then divided by 2, i.e. the average of the lowest price of the highest price) then calculates the ATR indicator ((wavelength) of a given cycle according to the parameter atrPeriod. Then using hl2 and ATR construct the uptrack, downtrack.

Updates based on the three-letter expression in the codelowerBandandupperBand

    lowerBand := lowerBand > prevLowerBand or close[1] < prevLowerBand ? lowerBand : prevLowerBand
    upperBand := upperBand < prevUpperBand or close[1] > prevUpperBand ? upperBand : prevUpperBand

lowerBand: downtrend line, used to determine whether there is a change in the uptrend. upperBand: uptrend line, used to determine whether there is a change in the downtrend. both lowerBand and upperBand are always being calculated, only in this custom function the current trend direction is finally determined.

    else if prevSuperTrend == prevUpperBand
        direction := close > upperBand ? -1 : 1
    else
        direction := close < lowerBand ? 1 : -1

So if you're trying to figure out if the price value of the supertrend on the last bar isprevUpperBandThis is the uptrend, which is the current downtrend.closemore thanupperBandThe price breaks out, thinking that the trend is changing and converting to an uptrend.directionThe direction variable is set to −1 (uptrend); otherwise it is still set to 1 (downtrend); so you will see it in the supertrend strategy.if direction < 0When the signal conditions are triggered, do more.direction > 0When the signal conditions are triggered, do nothing.

    superTrend := direction == -1 ? lowerBand : upperBand
    [superTrend, direction]

Finally, the selected direction returns the price data and directional data of the specific supertrend indicator.

The dynamic balancing strategy

/*backtest
start: 2021-03-01 00:00:00
end: 2022-09-08 00:00:00
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Binance","currency":"ETH_USDT"}]
args: [["v_input_1",4374],["v_input_2",3],["v_input_3",300],["ZPrecision",0,358374]]
*/

varip balance = input(50000, "balance")
varip stocks = input(0, "stocks")

maxDiffValue = input(1000, "maxDiffValue")


if balance - close * stocks > maxDiffValue and not barstate.ishistory
    // more balance , open long 
    tradeAmount = (balance - close * stocks) / 2 / close
    strategy.order("long", strategy.long, tradeAmount)
    balance := balance - tradeAmount * close
    stocks := stocks + tradeAmount
    runtime.log("balance:", balance, ", stocks:", stocks, ", tradeAmount:", tradeAmount)

else if close * stocks - balance > maxDiffValue and not barstate.ishistory
    // more stocks , open short 
    tradeAmount = (close * stocks - balance) / 2 / close
    strategy.order("short", strategy.short, tradeAmount)
    balance := balance + tradeAmount * close
    stocks := stocks - tradeAmount
    runtime.log("balance:", balance, ", stocks:", stocks, ", tradeAmount:", tradeAmount)

plot(balance, title="balance value(quoteCurrency)", color=color.red)
plot(stocks*close, title="stocks value(quoteCurrency)", color=color.blue)

img

img

Let's continue to learn some of the strategies design paradigms in the Pine language, and this time we'll look at a dynamic balancing strategy.BaseCurrencyThe amount of (trade varieties) andQuoteCurrencyThe amount is always balanced. If the relative price of an asset increases, the value of the asset held in the account increases, the asset is sold. If the relative price of an asset decreases, the value of the asset held in the account decreases, the asset is bought. This is the so-called dynamic balancing strategy.

The disadvantages of this strategy are that, as shown in the retrospective chart, the strategy is more likely to float during the uptrend (or downtrend) phase.

Let's take a look at the strategic code design:

We used a simplified design to simulate a strategy in which a user would be able to use a computer to run a program.balance(i.e. the number of QuotCurrency assets) andstocksWe don't read the actual number of assets in the account, we just use the simulated amount to calculate the appropriate buy and sell. Then the key parameters that affect the grid that pulls out this dynamic balancing strategy are:maxDiffValueThis parameter is the criterion for balancing judgments.BaseCurrencyandQuoteCurrencyThe deviation is overmaxDiffValueIt's time to rebalance, sell high-priced assets, buy low-priced assets, and rebalance assets.

The triggering of the strategic trading signal must be meaningful only in the real-time BAR stage, so the strategic trading conditions are set in the if judgment.not barstate.ishistoryThe price of the product has been calculated on the basis of current prices.balanceThe value is exceeded.stocksBuy when the value is; sell when the value is; update after the transaction statement is executedbalanceandstocksThe next time the balance is triggered.

The above policy retest information contains the price of the variety at the time the policy retest started, which is 1458, so I set the parameter intentionally.balanceSet the parameter to 4374 ((1458*3))stocksFor example: 3. Keep the asset in balance when it starts.

Supertrend strategy with stop-loss tracking

In the previous lesson, we learnedstrategy.exitWe don't have an example of a position exit function, where the tracking stop-loss stop-loss functionality is explained.strategy.exitThe tracking stop-loss and stop-loss function is used to optimize a supertrending strategy.

Let's look at it first.strategy.exitThe tracking stop-loss stop-loss parameter of the function:

1、trail_priceParameter: triggers placement of this logical behavior in the tracking stop loss and stop loss position (specified by price)


More