Pine语言

本文仅供个人学习记录参考,参考教程:

Pine-Script-Docs-v5

发明者量化PINE语言入门教程 - FMZ

Note

tradingview的移动止盈问题

k线放大器可以减少误差,但是同样不能解决该问题

tradingview的回测机制

pine语言在回测时为收盘价模型,只会考虑这条k线收盘时的high,low,open,close,不会考虑先后,

对于实盘模型的最新K柱,指标的计算会随着数据更新一直执行,与真实情况可能会有所不同,叫做重绘,但这种重绘不一定是坏处,然而对于平时收盘价模型回测时出现的回测通常是用到了未来函数,这种情况可以用k线回放解决

但是对于策略的买入卖出操作,只会在实盘时设置calc_on_every_tick=true(即勾选每笔数据上为true),时才会实时执行,否则默认在实时柱关闭时执行一次模型,然后再第二天的开盘时执行买入卖出,所以开启calc_on_every_tick=true后可能会出现在同一根k线上多次触发买入卖出的情况

建议不要勾选calc_on_every_tick=true,这样并不会影响止盈止损不及时,因为订单执行会被放到tradingview的虚拟交易市场,并不影响止盈止损

pine的订单id和订单标识符

pine的每个entry和exit都有一个订单id,对于strategy.exit他自己需要有一个订单id,此外还需要指定一个from_entry,说明他要退出的是订单是哪个,这个from_entry即strategy.entry的订单id

pine的下单机制

假设在2023-11-15的K线上执行

1.strategy.entry("long",strategy.long)

2.strategy.close("long")

3.if strategy.opentrades==0 x:=0

在2023-11-16号执行

if strategy.opentrades!=0 plot(x)

会发现x画出来为0,这是因为在15号执行了2.close long之后 3.成立,就已经把x赋值为0了,16号会先执行15号还没执行的entry,执行完后opentrades又重新不等于0了,此时plot(x)会发现为0,所以需要注意这种细节

顺序

1.先对之前的订单执行止盈止损平仓等操作,平仓后重新给变量赋值为0

2.再对新的订单执行开单操作,开单后重新给变量赋值为应有的值

3.再进行plot等操作

Pine语言给变量重新赋值

假设1.先给x赋值为1 2.下单 3.执行 if strategy.opentrades!=0 x=0

那么即使刚下完单,因为下单是在第二根k线开盘价执行,所以x相当于又变为0了,所以一般需要这样:

if strategy.position_size==0 and strategy.position_size[1]!=0

        x=0

Pine语言的下单需要取消

如图在下面这个网格加仓中,对于strategy.entry为一个挂单,指定了limit为某一个价格相当于是一个限价单,他会一直挂在那里等着成交,所以需要再清仓后取消所有订单

pine的限定类型
pine的基本类型

Pine语言模型

收盘价模型和实时价模型

  • 收盘价模型:

策略代码执行时,当前K线的Bar周期(如1分钟)完全执行完成,K线闭合时即K线周期已经走完,此时执行一遍Pine策略逻辑,触发的交易信号将在下一根K线Bar开始时执行

  • 实时价模型:

策略代码按照tick(如1秒)执行时,当前K线Bar不论是否闭合,每次行情变动,即新tick就会执行一遍Pine策略逻辑,触发的交易信号立即执行

实时Bar和历史Bar

  • 历史Bar

策略设置为「实盘价模型」开始执行时,图表上除了最右侧的那一根K线Bar之外所有K线Bar都是历史Bar。策略逻辑在每根历史Bar上仅执行一次。
策略设置为「收盘价模型」开始执行时,图表上所有Bar都是历史Bar。策略逻辑在每根历史Bar上仅执行一次。        

基于历史Bar的计算:
策略代码在历史Bar收盘状态下执行一次,然后策略代码继续在下一个历史Bar执行,直到所有历史Bar都执行一次。

  • 实时Bar

当策略执行到最右边的最后一根K线Bar上时,该Bar为实时Bar。当实时Bar闭合之后,这根Bar就变成了一个经过的实时Bar(变成了历史Bar)。图表最右侧会产生新的实时Bar。

