istorical value can continue to be referenced. So we see that in the drawn three lines a, b, and c, line b is one BAR slower than line a, and line c is one BAR slower than line b. Line c is 2-BAR slower than line a.
We can pull the chart to the far left and observe that on the first K-line, both the values of b and c are null (na). This is because when the script is executed on the first K-line BAR, it does not exist when referring to the historical value of one or two periods forward, which does not exist. Therefore, we need to be careful when writing strategies to check whether referencing historical data will result in null values. If the null value is used carelessly, it will cause a series of calculation differences, and may even affect the real-time BAR. Usually we will use built-in functions na
, nz
to judge in the code (in fact, we have also encountered nz
, ```na`` in our previous vedios, do you remember which chapter it is?) deal with the case of null values, for example:
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
This is a way to handle possible references to null values (na).
We have learned a lot of operators in Pine language. These operators form expressions through various combinations with operands. So what is the priority of these operations when evaluating in expressions? Just like the arithmetic we learned in school, multiplication and division are calculated first, followed by addition and subtraction. The same goes for expressions in Pine language.
Priority | Operators |
---|---|
9 | [] |
8 | + 、- andnot in the unary operator |
7 | * 、/ 、% |
6 | + 、- in the binary operator |
5 | > 、< 、>= 、<= |
4 | == 、!= |
3 | and |
2 | or |
1 | ?: |
High-priority expressions are calculated first, and if the priorities are the same, it is evaluated from left to right. If you want to force a certain part to be evaluated first, you can use ()
to wrap the expression to force the part to be evaluated first.
We have already studied the concept of “marker” before, which is used as the name of a variable, that is, a variable is a marker that holds a value. So how do we declare a variable? What are the rules for declaring variables?
Declaration Mode: The first thing to write when declaring a variable is the “declaration mode”. There are three declaration modes for variables:
var
.varip
.The var
and varip
keywords have actually been studied in our previous chapter on Assignment Operators
, so we won’t go into details here. If nothing is written for the variable declaration mode, sucha as the statement: i = 1
, as we have also mentioned before, such a variable declared and assigned is executed on every K-line BAR.
Type The Pine language on FMZ is not strict about types, and it can generally be omitted. However, in order to be compatible with the scripting strategy on Trading View, variables can also be declared with types. For example:
int i = 0
float f = 1.1
The type requirements on Trading View are quite strict, and an error will be reported if the following code is used on Trading View:
baseLine0 = na // compile time error!
Marker Markers are variable names. The naming of markers has been mentioned in previous chapters, so you can review it here: https://www.fmz.com/bbs-topic/9637#markers
In summary, declaring a variable can be written as:
// [<declaration_mode>] [<type>] <marker> = value
declaration mode type marker = value
The assignment operator is used here: =
assigns a value to a variable when it is declared. When assigning, the value can be a string, number, expression, function call, if
, for
, while
, or switch
and other structures (these structural keywords and statement usage will be explained in detail in subsequent courses. In fact, we have learned simple if statement assignments in previous courses and you can review them).
Here we focus on the input function, which is a function that we will use frequently when designing and writing strategies. It is also a very critical function when designing strategies.
input function:
input function, parameters: defval、title、tooltip、inline、group
The input function on FMZ is somewhat different from that on Trading View, but this function is used as the assignment input of strategy parameters. Let’s use an example to illustrate the use of the input function on FMZ in detail:
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)
The input function is often used to assign values to variables when declaring them. The input function on FMZ draws controls for setting strategy parameters automatically in the FMZ strategy interface. The controls supported on FMZ currently include numeric input boxes, text input boxes, drop-down boxes, and Boolean checkboxes. And you can set the strategy parameter grouping, set the parameter prompt text message and other functions.
We introduce several main parameters of the input function:
In addition to individual variable declaration and assignment, there is also a way of declaring a group of variables and assigning them in the Pine language:
[Variable A, Variable B, Variable C] = function or structure, such as ```if```, ```for```, ```while``` or ```switch```
The most common one is when we use the ta.macd
function to calculate the MACD indicator, since the MACD indicator is a multi-line indicator, three sets of data are calculated. So it can be written as:
[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)
We can draw the MACD chart using the above code easily. Not only the built-in functions can return to multiple variables, but also the written custom functions can return to multiple data.
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)
The writing method of using if and other structures as multiple variable assignments is also similar to the above custom function, and you can try it if you are interested.
[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)
Some functions cannot be written in the local code block of the conditional branch, mainly including the following functions:
barcolor(), fill(), hline(), indicator(), plot(), plotcandle(), plotchar(), plotshape()
Trading View will compile with errors, FMZ is not as restrictive, but it is recommended to follow the specifications of Trading View. For example, although this does not report an error on FMZ, it is not recommended.
strategy("test", overlay=true)
if close > open
plot(close, title="close")
else
plot(open, title="open")
Explanation example:
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)
Key point: Expressions used for judgments that return Boolean values. Note the indentation. There can be at most one else branch. If all branch expressions are not true and there is no else branch, return na.
x = if close > open
close
plot(x, title="x")
The switch statement is also a branch-structured statement, which is used to design different paths to be executed according to certain conditions. Generally, the switch statement has the following key knowledge points.
There are two forms of switch, let’s look at the examples one by one to understand their usage.
switch
with expressions - example explanation:// 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)
We learned the input function before, here we continue to learn two functions similar to input: input.string
, input.int
functions.
input.string
is used to return a string, and the input.int
function is used to return an integer value. In the example, there is a new usage of the options
parameter. The options
parameter can be passed an array of optional values, such as options=["EMA", "SMA", "RMA", "WMA"]
and options=[5, 10, 20]
in the example (note that one is string type, the other is a numeric type). In this way, the controls on the strategy interface do not need to input specific values, but the controls become drop-down boxes to select these options provided in the options parameter.
The value of the variable func is a string, and the variable func is used as the expression for switch (which can be a variable, function call, or expression) to determine which branch in the switch is executed. If the variable func cannot match (ie, equal) the expression on any branch in the switch, the default branch code block will be executed, and the runtime.error("error")
function will be executed, causing the strategy to throw an exception and stop.
In our test code above, after the last line of runtime.error in the default branch code block of switch, we did not add code like [na, na] to be compatible with the return value. This problem needs to be considered on Trading View. If the type is inconsistent, an error will be reported. But on FMZ, since the type is not strictly required, this compatibility code can be omitted. Therefore, there is no need to consider the type compatibility of the return value of if and switch branches on 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 error will be reported on FMZ, but an error will be reported on trading view. Because the type returned by the if branch is inconsistent.
switch
without expressionsNext, let’s look at another way to use switch
, that is, 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)
As we can see from the test code example, switch will match the execution of the local code block that is true on the branch condition. In general, the branch conditions following a switch statement must be mutually exclusive. That is to say, up and down in the example cannot be true at the same time. Because switch can only execute the local code block of one branch, if you are interested, you can replace this line in the code: up = close > open // up = close < open
to the content in the comment, and backtest to observe the result. You will find that the switch branch can only execute the first branch. In addition, it is necessary to pay attention not to write function calls in the branch of switch as much as possible, the function cannot be called on each BAR may cause some data calculation problems (unless as in the example of "switch
with expressions", the execution branch is deterministic and will not be changed during strategy operation).
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
The for statement is very simple to use, the for loop can finally return a value (or multiple values, in the form of [a, b, c]). Like the variable assigned to the “return value” position in the pseudocode above. The for statement is followed by a “count” variable used to control the number of loops, refer to other values, etc. The “count” variable is assigned the “initial count” before the loop starts, then increments according to the “step length” setting, and the loop stops when the “count” variable is greater than the “final count”.
The break
keyword used in the for loop: the loop stops when the break
statement is executed.
The continue
keyword used in the for loop: When the continue
statement is executed, the loop will ignore the code after continue
and execute the next round of the loop directly. The for statement returns to the return value from the last execution of the loop. And it returns null if no code is executed.
Then we demonstrate with a simple example:
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")
The for ... in
statement has two forms, we will illustrate them in the following pseudocode.
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
We can see that the main difference between the two forms is the content that follows the for keyword, one is to use a variable as a variable that refers to the elements of the array, the other is to use a structure containing index variables, tuples of array element variables as references. For other return value rules, such as using break, continue, etc., are consistent with for loops. We also illustrate the use with a simple example.
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")
When it need to use the index, use the grammar for [i, ele] in testArray
.
Application of for loops
We can use the built-in functions provided in the Pine language to complete some of the loop logic calculations, either written by using the loop structure directly or processed using the built-in functions. Let’s take two examples:
When designing with a loop structure:
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 example uses a for loop to calculate the sum and then calculate the mean value.
Calculate the moving average directly using the built-in function:
plot(ta.sma(close, length), title="ta.sma", overlay=true)
Use the built-in function ta.sma
directly to calculate the moving average indicator. Obviously, it is simpler to use the built-in function for calculating the moving average. By comparing on the chart, it can be seen that the calculated results are exactly the same.
We still use the example above to illustrate.
When designing with a loop structure:
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 in an array, we can use a loop to process it, or use the built-in function array.sum
to calculate.
Calculate the sum 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)
We can see the calculated data is exactly the same as that displayed on the chart using plot.
So why design loops when we can do all these with built-in functions? The use of loops is mainly based on the application of these 3 points:
The while
statement keeps the code in the loop section executing until the judgment condition in the while structure is false.
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
Other rules of while are similar to those of the for loop. The last line of the local code block of the loop body is the return value, which can return multiple values. Execute the loop when the “loop condition” is true, and stop the loop when the condition is false. The break and continue statements can also be used in the loop body.
We will still use the example of calculating moving averages for demonstration:
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)
We can see that the while loop is also very simple to use, and it is also possible to design some calculation logic that cannot be replaced by the built-in functions, such as calculating 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
The definition of arrays in the Pine language is similar to that in other programming languages. Pine’s arrays are one-dimensional arrays. Usually it’s used to store a continuous series of data. The single data stored in the array is called the element of the array, and the types of these elements can be: integer, floating point, string, color value, boolean value. The Pine language on FMZ is not very strict about types, and it can even store strings and numbers in an array at the same time. Since the underlying structure of the array is also a series structure, if the historical operator is used to refer to the array state on the previous BAR. So instead of using the historical operator []
to refer to an element in the array, we need to use the functions array.get()
and array.set()
. The index order of the elements in the array is that the index of the first element of the array is 0, and the index of the next element is incremented by 1.
We illustrate it with a simple code:
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))
Use array<int> a
, float[] b
to declare an array or just declare a variable that can be assigned 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")
Array variables are initialized by using the array.new
and array.from
functions. There are also many types-related functions similar to array.new in the Pine language: array.new_int()
, array.new_bool()
, array.new_color()
, array.new_string()
, etc.
The var keyword also works with the array declaration mode. Arrays declared with the var keyword are initialized only on the first BAR. Let’s observe 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 of the array a have been continuously determined and have not been reset. The array b is initialized on each BAR. Finally, when barstate.islast
is true, there is still only one element printed with a value of 0.
Use array.get to get the element at the specified index position in the array, and use array.set to modify the element at the specified index position in the array.
The first parameter of array.get is the array to be processed, and the second parameter is the specified index. The first parameter to 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.
We use the following simple example to illustrate:
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")
The example initializes the base color green, declares and initializes an array to store colors, and then assigns different transparency values to the colors (by using the color.new function). The color level is calculated by calculating the distance of the current BAR from the maximum value of high in 100 lookback periods. The closer the distance to the maximum value of HIGH in the last 100 lookback cycles, the higher the rank and the darker (lower transparency) the corresponding color value. Many similar strategies use this method to represent the current price level within N lookback periods.
How to iterate through an array, we can use the for/for in/while statements that we have 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")
These three traversal methods have the same execution results.
Arrays can be declared in the global scope of a script, or in the local scope of a function or if branch.
For the use of elements in arrays, the following ways are equivalent. We can see by the following example that two sets of lines are drawn on the chart, two in each set, and the two lines in each set have exactly the same value.
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)
array.unshift()
, array.insert()
, array.push()
.
array.remove()
, array.shift()
, array.pop()
, array.clear()
.
We use the following example to test these array adding and deleting operation functions.
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")
Application of additions, deletions: arrays as queues
We can construct a “queue” data structure by using arrays and some functions of adding and deleting arrays. Queues can be used to calculate the moving average of tick prices. Someone may ask, “Why should we construct a queue structure? Didn’t we use arrays to calculate the average before?”
A queue is a structure that is often used in the field of programming, the characteristics of a queue are:
The element that enters the queue first, leaves the queue first.
In this way, it ensures that the data in the queue is the latest data, and that the length of the queue will not expand indefinitely.
In the following example, we use a queue structure to record the price of each tick, calculate the mobile average price at the tick level, and then compare it with the moving average 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 array a, we specify the declaration mode and use the keyword variant
. In this way, each price change will be recorded in array a.
Calculate correlation functions:
array.avg()
calculates the average value of all elements in an array, array.min()
calculates the smallest element in an array, array.max()
calculates the smallest element in an array, array.stdev()
calculates the standard deviation of all elements in an array, array.sum()
calculates the standard deviation of all elements in an array.
Operation-related functions:
array.concat()
to merge or concatenate two arrays.
array.copy()
to copy the array.
array.join
to concatenates all the elements of an array into a string.
array.sort()
to sort by ascending or descending order.
array.reverse()
to reverse the array.
array.slice()
to slice the array.
array.includes()
to judge the element.
array.indexof()
to return to the index of the first occurrence of the value passed in as a parameter. If the value is not found, -1 will be returned.
array.lastindexof()
to find the last occurrence of the value.
Test examples of array calculation-related functions:
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")
These are commonly used array calculation functions.
Examples of operation-related functions:
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")
The Pine language can be designed with custom functions. In general, the following rules applied to custom functions in the Pine language:
barcolor(), fill(), hline(), plot(), plotbar(), plotcandle()
) cannot be called in custom functions.We have also used the custom functions for many times in our previous tutorials, such as those designed as a single line:
barIsUp() => close > open
Whether the current BAR is a positive line when the function returns.
Custom functions designed to be multiple line:
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)
We use a custom function to realize a function of sma average calculation.
In addition, two examples of custom functions that we can return:
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)
One function can calculate the fast line, slow line and two EMA averages.
Built-in functions can be easily found in the FMZ PINE Script document.
Classification of built-in functions in the Pine language:
str.
series.color.
series.input.
series.ta.
series.plot.
series.array.
series.strategy.
series.math.
series.request.
series functions, type handling functions, etc.)The strategy.
series of functions are functions that we often use in the design of strategies, and these functions are closely related to the execution of trading operations when the strategy is running specifically.
strategy.entry
strategy.entry
function is a more important function when we write a strategy to place an order, several important parameters for the function are: id
, direction
, qty
, when
, etc.
Parameters:
id
: This can be understood as giving a name to a trading position for referencing. This id can be referenced to cancel, modify orders and close positions.direction
: If the direction of the order is long (buy), pass in the built-in variable strategy.long
, and if you want to go short (sell), pass in the variable strategy.short
.qty
: Specify the amount of orders to be placed, if this parameter is not passed, the default amount of orders will be used.when
: Execution condition, you can specify this parameter to control whether this current order operation is triggered or not.limit
: Specify the order limit price.stop
: Stop loss price.The specific execution details of the strategy.entry
function are controlled by the parameter settings when the strategy
function is called, and it can also be controlled by the [“Pine Language Trade-Class Library Template Arguments”](https://www.fmz.com/bbs-topic/9293#template- arguments-of-pine-language-trade-class-library) setting control, Pine language trade-class library template arguments control more details of the transaction, you can check the linked documentation for details.
We focus on the the pyramiding
, default_qty_value
parameters in the strategy
function. We use the following code for 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 part at the beginning of the code /* backtest... */
is a backtest setting, which is used to record the backtest setting time and other information at that time for debugging, not the startegy code.
In the code: strategy(title = "open long example", pyramiding = 3, default_qty_value=0.1, overlay=true)
, when we specify the pyramiding
parameter as 3, we set the maximum number of trades in the same direction to 3. So one of the four strategy.entry
order operations in the example is not executed. Since we also specified the default_qty_value
parameter to be 0.1, this strategy.entry
operation with ID long1
has a default order size of 0.1. strategy.entry
function call when we specify the direction
as strategy.long
, so the backtest test orders are all buy orders.
Note that the order operation strategy.entry("long3", ...
in the code is called twice, for the same ID: long3
, the first strategy.entry
order operation was not filled, and the second call to the strategy.entry
function was to modify the order for this ID (the data shown in the backtest test also shows that the order quantity for this limit order was modified to 0.3). Another case, for example, if the first order with ID “long3” is filled, continue to use the strategy.entry
function to place orders according to the ID “long3”, then the order positions will be accumulated on the ID “long3”.
strategy.close
The strategy.close
function is used to close the entry position with the specified identification ID. The main parameters are: id
, when
, qty
, qty_percent
.
Parameters:
id
: The entry ID that needs to be closed is the ID that we specify when we open a position using an entry order function, such as strategy.entry
.when
: Execution conditions.qty
: Number of closed positions.qty_percent
: Percentage of closed positions.Let’s familiarize with the details of the use of this function through an example:
The /*backtest ... */
in the code is the configuration information for FMZ.COM international website backtest, you can delete it and set the market, variety, time range and other information you need 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("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
The test strategy shows three consecutive long entries with the entry ID “long1”, and then use different parameters of the strategy.close
function to set the different results of the backtest when closing a position. It can be found that strategy.close
function has no parameters to specify the price of the order to close the position, this function is mainly used to close the position immediately at the current market price.
strategy.close_all
The function strategy.close_all
is used to close all current positions, because the Pine language script positions can only have a direction, that is, if there is a signal triggered in the opposite direction of the current position will close the current position and then open it according to the signal trigger. So strategy.close_all
will close all the positions in the current direction when it is called. The main parameter of the strategy.close_all
function is: when
.
Parameters:
when
: Execution conditions.Let’s use an example to observe:
/*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
The test code starts with a position number of 0 (i.e. strategy.position_size==0
is true), so when the conditions set by the when parameter are met, only the strategy.entry
entry function with ID “long” is executed. After holding a long position, strategy.position_size
is greater than 0, then the entry function with ID “short” may be executed, since the current long position is held, this shorting reversal signal at this time will result in closing the long position and then opening the short position in the opposite direction. Then we write in the if condition that when strategy.position_size < 0
, i.e. when holding a short position, all positions in the current holding direction will be closed. And mark enableStop := true
. Stops the strategy execution so that the log can be observed.
It can be found that the function strategy.close_all
has no parameters to specify the price of closing the order, this function is mainly used to immediately close the position at the current market price.
strategy.exit
The strategy.exit
function is used to close an entry position. Unlike this function, the strategy.close
and strategy.close_all
functions close a position immediately at the current market price. The strategy.exit
function will close the position according to the parameter settings.
Parameters:
id
: The order identifier ID of the current closeout condition order.from_entry
: Used to specify the entry ID of the position to be closed.qty
: Number of closed positions.qty_percent
: Percentage of closed positions, range: 0 ~ 100.profit
: Profit target, expressed in points.loss
: Stop loss target, expressed in points.limit
: Profit target, specified by price.stop
: Stop loss target, specified by price.when
: Execution conditions.Use a test strategy to understand the parameter usage.
/*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
We use the real-time price model for backtest, the test strategy starts with 3 entry operations (strategy.entry
function), and long1
is intentionally set with limit
parameter with a pending order price of 1, so that it cannot be filled. Then test the conditional exit function strategy.exit
. We used the ‘‘take profit by point’’ and ‘‘take profit by price’’, the close a fixed number of positions, and the close positions by percentage. Given the length of the example, only the “take profit” is demonstrated. The stop-loss operation is also the same. The strategy.exit
function also has more complex trailing stop parameters: trail_price
, trail_points
, trail_offset
can also be tested in this example to learn their usage.
strategy.cancel
The strategy.cancel
functions are used to cancel/stop all pre-pending orders. These functions: strategy.order
, strategy.entry
, strategy.exit
can generate entry IDs. The main parameters of this function are: id
, when
.
Parameters:
id
: The admission ID to be cancelled.when
: Execution conditions.This function is easy to understood, and it is used to cancel entry orders that are not filled.
/*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