方法
介绍
Pine Script™ 方法是与特定内置类型、 用户定义类型或枚举类型的值相关联的专用函数。它们在大多数方面的行为与常规函数相同,但提供更短、更方便的语法。用户可以使用点符号语法对关联类型的变量访问方法,类似于访问 Pine Script™对象的字段。
内置方法
Pine Script™ 包含所有特殊类型的内置方法,包括 数组、 矩阵、 地图、 线、 线填充、 框、 折线、 标签和 表。这些方法为用户提供了一种更简洁的方法来在其脚本中调用这些类型的专用例程。
使用这些特殊类型时,表达式:
和:
是等效的。例如,不要使用:
array.get(id, index)
id
要从指定的数组中获取值index
,我们可以简单地使用:
id.get(index)
达到同样的效果。这种表示法消除了用户引用函数命名空间的需要,因为
get()id
是此上下文中
的一种方法。
下面写了一个实际的例子,演示如何使用内置方法代替函数。
以下脚本计算每条n
柱状图上采样一次的指定数量价格的布林带。它调用
array.push()
和
array.shift()
将sourceInput
值通过 排队sourceArray
,然后
调用array.avg()
和
array.stdev()
计算sampleMean
和sampleDev
。然后,脚本使用这些值计算highBand
和lowBand
,并将其与 一起绘制在图表上sampleMean
:
//@version=5
indicator("Custom Sample BB", overlay = true)
float sourceInput = input.source(close, "Source")
int samplesInput = input.int(20, "Samples")
int n = input.int(10, "Bars")
float multiplier = input.float(2.0, "StdDev")
var array<float> sourceArray = array.new<float>(samplesInput)
var float sampleMean = na
var float sampleDev = na
// Identify if `n` bars have passed.
if bar_index % n == 0
// Update the queue.
array.push(sourceArray, sourceInput)
array.shift(sourceArray)
// Update the mean and standard deviaiton values.
sampleMean := array.avg(sourceArray)
sampleDev := array.stdev(sourceArray) * multiplier
// Calculate bands.
float highBand = sampleMean + sampleDev
float lowBand = sampleMean - sampleDev
plot(sampleMean, "Basis", color.orange)
plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)
让我们重写此代码以利用方法而不是内置函数。在此版本中,我们已将 脚本中的所有内置array.*函数替换为等效方法:
//@version=5
indicator("Custom Sample BB", overlay = true)
float sourceInput = input.source(close, "Source")
int samplesInput = input.int(20, "Samples")
int n = input.int(10, "Bars")
float multiplier = input.float(2.0, "StdDev")
var array<float> sourceArray = array.new<float>(samplesInput)
var float sampleMean = na
var float sampleDev = na
// Identify if `n` bars have passed.
if bar_index % n == 0
// Update the queue.
sourceArray.push(sourceInput)
sourceArray.shift()
// Update the mean and standard deviaiton values.
sampleMean := sourceArray.avg()
sampleDev := sourceArray.stdev() * multiplier
// Calculate band values.
float highBand = sampleMean + sampleDev
float lowBand = sampleMean - sampleDev
plot(sampleMean, "Basis", color.orange)
plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)
注意:
sourceArray.*
我们使用而不是引用 数组命名空间来调用数组方法 。sourceArray
由于调用方法时已经引用了对象,因此我们不会将其作为参数包含进去。
用户定义的方法
Pine Script™ 允许用户定义自定义方法,以用于任何内置或用户定义类型的对象。定义方法本质上与定义函数相同,但有两个主要区别:
- 函数 名之前必须包含方法关键字。
- 签名中第一个参数的类型必须明确声明,因为它表示与该方法关联的对象的类型。
让我们将用户定义的方法应用于我们之前的布林线示例,以封装来自全局范围的操作,这将简化代码并提高可重用性。请参阅示例中的这一部分:
// Identify if `n` bars have passed.
if bar_index % n == 0
// Update the queue.
sourceArray.push(sourceInput)
sourceArray.shift()
// Update the mean and standard deviaiton values.
sampleMean := sourceArray.avg()
sampleDev := sourceArray.stdev() * multiplier
// Calculate band values.
float highBand = sampleMean + sampleDev
float lowBand = sampleMean - sampleDev
我们将首先定义一个简单的方法,通过一次调用将值通过数组进行排队。
此方法
在为真时maintainQueue()
调用
push()
和
shift()方法并返回对象:srcArray
takeSample
// @function Maintains a queue of the size of `srcArray`.
// It appends a `value` to the array and removes its oldest element at position zero.
// @param srcArray (array<float>) The array where the queue is maintained.
// @param value (float) The new value to be added to the queue.
// The queue's oldest value is also removed, so its size is constant.
// @param takeSample (bool) A new `value` is only pushed into the queue if this is true.
// @returns (array<float>) `srcArray` object.
method maintainQueue(array<float> srcArray, float value, bool takeSample = true) =>
if takeSample
srcArray.push(value)
srcArray.shift()
srcArray
注意:
- 与用户定义函数一样,我们使用
@function
编译器注释来记录方法描述。
现在我们可以在示例中用sourceArray.push()
和sourceArray.shift()
替换
:sourceArray.maintainQueue()
// Identify if `n` bars have passed.
if bar_index % n == 0
// Update the queue.
sourceArray.maintainQueue(sourceInput)
// Update the mean and standard deviaiton values.
sampleMean := sourceArray.avg()
sampleDev := sourceArray.stdev() * multiplier
// Calculate band values.
float highBand = sampleMean + sampleDev
float lowBand = sampleMean - sampleDev
从这里开始,我们将通过定义一种处理其范围内的所有布林带计算的方法来进一步简化我们的代码。
此calcBB()
方法在为 true 时调用
avg()
和
stdev()
方法来srcArray
更新mean
和dev
值
calculate
。该方法使用这些值分别返回包含基础、上带和下带值的元组:
// @function Computes Bollinger Band values from an array of data.
// @param srcArray (array<float>) The array where the queue is maintained.
// @param multiplier (float) Standard deviaiton multiplier.
// @param calcuate (bool) The method will only calculate new values when this is true.
// @returns A tuple containing the basis, upper band, and lower band respectively.
method calcBB(array<float> srcArray, float mult, bool calculate = true) =>
var float mean = na
var float dev = na
if calculate
// Compute the mean and standard deviation of the array.
mean := srcArray.avg()
dev := srcArray.stdev() * mult
[mean, mean + dev, mean - dev]
通过这种方法,我们现在可以从全局范围中删除布林带计算并提高代码的可读性:
// Identify if `n` bars have passed.
bool newSample = bar_index % n == 0
// Update the queue and compute new BB values on each new sample.
[sampleMean, highBand, lowBand] = sourceArray.maintainQueue(sourceInput, newSample).calcBB(multiplier, newSample)
注意:
- 我们不再使用
if
全局范围内的块,而是定义了一个newSample
变量,该n
变量每条仅一次为真。maintainQueue()
和calcBB()
方法将此值用作各自的takeSample
和calculate
参数。 - 由于该
maintainQueue()
方法返回它引用的对象,我们可以calcBB()
从同一行代码进行调用,因为这两种方法都适用于array<float>
实例。
现在我们已经应用了用户定义的方法,完整的脚本示例如下所示:
//@version=5
indicator("Custom Sample BB", overlay = true)
float sourceInput = input.source(close, "Source")
int samplesInput = input.int(20, "Samples")
int n = input.int(10, "Bars")
float multiplier = input.float(2.0, "StdDev")
var array<float> sourceArray = array.new<float>(samplesInput)
// @function Maintains a queue of the size of `srcArray`.
// It appends a `value` to the array and removes its oldest element at position zero.
// @param srcArray (array<float>) The array where the queue is maintained.
// @param value (float) The new value to be added to the queue.
// The queue's oldest value is also removed, so its size is constant.
// @param takeSample (bool) A new `value` is only pushed into the queue if this is true.
// @returns (array<float>) `srcArray` object.
method maintainQueue(array<float> srcArray, float value, bool takeSample = true) =>
if takeSample
srcArray.push(value)
srcArray.shift()
srcArray
// @function Computes Bollinger Band values from an array of data.
// @param srcArray (array<float>) The array where the queue is maintained.
// @param multiplier (float) Standard deviaiton multiplier.
// @param calcuate (bool) The method will only calculate new values when this is true.
// @returns A tuple containing the basis, upper band, and lower band respectively.
method calcBB(array<float> srcArray, float mult, bool calculate = true) =>
var float mean = na
var float dev = na
if calculate
// Compute the mean and standard deviation of the array.
mean := srcArray.avg()
dev := srcArray.stdev() * mult
[mean, mean + dev, mean - dev]
// Identify if `n` bars have passed.
bool newSample = bar_index % n == 0
// Update the queue and compute new BB values on each new sample.
[sampleMean, highBand, lowBand] = sourceArray.maintainQueue(sourceInput, newSample).calcBB(multiplier, newSample)
plot(sampleMean, "Basis", color.orange)
plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)
方法重载
用户定义的方法可以覆盖和重载具有相同标识符的现有内置方法和用户定义方法。此功能允许用户在同一方法名称下定义与不同参数签名相关联的多个例程。
举一个简单的例子,假设我们要定义一个方法来识别变量的类型。由于我们必须明确指定与用户定义方法关联的对象的类型,因此我们需要为我们希望它识别的每种类型定义重载。
下面,我们定义了一个getType()
方法,它返回变量类型的字符串表示形式,并对五种原始类型进行了重载:
// @function Identifies an object's type.
// @param this Object to inspect.
// @returns (string) A string representation of the type.
method getType(int this) =>
na(this) ? "int(na)" : "int"
method getType(float this) =>
na(this) ? "float(na)" : "float"
method getType(bool this) =>
na(this) ? "bool(na)" : "bool"
method getType(color this) =>
na(this) ? "color(na)" : "color"
method getType(string this) =>
na(this) ? "string(na)" : "string"
现在我们可以使用这些重载来检查一些变量。此脚本使用
str.format()将对五个不同变量
调用该getType()
方法的结果格式化为单个字符串,然后使用内置的
set_text()results
方法在标签中显示该字符串
:lbl
//@version=5
indicator("Type Inspection")
// @function Identifies an object's type.
// @param this Object to inspect.
// @returns (string) A string representation of the type.
method getType(int this) =>
na(this) ? "int(na)" : "int"
method getType(float this) =>
na(this) ? "float(na)" : "float"
method getType(bool this) =>
na(this) ? "bool(na)" : "bool"
method getType(color this) =>
na(this) ? "color(na)" : "color"
method getType(string this) =>
na(this) ? "string(na)" : "string"
a = 1
b = 1.0
c = true
d = color.white
e = "1"
// Inspect variables and format results.
results = str.format(
"a: {0}\nb: {1}\nc: {2}\nd: {3}\ne: {4}",
a.getType(), b.getType(), c.getType(), d.getType(), e.getType()
)
var label lbl = label.new(0, 0)
lbl.set_x(bar_index)
lbl.set_text(results)
注意:
- 每个变量的底层类型决定了
getType()
编译器将使用哪种重载。 - 当变量需要
na
标示其为空时,该方法将在输出字符串后附加“(na)”。
高级示例
让我们应用所学知识来构建一个脚本,估计数组中元素的累积分布,即数组中小于或等于任何给定值的元素的比例。
我们可以选择多种方式来实现这一目标。在这个例子中,我们将首先定义一个方法来替换数组中的元素,这将帮助我们计算某个值范围内元素的出现次数。
下面写的是实例的内置
fill()
方法的重载array<float>
。此重载将和
之间srcArray
范围内的 元素替换为,并将范围之外的所有元素替换为:lowerBound
upperBound
innerValue
outerValue
// @function Replaces elements in a `srcArray` between `lowerBound` and `upperBound` with an `innerValue`,
// and replaces elements outside the range with an `outerValue`.
// @param srcArray (array<float>) Array to modify.
// @param innerValue (float) Value to replace elements within the range with.
// @param outerValue (float) Value to replace elements outside the range with.
// @param lowerBound (float) Lowest value to replace with `innerValue`.
// @param upperBound (float) Highest value to replace with `innerValue`.
// @returns (array<float>) `srcArray` object.
method fill(array<float> srcArray, float innerValue, float outerValue, float lowerBound, float upperBound) =>
for [i, element] in srcArray
if (element >= lowerBound or na(lowerBound)) and (element <= upperBound or na(upperBound))
srcArray.set(i, innerValue)
else
srcArray.set(i, outerValue)
srcArray
使用此方法,我们可以按值范围过滤数组以生成出现次数数组。例如,表达式:
srcArray.copy().fill(1.0, 0.0, min, val)
复制对象,将和
srcArray
之间的所有元素替换为1.0,然后将以上所有元素替换为 0.0。从这里,很容易估计在 处的累积分布函数的输出,因为它只是结果数组的平均值:min
val
val
val
srcArray.copy().fill(1.0, 0.0, min, val).avg()
注意:
- 仅当用户在调用中提供、、和参数时,
编译器才会使用此
fill()
重载而不是内置重载。innerValue
outerValue
lowerBound
upperBound
- 如果 或
lowerBound
为upperBound
,na
则在过滤填充范围时忽略其值。 - 我们可以在同一行代码上依次
调用
copy()
、fill()
和,因为前两种方法返回一个实例。avg()
array<float>
现在我们可以使用它来定义一个方法来计算我们的经验分布值。以下方法从 a 的累积分布函数中eCDF()
估计出一些均匀分布的上升值,并将结果推送到 a 中:steps
srcArray
cdfArray
// @function Estimates the empirical CDF of a `srcArray`.
// @param srcArray (array<float>) Array to calculate on.
// @param steps (int) Number of steps in the estimation.
// @returns (array<float>) Array of estimated CDF ratios.
method eCDF(array<float> srcArray, int steps) =>
float min = srcArray.min()
float rng = srcArray.range() / steps
array<float> cdfArray = array.new<float>()
// Add averages of `srcArray` filtered by value region to the `cdfArray`.
float val = min
for i = 1 to steps
val += rng
cdfArray.push(srcArray.copy().fill(1.0, 0.0, min, val).avg())
cdfArray
最后,为了确保我们的eCDF()
方法能够正确用于包含小值和大值的数组,我们将定义一个方法来规范化我们的数组。
此featureScale()
方法使用数组
min()
和
range()
方法生成 的重新缩放副本srcArray
。在调用该eCDF()
方法之前,我们将使用它来规范化数组:
// @function Rescales the elements within a `srcArray` to the interval [0, 1].
// @param srcArray (array<float>) Array to normalize.
// @returns (array<float>) Normalized copy of the `srcArray`.
method featureScale(array<float> srcArray) =>
float min = srcArray.min()
float rng = srcArray.range()
array<float> scaledArray = array.new<float>()
// Push normalized `element` values into the `scaledArray`.
for element in srcArray
scaledArray.push((element - min) / rng)
scaledArray
注意:
- 此方法不包括对除以零的情况的特殊处理。如果
rng
为 0,则数组元素的值将为na
。
下面的完整示例使用我们之前的方法将sourceArray
大小为 的数组length
与
值进行排队,使用该方法对数组的元素进行规范化,然后调用该方法获取分布上均匀间隔步骤的估计值数组。然后,脚本调用用户定义的
函数在图表右侧的标签中显示估计值和价格:sourceInput
maintainQueue()
featureScale()
eCDF()
n
makeLabel()
//@version=5
indicator("Empirical Distribution", overlay = true)
float sourceInput = input.source(close, "Source")
int length = input.int(20, "Length")
int n = input.int(20, "Steps")
// @function Maintains a queue of the size of `srcArray`.
// It appends a `value` to the array and removes its oldest element at position zero.
// @param srcArray (array<float>) The array where the queue is maintained.
// @param value (float) The new value to be added to the queue.
// The queue's oldest value is also removed, so its size is constant.
// @param takeSample (bool) A new `value` is only pushed into the queue if this is true.
// @returns (array<float>) `srcArray` object.
method maintainQueue(array<float> srcArray, float value, bool takeSample = true) =>
if takeSample
srcArray.push(value)
srcArray.shift()
srcArray
// @function Replaces elements in a `srcArray` between `lowerBound` and `upperBound` with an `innerValue`,
// and replaces elements outside the range with an `outerValue`.
// @param srcArray (array<float>) Array to modify.
// @param innerValue (float) Value to replace elements within the range with.
// @param outerValue (float) Value to replace elements outside the range with.
// @param lowerBound (float) Lowest value to replace with `innerValue`.
// @param upperBound (float) Highest value to replace with `innerValue`.
// @returns (array<float>) `srcArray` object.
method fill(array<float> srcArray, float innerValue, float outerValue, float lowerBound, float upperBound) =>
for [i, element] in srcArray
if (element >= lowerBound or na(lowerBound)) and (element <= upperBound or na(upperBound))
srcArray.set(i, innerValue)
else
srcArray.set(i, outerValue)
srcArray
// @function Estimates the empirical CDF of a `srcArray`.
// @param srcArray (array<float>) Array to calculate on.
// @param steps (int) Number of steps in the estimation.
// @returns (array<float>) Array of estimated CDF ratios.
method eCDF(array<float> srcArray, int steps) =>
float min = srcArray.min()
float rng = srcArray.range() / steps
array<float> cdfArray = array.new<float>()
// Add averages of `srcArray` filtered by value region to the `cdfArray`.
float val = min
for i = 1 to steps
val += rng
cdfArray.push(srcArray.copy().fill(1.0, 0.0, min, val).avg())
cdfArray
// @function Rescales the elements within a `srcArray` to the interval [0, 1].
// @param srcArray (array<float>) Array to normalize.
// @returns (array<float>) Normalized copy of the `srcArray`.
method featureScale(array<float> srcArray) =>
float min = srcArray.min()
float rng = srcArray.range()
array<float> scaledArray = array.new<float>()
// Push normalized `element` values into the `scaledArray`.
for element in srcArray
scaledArray.push((element - min) / rng)
scaledArray
// @function Draws a label containing eCDF estimates in the format "{price}: {percent}%"
// @param srcArray (array<float>) Array of source values.
// @param cdfArray (array<float>) Array of CDF estimates.
// @returns (void)
makeLabel(array<float> srcArray, array<float> cdfArray) =>
float max = srcArray.max()
float rng = srcArray.range() / cdfArray.size()
string results = ""
var label lbl = label.new(0, 0, "", style = label.style_label_left, text_font_family = font.family_monospace)
// Add percentage strings to `results` starting from the `max`.
cdfArray.reverse()
for [i, element] in cdfArray
results += str.format("{0}: {1}%\n", max - i * rng, element * 100)
// Update `lbl` attributes.
lbl.set_xy(bar_index + 1, srcArray.avg())
lbl.set_text(results)
var array<float> sourceArray = array.new<float>(length)
// Add background color for the last `length` bars.
bgcolor(bar_index > last_bar_index - length ? color.new(color.orange, 80) : na)
// Queue `sourceArray`, feature scale, then estimate the distribution over `n` steps.
array<float> distArray = sourceArray.maintainQueue(sourceInput).featureScale().eCDF(n)
// Draw label.
makeLabel(sourceArray, distArray)