策略设置为「实时价模型」开始执行时,在实时Bar上每次行情变动都会执行一次策略逻辑。
策略设置为「收盘价模型」开始执行时,图表上不显示实时Bar。

基于实时Bar的计算:
如果设置策略为「收盘价模型」图表不显示实时Bar,策略代码只在当前Bar收盘时执行一次。
如果设置策略为「实盘价模型」在实时Bar上的计算和历史Bar就完全不同了,在实盘Bar上每次行情变动都会执行一次策略代码。例如内置变量highlowclose在历史Bar上是确定的,在实时Bar上可能每次行情变动时这些值是会发生变化的。所以基于这些值计算的指标等数据也是会实时变动的。在实时Bar上close始终代表当前最新价格,highlow始终代表自当前实时Bar开始以来达到的最高高点和最低低点。这些内置变量代表实时Bar最后一次更新时的最终值。

实时Bar上执行策略时的回滚机制(实时价模型):
在实时Bar执行时,策略的每次新迭代执行前重置用户定义的变量称为回滚。我们来以一个例子理解回滚机制,如下测试代码。

Pine语言函数的变量上下文

Pine语言中函数中的变量和函数外的变量有着以下差异:

Pine函数中使用的系列变量的历史是通过对函数的每次连续调用创建的。如果没有在脚本运行的每个柱上调用函数,这将导致函数本地块内部与外部系列的历史值之间存在差异。因此,如果没有在每个柱上调用函数,则使用相同索引值在函数内部和外部引用的系列将不会引用相同的历史点。

对于以下代码虽然f和f2()都是使用[1]来引用数据系列上的历史数据,但是最终图上A和B的位置是不一样的,因为使用f(a)=>a[1]这种方式引用的值,并不是在每根Bar上都调用

f(a) => a[1]
f2() => close[1]  

oneBarInTwo = bar_index % 2 == 0
plotchar(oneBarInTwo ? f(close) : na, title = "f(close)", color = color.red, location = location.absolute, style = shape.xcross, overlay = true, char = "A")   
plotchar(oneBarInTwo ? f2() : na, title = "f2()", color = color.green, location = location.absolute, style = shape.circle, overlay = true, char = "B")   
plot(close[2], title = "close[2]", color = color.red, overlay = true)
plot(close[1], title = "close[1]", color = color.green, overlay = true)

Pine的一些内置函数需要在每一根Bar上计算才能得到正确结果

ta.barssince(condition):计算从最近一次condition成立时K线的数量,如果在当前K线之前从未满足该条件,则该函数返回na。

res = close > close[1] ? ta.barssince(close < close[1]) : -1
plot(res, style = plot.style_histogram, color=res >= 0 ? color.red : color.blue)

可以看到任何时候,ta.barssince返回值都是na,这是因为我们只在close>close[1]时才调用了该函数,为了避免该问题,我们需要把ta.barssince(close < close[1])拿出来,写在任何可能的条件分支外部,使得其在每根K线bar上都执行计算

a=ta.barssince(close < close[1]) //从上次条件为true起,计算K线数量。
res=close>close[1]?a:-1  //当是阳线时,返回上一次连续阴线的长度
plot(res,style=plot.style_histogram,color=res>=0?color.red:color.blue)

Pine语言时间序列

时间序列是Pine语言中的一个基本你概念,它不是一种类型,而是随着时间存储的连续值的基本结构,Pine语言是基于图表的,最基本的内容是K线图,时间序列的每个值都与一个K线Bar的时间戳关联,例如open内置变量存储了每根K线开盘价的时间序列,open[1]就表示当前脚本执行的这根K线Bar的上一根K线Bar的开盘价

此外函数调用结果同样会在时间序列上留下痕迹,以下代码为例:

a = ta.highest(close, 10)[1]
b = ta.highest(close[1], 10)
plotchar(true, title="a", char=str.tostring(a), location=location.abovebar, color=color.red, overlay=true)
plotchar(true, title="b", char=str.tostring(b), location=location.belowbar, color=color.green, overlay=true)

可以看到a和b的值是相同的,都是函数调用结果的时间序列

Pine语言打印日志

在TradingView上使用log.info即可

