颜色过渡:如何实现渐变色效果
在TradingView中,我们可以从内置的基础颜色变量中选择颜色,也可以使用十六进制颜色值。但我们该如何应用颜色,才能获得类似渐变的着色效果呢?
颜色的深浅:在TradingView中应用渐变色效果
在TradingView中,我们可以使用标准的TradingView颜色和十六进制颜色值。在我们的Pine脚本中使用过这些颜色后,我们还可以手动更改脚本的颜色。但渐变色效果又该如何实现呢?
渐变色的理念在于使用不同的色调来凸显不同的数值,例如,当数值高时使用醒目的颜色,而数值低时则伴以较浅的色调。首先要说的是一个坏消息:目前TradingView中并没有一个内置函数可以根据数值的高低来直接生成从深到浅的渐变色。
然而,我们可以创建自己的函数来返回不同的颜色深浅。我们可以使用的一种颜色是单色(monochromatic colours),即由单一颜色衍生出的不同色调、色相和色度。例如,我们可以通过ColorHexa网站为绿色和红色生成一些这样的颜色(包括它们的十六进制值):
这里的中间方块是主色调的绿色(十六进制 #00ff00)和红色(#ff0000),左侧的颜色色调更深,而右侧方块中的颜色则逐渐变浅。现在,让我们看看如何在TradingView中使用这些不同的色度。
在TradingView中使用自定义函数实现渐变色
在下方的示例指标中,我们创建了一个绘制每根价格K线所包含的跳动点(ticks)数量的直方图。每当K线收高时,我们让直方图的柱子显示为绿色;否则,则显示为红色系的某个色度。我们使用了两个自定义函数来返回上图中显示的颜色之一。我们稍后也会看到该指标在图表上的样子,但首先来看代码:
//@version=5
indicator(title="Gradient-like colours", overlay=false, precision=0)
// 输入选项
useWholePips = input.bool(false, title="Base ticks on whole pips (fx)?")
// 自定义函数
GreenShade(value) =>
value >= 75 ? #00b300 : // 深绿色
value > 50 ? #00cc00 :
value > 30 ? #00e600 :
value > 20 ? #00ff00 : // 绿色
value > 15 ? #1aff1a :
value > 10 ? #33ff33 :
#4dff4d // 浅绿色
RedShade(value) =>
value >= 75 ? #b30000 : // 深红色
value > 50 ? #cc0000 :
value > 30 ? #e60000 :
value > 20 ? #ff0000 : // 红色
value > 15 ? #ff1a1a :
value > 10 ? #ff3333 :
#ff4d4d // 浅红色
// 计算K线内的跳动点数
tickCorrection = useWholePips ? 0.1 : 1
ticksInBar = ((high - low) / syminfo.mintick) * tickCorrection
// 确定绘图颜色
plotColour = close > open ? GreenShade(ticksInBar) : RedShade(ticksInBar)
// 执行绘图
plot(ticksInBar, style=plot.style_histogram, linewidth=4, color=plotColour)
hline(0, linestyle=hline.style_dotted)
我们以 indicator() 函数开始,这是每个指标都必需的。我们在这里设置了它的三个参数:title(指定指标名称)、overlay(当设为 false 时,在子图中显示指标)以及 precision(指定指标值小数点后显示的位数)。
然后,我们向脚本添加一个输入选项:
// 输入选项
useWholePips = input.bool(false, title="Base ticks on whole pips (fx)?")
这个用户可配置的输入项是通过 input.bool() 函数创建的,它会在脚本的设置中生成一个布尔型(真/假)复选框,并返回该输入的当前值。title 参数指定了显示在输入选项前的名称,这里我们设为“Base ticks on whole pips (fx)?”。
我们稍后将计算价格K线内的跳动点数,通过这个输入选项,我们可以区分在外汇交易中是应该使用标准点(pips)还是分数点(fractional pips)作为跳动点的衡量单位。目前,我们默认禁用此复选框(false),并将其值赋给 useWholePips 变量供后续使用。
接下来,我们创建第一个自定义函数:
GreenShade(value) =>
value >= 75 ? #00b300 : // 深绿色
value > 50 ? #00cc00 :
value > 30 ? #00e600 :
value > 20 ? #00ff00 : // 绿色
value > 15 ? #1aff1a :
value > 10 ? #33ff33 :
#4dff4d // 浅绿色
我们通过键入函数名称(这里是GreenShade),然后在括号内放置函数使用的任何参数(这里我们有一个参数:value)来创建多行函数。接着,我们键入函数声明运算符 =>,然后是函数的代码,代码需要用四个空格或一个制表符进行缩进。函数的返回值是最后一个表达式或变量声明。在我们的例子中,就是那个将 value 参数与几个硬编码数字进行比较的多行表达式。
我们的 GreenShade() 函数会返回上图中显示的一种绿色。在函数内部,我们使用了几个条件(三元)运算符(?:) 来评估函数的 value 参数。该运算符处理一个布尔(真/假)条件以及第二和第三个值。每当该布尔条件为 true 时,运算符返回其第二个值;当条件为 false 时,则返回其第三个值。
在函数中,我们组合了一系列条件运算符,将函数的 value 参数与字面量数值75、50、30、20、15和10进行比较。这些数字是我们为每根K线任意选择的、代表从高到低不同波动幅度的跳动点数量。
逻辑流程如下:首先,第一个条件运算符检查 value 是否大于或等于75。如果是,则返回 #00b300,这是上图中绿色的最深色度。如果 value 不大于或等于75,则继续判断下一个条件运算符。
那一个检查 value 是否大于50。如果为 true,该运算符返回 #00cc00(我们第二深的绿色色度)。如果此条件为 false,则执行下一个条件运算符。
第三个判断 value 是否高于30,如果是,则返回 #00e600 这个十六进制颜色。如果此条件也为 false,代码执行将继续到第四个条件运算符。该运算符的条件是判断 value 是否大于20,如果是,则返回 #00ff00,这是上图中绿色的中间色度。
如果函数的参数值也不高于20,下一个条件运算符会执行。它的条件是 value > 15,当为 true 时,返回 #1aff1a 这个十六进制颜色。如果这个条件运算符的条件也为 false,那么TradingView Pine会处理最后一个条件运算符。
那一个判断 value 是否大于10。如果是,该运算符返回 #33ff33。然而,如果 value 低于10,那么最后一个条件运算符会返回 #4dff4d,这是上图中绿色最浅色度的十六进制颜色值。
这些条件运算符之一所返回的值,也就是 GreenShade() 函数最终返回的值。
第二个自定义函数 RedShade() 用于返回一个红色系的颜色,其逻辑与第一个非常相似:
RedShade(value) =>
value >= 75 ? #b30000 : // 深红色
value > 50 ? #cc0000 :
value > 30 ? #e60000 :
value > 20 ? #ff0000 : // 红色
value > 15 ? #ff1a1a :
value > 10 ? #ff3333 :
#ff4d4d // 浅红色
RedShade() 函数也需要一个参数(value),并使用一系列条件(三元)运算符(?:)将该参数与几个字面量整数(75, 50, 30, 20, 15, 10)进行比较。这个函数与 GreenShade() 的唯一区别在于,它返回的是前一张图片中的红色系颜色,从最深的红色(#b30000)到常规的红色(#ff0000),最后到浅红色的色调(#ff4d4d)。
无论这里的哪个条件运算符的条件评估为 true 并返回了哪个十六进制颜色值,那个返回值也就是 RedShade() 函数的最终结果。
创建完自定义函数后,我们来计算当前K线内包含了多少个跳动点:
tickCorrection = useWholePips ? 0.1 : 1
ticksInBar = ((high - low) / syminfo.mintick) * tickCorrection
tickCorrection 变量在条件(三元)运算符(?:)的帮助下,根据 useWholePips 的值被赋予0.1或1。当其复选框被勾选时,该输入变量的值为 true,未勾选时则为 false。
我们在下一个语句中使用 tickCorrection 变量来计算 ticksInBar 的值。该变量的值等于K线的波幅(high - low)除以 syminfo.mintick,这是一个返回交易品种最小跳动点大小的内置变量。例如,EuroStoxx 50指数差价合约的最小价格变动是1个点,而S&P 500指数以及许多股票的最小价格波动是0.01。
然而,在外汇市场中,与跳动点等效的概念是点(pip),它通常基于四位小数(如1.4586)的报价,对于日元交叉盘则是两位小数。但现在大多数外汇经纪商都提供5位或3位小数的报价,这种更小的价格变动单位被称为分数点(pipettes)。
我们添加“Base ticks on whole pips?”这个输入选项的目的,就是让用户可以选择是以标准点(而不是分数点)来显示K线的波幅。当该输入被启用时,ticksCorrection 变量被设为0.1。K线的波幅随后会乘以该值并存储在 ticksInBar 变量中。这样,外汇交易品种的最小价格变动就从分数点转换为了常规点。得益于这个输入选项,这是我们示例脚本的一个可选功能。这意味着,当英镑/美元的最高价为1.45860,最低价为1.44750时,我们计算出的波幅就不是1110个跳动点,而是更为人所熟知的111个标准点。
在计算出K线的波幅后,我们来确定直方图的颜色:
// 确定绘图颜色
plotColour = close > open ?
GreenShade(ticksInBar) :
RedShade(ticksInBar)
plotColour 变量的值是使用条件(三元)运算符(?:)来赋予的。其评估的条件是K线是否收高(close > open)。如果是,我们就调用 GreenShade() 函数,并将包含K线波幅的 ticksInBar 变量作为参数传入。该函数返回的绿色色度随后被存储在 plotColour 变量中供后续使用。
如果K线没有收高(即条件运算符的else部分),那么 RedShade() 函数就会被执行,同样以K线的波幅(ticksInBar)作为其参数。该函数调用会返回一个红色系的色度,这个颜色随即被存储在 plotColour 中。
在确定了绘图颜色后,就该绘制直方图本身了:
// 执行绘图
plot(ticksInBar, style=plot.style_histogram, linewidth=4,
color=plotColour)
hline(0, linestyle=hline.style_dotted)
plot() 函数会在图表上显示其第一个参数的数据。这里,我们将其设为 ticksInBar,即我们那个包含K线波幅(以跳动点数计)的变量。通过将 style 参数设为 plot.style_histogram,这些值会被绘制成直方图。linewidth 参数用于设置绘图的大小,这里我们设为4以创建尽可能粗的直方图柱子。这些柱子的颜色则被设为我们存储在 plotColour 变量中的不同色度的绿色和红色。
然后,我们使用 hline() 函数,它可以在一个固定的价格水平上创建一条水平线。我们将这条线放在0的位置,并将其样式设为 hline.style_dotted(虚线)。这提供了一个不错的视觉效果,因为直方图的柱子现在看起来像是被放置在零轴线上。
在TradingView直方图上应用渐变色效果
当我们将上述示例指标添加到EuroStoxx 50指数差价合约的图表上时,它看起来是这样的:
虽然颜色的差异比较细微,但最高的两根直方图柱子有着更深的色调,而数值在25到50之间的柱子则使用了较不醒目的颜色。最小的直方图柱子则以最浅的颜色显示。
如果我们从30分钟时间周期切换到2小时图,这个渐变色效果的直方图看起来是这样的:
当我们将该示例指标添加到一张30分钟的英镑/美元图表上时,它看起来是这样的:
现在直方图柱子的颜色并没有太大区别,几乎所有的柱子都是同一种绿色或红色的色度。这是因为脚本默认将分数点(fractional pips)视为跳动点,而几乎所有K线的波动都超过了75——这是我们的函数应用最深色度的分界值。
要改变这种行为,我们需要启用脚本的输入选项,这将使跳动点基于标准点(whole pips)进行计算:
启用该输入选项后,脚本会将10个分数点视为一个跳动点。现在,英镑/美元直方图的渐变色效果就更加明显了:
总结
通过使用十六进制颜色值,我们可以在TradingView中实现类似渐变色的效果。为此,我们需要将颜色按某种方式排序(例如从浅到深),然后根据一个数值或数值范围来返回其中一种颜色。实现这一点的一种方法是使用自定义函数,它会将其参数与其他值进行评估,然后根据比较结果返回一个特定的颜色。这种评估可以通过 if/else 语句或条件(三元)运算符来完成。该运算符会评估一个条件,当条件为 true 时,返回其第二个值;当条件为 false 时,则返回其第三个值。
如何使用随机颜色
在TradingView中,我们既可以使用基础颜色变量,也可以使用十六进制颜色值。但我们该如何应用颜色,才能获得类似随机的着色效果呢?
基于类随机数在TradingView中着色
通常,当我们在TradingView中为绘图或背景着色时,我们会选择一个基础颜色或十六进制颜色。另一种方法是让Pine Script从一个颜色列表中随机选择,以获得有趣的视觉效果。然而,TradingView目前并没有一个能生成随机数的内置函数。
幸运的是,我们可以生成自己的伪随机数。伪随机数(pseudo-random number)是一个可预测的数(例如,因为它是由一个数学公式生成的),但其表现却显得不可预测。不过,对于实际应用来说,伪随机性已经足够了。
实现这种伪随机数的一种方法是使用线性同余生成器(Linear Congruential Generator, LCG),其经典形式如下:
n = (a * n + c) mod m
为方便起见,我们可以去掉与c的加法;公式就简化为 n = a * n mod m。这个公式的作用是取一个初始值(n,也称为种子),并对其进行一些数学运算。其结果就是生成的随机数,这个随机数又会成为生成下一个随机数的种子。这也表明这些数字并非真正的随机,因为它们部分依赖于前一个值。
使用这个公式的诀窍在于为乘数 a 和模数 m 选择合适的数值;如果数值不当,生成的结果序列将不再像随机数,反而会呈现出重复的模式。幸运的是,研究人员已经对此进行了深入研究,并发现16708(对应a)和2147483647(对应m)是比较理想的数值。现在,让我们看看如何在TradingView中使用这个公式。
快速示例:在TradingView中生成类随机数
我们可以在TradingView中实现经典的LCG,如下所示:
//@version=5
indicator(title="Random numbers example", overlay=false)
x = 1
x := 16708 * nz(x[1], 1) % 2147483647
plot(x % 17, style=plot.style_histogram, linewidth=3)
这个示例以 indicator() 函数开始,用于定义指标名称并设置 overlay 参数。当该参数值为 false 时,脚本会显示在一个单独的图表面板中。
然后,我们声明变量 x 并为其赋一个默认值 1。这个占位符确保我们创建了变量以供后续使用。我们使用 x 作为变量名而不是 n,因为后者已经被TradingView自身占用。
接下来,我们计算随机值。在公式中,我们通过历史运算符 x[1] 来引用前一根K线上的随机值。
由于当图表上没有前一根K线时,引用前值可能会返回一个NaN(非数字)值,因此我们使用 nz() 函数来将那些无效值替换为1。所以,nz(x[1], 1) 会返回 x 在前一根K线上的值,除非该值为NaN,此时它会返回1。通过这种方式,我们避免了因单个NaN值在每次计算中传播而扰乱我们的计算结果。
然后,我们调用 plot() 函数在图表上显示这些值。我们没有直接将包含随机值的 x 变量作为函数的第一个参数,而是设为 x % 17。我们在这里使用模数运算符(%),因为TradingView有17种基础颜色,而x模17会返回一个0到16之间的值。这样,我们就可以通过将这17个数字分别匹配到17个颜色变量之一,来实现随机着色。
我们将 plot() 函数的 style 参数设为 plot.style_histogram,将数据以直方图柱的形式显示。并将 linewidth 参数设为3,使这些柱子以接近最粗的尺寸显示。
注意
像我们在这里与 plot() 一起使用的条件性颜色,并不能与所有的TradingView函数一起使用。fill() 和 hline() 函数不接受条件性颜色,而 barcolor()、bgcolor()、plot()、plotarrow()、plotbar()、plotcandle()、plotchar() 和 plotshape() 函数则可以接受一个每根K线都可能改变的颜色值。
当我们将上述示例指标添加到图表时,它看起来是这样的:
正如我们在这里看到的,直方图中绘制的值看起来相当随机。现在让我们看看如何使用这种方法来做一些随机着色。
在TradingView中随机应用颜色
下面的示例对之前的例子进行了扩展:每当出现一个新的20周期高点或20周期低点时,我们就会用17种基础颜色之一来为图表背景着色。具体使用哪种颜色,则取决于伪随机数的值。在逐一解析示例代码之后,我们再来看看该指标在图表上的样子。
//@version=5
indicator(title="Random colouring", overlay=true)
// 输入选项
highLen = input.int(20, title="Highest high period")
lowLen = input.int(20, title="Lowest low period")
// 确定随机值
x = 1
x := 16708 * nz(x[1], 1) % 2147483647
// 一个基于`x`中的随机值来返回一个随机颜色的函数
RandomColour() =>
y = x % 17
y == 0 ? color.aqua :
y == 1 ? color.black :
y == 2 ? color.blue :
y == 3 ? color.fuchsia :
y == 4 ? color.gray :
y == 5 ? color.green :
y == 6 ? color.lime :
y == 7 ? color.maroon :
y == 8 ? color.navy :
y == 9 ? color.olive :
y == 10 ? color.orange :
y == 11 ? color.purple :
y == 12 ? color.red :
y == 13 ? color.silver :
y == 14 ? color.teal :
y == 15 ? color.white :
color.yellow
// 确定背景颜色
recentHigh = ta.highest(high, highLen)
recentLow = ta.lowest(low, lowLen)
backgroundColour = if high == recentHigh
color.new(RandomColour(), 80)
else if low == recentLow
color.new(RandomColour(), 80)
// 为K线背景着色
bgcolor(backgroundColour)
我们以 indicator() 函数开始,这是每个指标都必需的。通过其 title 参数我们设置脚本的名称,并将 overlay 设为 true,使脚本能叠加显示在主图表的交易品种上。
然后,我们创建两个输入选项:
// 输入选项
highLen = input.int(20, title="Highest high period")
lowLen = input.int(20, title="Lowest low period")
手动输入选项是通过 input() 函数添加到脚本中的,该函数同时也会返回输入的当前值。我们在这里创建的两个输入都是数值整数输入,这是通过 input.int() 函数实现的。
这个函数不仅在脚本的设置窗口中创建一个自定义输入项,还会返回该输入的当前值。这里我们将该值存储在一个变量中,以便稍后在脚本中访问。
第一个输入名为“Highest high period”,其默认值为20,其当前值存储在 highLen 变量中。该变量稍后将用于获取过去20根K线的最高价。
另一个输入名为“Lowest low period”,初始值也为20。我们将该输入的值赋给 lowLen 变量,稍后在计算最低价的最低点时将使用它。
接着,我们计算随机值(与前一个示例一样)并创建一个多行函数:
// 确定随机值
x = 1
x := 16708 * nz(x[1], 1) % 2147483647
// 一个基于`x`中的随机值来返回一个随机颜色的函数
RandomColour() =>
y = x % 17
y == 0 ? color.aqua :
y == 1 ? color.black :
y == 2 ? color.blue :
y == 3 ? color.fuchsia :
y == 4 ? color.gray :
y == 5 ? color.green :
y == 6 ? color.lime :
y == 7 ? color.maroon :
y == 8 ? color.navy :
y == 9 ? color.olive :
y == 10 ? color.orange :
y == 11 ? color.purple :
y == 12 ? color.red :
y == 13 ? color.silver :
y == 14 ? color.teal :
y == 15 ? color.white :
color.yellow
我们通过声明函数名称(这里是RandomColour)来创建一个多行函数,并可以在括号中列出函数的参数(我们的函数不接受参数)。然后,我们在函数名称后放置函数声明运算符(=>),其后是用四个空格或一个制表符缩进的函数代码。这些语句中的最后一个就是函数返回的值。
我们的 RandomColour() 函数的代码首先将 y 变量设为我们的随机值 x 除以17的余数,我们用模数运算符(%)计算这个值。这个计算确保了 y 的值总是在0到16之间。利用这17个不同的值,我们就可以为 y 的每个值返回17种TradingView基础颜色之一。
实现这一点的代码位于函数的第二部分,它由一系列嵌套的条件运算符(?:)组成,每个都将 y 与16个数字之一进行比较。该运算符处理三个值:第一个是一个布尔(真/假)条件,当条件为 true 时,运算符返回第二个值;当条件为 false 时,则返回其第三个值。
第一个条件运算符判断 y 是否等于0。如果是,则返回 color.aqua,并且由于这一系列条件运算符是函数的最后一个语句,这个颜色也就是 RandomColour() 函数的返回值。但如果 y 不等于0,则会继续处理下一个条件运算符。
那一个检查 y 是否等于1。如果是,则返回 color.black。否则,执行下一个条件运算符。这个过程会一直持续,直到代码执行到达一个条件为 true 的条件运算符,或者一直到评估 y == 15 的最后一个条件运算符。当该条件为 true 时,这个最后的条件运算符返回 color.white。否则,当这个最后的条件也为 false 时(这也意味着所有之前的条件都为 false),那么最后的条件运算符会返回 color.yellow。
在创建完 RandomColour() 函数后,我们用它来确定背景颜色:
// 确定背景颜色
recentHigh = ta.highest(high, highLen)
recentLow = ta.lowest(low, lowLen)
backgroundColour = if high == recentHigh
color.new(RandomColour(), 80)
else if low == recentLow
color.new(RandomColour(), 80)
这里我们首先计算近期的最高价最高点和最低价最低点,这样我们稍后就可以在 if 条件中使用这些值。
为了获取最高价的最高点,我们调用Pine Script的 ta.highest() 函数并为其提供两个值:high(用于计算价格K线的最高价)和 highLen 输入变量(用于确定周期)。我们将近期最高价存储在 recentHigh 变量中。
我们使用 ta.lowest() 函数来检索近期最低价的最低点。该函数作用于最低价(low),周期为 lowLen 根K线。我们将近期最低价存储在 recentLow 变量中。
然后,我们使用一个级联 if 语句来确定背景颜色。第一个 if 条件检查当前K线的最高价(high)是否等于(==)近期最高价。如果是,我们就调用 color.new() 函数,为 RandomColour() 函数返回的随机颜色应用80%的透明度。这个80%的透明度稍后会创建一个柔和的透明背景。Pine Script随后将这个颜色存储在 backgroundColour 变量中供后续使用。
如果当前K线没有创下近期新高,第二个 if 条件会判断K线的最低价(low)是否等于(==)近期最低价。如果是,color.new() 会为 RandomColour() 返回的随机颜色应用80%的透明度,然后我们将该颜色存入 backgroundColour 变量。
如果第二个 if 条件也不满足,那么Pine Script会隐式地返回 na 值。当颜色值为 na 时,其效果是关闭着色。因此,图表的背景色将不会改变。
所以,我们的 backgroundColour 变量要么在出现20周期最高价或最低价时持有一个随机颜色,要么不包含任何颜色,此时图表背景不会改变。
在创建了 backgroundColour 变量之后,我们进行实际的背景着色:
// 为K线背景着色
bgcolor(backgroundColour)
bgcolor() 函数会从上到下为图表的背景着色。这里我们将其颜色设为我们之前创建的 backgroundColour 变量。这意味着背景要么被设为一个随机颜色,要么被设为一个关闭状态的颜色(在这种情况下我们不会看到任何效果)。
在TradingView K线上随机着色背景
当我们将该示例指标添加到EuroStoxx 50指数差价合约的图表上时,它看起来是这样的:
该示例指标创建了以下输入选项:
当我们将“Highest High Period”输入设为10,“Lowest Low Period”输入设为25时,前一个图表将变为:
这里我们可以看到,虽然有几根额外的K线获得了背景色,但大多数K线的背景色保持不变。这是因为我们的伪随机数隐式地与图表上的K线数量相关联,这个种子不会在每次脚本重新计算时都改变。一个更好的生成伪随机数的种子是当前的日期和时间(因为该值总是在变化并且每次都是唯一的)。然而,我们目前无法在TradingView Pine中获取当前时间。
总结
TradingView目前没有一个能返回伪随机数的函数。不过,我们可以用像经典的线性同余生成器这样的公式来计算我们自己的类随机数。实现这一点的一种方法是将该随机值传递给一个自定义函数,该函数会将该值与几个硬编码的值进行比较,并返回伴随的预定义颜色作为函数的结果。这种比较可以通过条件运算符(?:) 或 if/else语句来完成。