//@version=5
strategy("My strategy", overlay = true, margin_long = 100, margin_short = 100, process_orders_on_close = true)
bracketTickSizeInput = input.int(1000, "Stoploss/Take-Profit distance (in ticks)")

longCondition = ta.crossover(ta.sma(close, 14), ta.sma(close, 28))
if (longCondition)
    limitLevel = close * 1.01
    log.info("Long limit order has been placed at {0}", limitLevel)
    strategy.order("My Long Entry Id", strategy.long, limit = limitLevel)

    log.info("Exit orders have been placed: Take-profit at {0}, Stop-loss at {1}", close)
    strategy.exit("Exit", "My Long Entry Id", profit = bracketTickSizeInput, loss = bracketTickSizeInput)

if strategy.opentrades > 10
    log.warning("{0} positions opened in the same direction in a row. Try adjusting `bracketTickSizeInput`", strategy.opentrades)

last10Perc = strategy.initial_capital / 10 > strategy.equity
if (last10Perc and not last10Perc[1])
    log.error("The strategy has lost 90% of the initial capital!")

Pine脚本结构

Pine脚本包括版本号,

//@version=5

declaration_statement,声明是指标还是策略,如指标

indicator("My indicator")

或者策略

strategy("My strategy")

接下来即为Code,整体结构如下

<version>
<declaration_statement>
<code>

注释

对于Pine脚本,Tradingview只支持单行注释//

代码

代码:即脚本中不是注释或者编译器指令的语句,可以是以下内容之一

  • 变量声明
  • 变量的重新赋值
  • 函数声明
  • 内置函数调用,用户定义的函数调用
  • ifforwhileswitch等结构

语句排列方式

  • 有些语句可以用一行表示,例如大多数变量声明,只包含一个函数调用的行或单行函数声明,其他的,像结构,总是需要多行,所以需要一个局部快
  • 脚本的全局范围内的语句,不能以空格制表符(tab键)开始。它们的第一个字符必须是该行的第一个位置开始
  • 结构或者多行函数声明总是需要一个local block,一个本地块必须缩进一个制表符或四个空格,否则会被解析成上一行代码的连续内容,每个局部块定义了不同的局部范围
  • 多个单行语句可以逗号(,)作为分隔符在一行中串联起来。
  • 长行可以被分割在多行上,但是缩进不能是4的倍数(因为如果是4的倍数即tab,就会被当做不是上一行代码的连续内容)

以下代码包含3个局部块,一个在自定义函数声明中,两个在变量声明的if结构中:

indicator("", "", true)             // 声明语句(全局范围),可以省略不写

barIsUp() =>                        // 函数声明(全局范围)
    close > open                    // 本地块(本地范围)

plotColor = if barIsUp()            // 变量声明 (全局范围)
    color.green                     // 本地块 (本地范围)
else
    color.red                       // 本地块 (本地范围)

runtime.log("color", color = plotColor)  // 调用一个内置函数输出日志 (全局范围)

Pine运算符

Pine的运算符包括赋值运算符、算术运算符、比较运算符、逻辑运算符、? : 三元运算符、[]历史引用运算符。

赋值运算符

赋值运算符包括有2种 =:=

=运算符用于给变量初始化时赋值,使用=初始化的变量在之后的每根Bar上都以该值开始

:=用于将值重新赋值给现有变量,如果:=操作符给未初始化或声明的变量赋值会引发错误

此外对于以下代码,时不时会认为只要出现一个阳线bar,b就会持续加1呢?实际上并不会,因为我们使用b=0初始化b后,在每一根bar上面代码执行时都会把b重新赋值为0

a=close>open
b=0
if a
    b:=b+1
plot(b)

讲到这里,我们扩展两个关键字:var、varip

  • var:

var 是用于分配和一次性初始化变量的关键字。通常,不包含关键字var的变量赋值语法会导致每次更新数据时都会覆盖变量的值。 与此相反,当使用关键字var分配变量时,尽管数据更新,它们仍可以“保持状态”

可以看到使用var声明变量时,b的值正常增加

  • varip:

varip(var intrabar persist)是用于分配和一次性初始化变量的关键词。它与var关键词相似,但是使用varip声明的变量在实时K线更新之间保留其值。

strategy(overlay=true)

// 测试 var varip
var i = 0
varip ii = 0  

// 将策略逻辑每轮改变的i、ii打印在图上
plotchar(true, title="ii", char=str.tostring(ii), location=location.abovebar, color=color.red)
plotchar(true, title="i", char=str.tostring(i), location=location.belowbar, color=color.green)  

// 每轮逻辑执行都给i、ii递增1
i := i + 1
ii := ii + 1

可以看到以上代码使用varip时,在实时价模型下,ii的值在同一根K线bar收束前自增结果也被保留了下来

算数运算符

运算符说明
+加法
-减法
*乘法
/除法
%求模

比较运算符

运算符说明
<小于
>大于
<=小于等于
>=大于等于
==相等
!=不相等

逻辑运算符

运算符代码符号说明
not一元操作符,非运算
and二元操作符,与(且)运算
or二元操作符,或运算

三元运算符

a = close > open
b = a ? math.abs(close-open) > 30 ? "阳线" : "十字星" : math.abs(close-open) > 30 ? "阴线" : "十字星"
c = not a ? math.abs(close-open) > 30 ? "阴线" : "十字星" : math.abs(close-open) > 30 ? "阳线" : "十字星"
plotchar(a, location=location.abovebar, color=color.red, char=b, overlay=true)
plotchar(not a, location=location.belowbar, color=color.green, char=c, overlay=true)

历史运算符

历史运算符[],用于获取时间序列上的历史值。以下代码包括对空值(na)的处理

b = close[1]
c = b[1]
// 当引用close内置变量前一个BAR的历史值时,如果不存在,则使用open内置变量
close > nz(close[1], open)

Pine变量

变量声明包括声明模式、类型、标识符

声明模式

  1. var
  2. varip
  3. 什么都不写

这三者区别在历史运算符提到过,即:

  • 什么都不写,不包含关键字var的变量赋值语法会导致每次更新数据时都会覆盖变量的值
  • varip和var的区别在于varip在实时bar上执行时会保存之前的值,而var不会

类型

类型可写可不写,基本类型有int,string,float,bool,特殊类型有array,matrix,map

标识符

标识符即变量的名字,这个不再赘述

总结一下,声明变量的形式

// [<declaration_mode>] [<type>] <identifier> = value 
   声明模式             类型     标识符       = 值

input声明

此外对于变量的声明,可以作为策略参数的赋值输入使用,以该代码为例

param1 = input(10, title="参数1名称", tooltip="参数1的描述信息", group="分组名称A")
param2 = input("close", title="参数2名称", tooltip="参数2的描述信息", group="分组名称A")
param3 = input(color.red, title="参数3名称", tooltip="参数3的描述信息", group="分组名称B")
param4 = input(close, title="参数4名称", tooltip="参数4的描述信息", group="分组名称B")
param5 = input(true, title="参数5名称", tooltip="参数5的描述信息", group="分组名称C")

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

主要参数如下:

  • defval :作为input函数设置的策略参数选项的默认值,支持Pine语言的内置变量、数值、字符串
  • title :策略在实盘/回测的策略界面上显示的参数名称。
  • tooltip :策略参数的提示信息,当鼠标悬停在策略参数上会显示出这个参数设置的文本信息。
  • group :策略参数分组名称,可以给参数分组。

声明一组变量

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)

条件结构

尽量不要把函数调用写在条件结构中,函数无法在每个BAR上被调用可能引起数据计算的问题

if

条件分支即if、else if、else,且在Pine中可以直接用 if 表达式给变量赋值,如果所有表达式都不为真,则返回na

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

需要注意的是对于以下函数,无法写在条件分支的本地代码块

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

switch

  • 带表达式的switch:
func = input.string("EMA", title="指标名称", tooltip="选择要使用的指标函数名称", options=["EMA", "SMA", "RMA", "WMA"])

fastPeroid=input.int(10,title="快线周期",options=[5,10])
slowPeriod=input.int(20,title="慢线周期",options=[20,25,30])

data=input(close,title="数据",options="选择使用收盘价、开盘价、最高价...")
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]
    =>
        [na,na]
  • 不带表达式的switch

switch会匹配执行分支条件,直到第一个为真的为止

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
    => na
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)

循环结构

for

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

  • 情况一:
返回值 = for 数组元素 in 数组 
    语句                        // 注释:语句里可以有break,continue
    语句                        // 注释:最后一条语句为返回值
  • 情况二:
返回值 = for [索引变量, 索引变量对应的数组元素] in 数组
    语句                        // 注释:语句里可以有break,continue
    语句                        // 注释:最后一条语句为返回值 
testArray = array.from(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)


// 修改成 [i, ele]的形式:for [i, ele] in testArray ,
// runtime.log("ele:", ele, ", i:", i)
for ele in testArray            
    runtime.log("ele:", ele)

runtime.error("stop")
  1. 计算均值
    length = 5
    var a = array.new<float>(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)
    
  2. 求和
    length = 5
    var a = array.new<float>(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)
    

while

返回值 = while 判断条件
    语句                    // 注释:语句里可以有break,continue
    语句                    // 注释:最后一条语句为返回值
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)

计算阶乘:

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

数组 

创建:

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

遍历:

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)

历史数据引用:

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)

数组相关函数

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

队列

以下代码使用实时价模型,创建一个队列记录每次tick价格,计算出tick级别的移动平均价,然后和1分钟k线级别的移动平均线进行对比

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

数组常用计算、操作函数

计算:

array.avg()求数组中所有元素的平均值、array.min()求数组中最小的元素、array.max()求数组中最大的元素、array.stdev()求数组中所有元素的标准差、array.sum()求数组中所有元素的和。

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

操作:
array.concat()合并或连接两个数组。
array.copy()复制数组。
array.join将数组中的所有元素连接成一个字符串。
array.sort()按升序或降序排序。
array.reverse()反转数组。
array.slice()对数组进行切片。
array.includes()判断元素。
array.indexof()返回参数传入的值首次出现的索引。如果找不到该值,则返回 -1。
array.lastindexof()找到最后一次出现的值。

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

函数

自定义函数

Pine语言可以设计自定义函数,一般来说Pine语言的自定义函数有以下规则:

1、所有函数都在脚本的全局范围内定义。不能在另一个函数中声明一个函数。
2、不允许函数在自己的代码中调用自己(递归)。
3、原则上所有PINE语言内置画图函数( barcolor()、 fill()、 hline()、plot()、 plotbar()、 plotcandle())不能在自定义函数内调用。
4、函数可以写成单行、多行。最后一条语句的返回值为当前函数返回值,返回值可以返回元组形式。

5、函数内的变量为局部变量,且函数只能读全局变量无法修改全局变量

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)



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)

内置函数

Pine语言内置函数分类:

1、字符串处理函数str.系列。
2、颜色值处理函数color.系列。
3、参数输入函数input.系列。
4、指标计算函数ta.系列。
5、画图函数plot.系列。
6、数组处理函数array.系列。
7、交易相关函数strategy.系列。
8、数学运算相关函数math.系列。
9、其它函数(时间处理、非plot系列画图函数、request.系列函数、类型处理函数等)。

对于request.security需要注意的是,获取其他商品别的周期数据时,正确写法如下:

其中 lookahead=barmerge.lookahead_on必须加上以免出现未来函数,mal2[1]是因为假设当前时间周期是30分钟为了获取60分钟的数据,当前K线还没完成,所以获取之前已经收盘的值

request.security(syminfo.tickerid,"60",ma12[1],lookahead=barmerge.lookahead_off,gaps=barmerge.gaps_off)

交易函数

交易函数即我们在设计策略中经常用到的函数,与具体执行交易操作息息相关

  • strategy.entry
  • strategy.close
  • strategy.close_all
  • strategy.exit
  • strategy.cancel
  • strategy.cancel_all
  • strategy.order

交易相关概念

交易品种精度

下单时下单量的精度,如qty设置为1.0001,精确到小数点后4位,那么交易品种精度就该设置为4

定价货币精度

下单时,货币的精度精确到第几位,比如设置为2,那么对于ETH换USDT,每跳价格ETH_USDT 就是 0.01,可以使用 syminfo.mintick 获取 ,此外该点数与滑点strategy.exit()中设置的 profit与loss有关

此外当设置收盘价模型时对于profit设置为50点货币精度,可能最终盈利不止50点货币精度,这是因为它不是实时进行的,当收盘时发现满足条件了为100点货币精度它也会进行平仓,那么获利即为100

滑点

下单的点位和最后成交的点位有差距。 硬件方面的因素,比如黄金价格行情波动剧烈时,网络延迟,软件系统以及服务器响应等方面造成成交价格与挂单价格不一致。

在交易时可以设置滑点点数,如果设置成5,同时货币精度位数设置为2,那么实际下单时,发给交易所的价格信号会比代码中设置的高0.05,避免因为滑点造成损失

单项持仓

PINE语言的持仓机制类似于单向持仓。举例子,当持有多头方向的头寸时(多头持仓),如果有卖出操作的订单、计划单等(相对于持仓方向反方向的)订单触发执行,此时会先平掉多头方向的头寸(平掉所有多头持仓),然后再执行触发的(相对于平仓前持仓方向反方向的)订单。

挂单

当strategy函数中指定了如when的参数时,满足了条件才会执行挂单,对于挂单,当指定了如limit的参数时,只有满足条件时才会执行下单操作,在下单执行之前就叫做挂单,此外Pine语言挂单不是真的挂在交易所盘口,而是逻辑挂单,作为计划单

如下代码中long1即为挂单(想要买),同时exit1也是挂单(想要卖)

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语言模板参数上「定价货币精度」参数设置有关

止盈止损

  • profit:利润目标,以货币点数表示。
  • loss:止损目标,以货币点数表示。
  • limit:利润目标,以价格指定。
  • stop:止损目标,以价格指定。

如果是strategy.entry或strategy.order:

对于limit和stop,在做多和做空时是相反的,以做多为例,limit设置为50,stop设置为100,代表价格低于50或者价格高于100时买入,而做空时,limit设置为100,stop设置为50,代表价格高于100或者价格低于50时买入

如果是strategy.exit:

对于limit和stop,在做多和做空时同样是相反的,假设基准价为beginPrice,以做多为例,limit设置为beginPrice+200,stop设置为beginPrice-200,那么在超过limit或低于stop时就会卖出,而且做空时,limit设置为beginPrice-200,stop设置为beginPrice+200,那么在低于limit或超过stop时就会买入

如下即为基础的网格策略

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("long"+i, strategy.long, 0.01, limit=beginPrice-i*200, when=(beginPrice-i*200)<close)
        strategy.exit("coverBuy"+i, "long"+i, qty=0.01, profit=200)
        
        strategy.order("short"+i, strategy.short, 0.01, limit=beginPrice+i*200, when=(beginPrice+i*200)>close)
        strategy.exit("coverSell"+i, "short"+i, qty=0.01, profit=200)

此外以下为追踪止损的例子:

当多头获利达到 5000 点时,追踪止损就会激活,并且每增加 100 点,追踪止损也会增加 100 点。商品价格达到的最高点与追踪止损之间会有 100 点的差异。追踪止损和loss同时存在时,谁更严格,谁就先被触发,平仓

strategy.exit("Exit buy", from_entry="buy", profit = 150000, loss = 10000, trail_points = 5000, trail_offset = 100)

1、trail_price参数:触发放置跟踪止盈止损平仓单这个逻辑行为的位置(以价格指定位置)。
2、trail_offset参数:执行跟踪止损止盈行为之后,放置的平仓单距离最高价(做多时)或者最低价(做空时)的距离。
3、trail_points参数:如同trail_offset参数,只不过是以盈利点数为指定位置。

tradingview中常用到的一些函数

ta.valuewhen(condition,source,occurrence)

返回前面第occurrence次满足condition条件时,从k线返回source

ta.pivotLow(source,leftbars,rightbars) 或ta.pivotHigh(source,leftbars,rightbars)
返回前面第rightbars条k线的source是不是比左边leftbars条k线和右边rightbars条k线都要大或者小, 注意这里不一定有用到未来函数 
leftBars = input(1)
rightBars=input(1)
pl = ta.pivotlow(close, leftBars, rightBars)
plotshape(pl, style=shape.xcross, color= color.blue, offset=-rightBars)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值