设计策略时经常需要设计策略图表显示,在使用JavaScript语言、Python语言编写策略的时候。对编程不熟悉或者对FMZ平台使用的图表库不熟悉的用户经常苦恼于自定义图表上画图的代码设计。那么如何可以只用编写少量的代码,又可以画出丰富内容的策略图表呢?
从拥有丰富画图功能著称的Pine语言中可以看到这种既简单又功能强大的画图方式。如果可以把Pine语言的画图接口接入到JavaScript、Python语言的策略中使用,那么就极大得方便了开发者设计策略的画图功能。于是FMZ平台基于这种需求,升级了自定义画图功能,扩展了使用KLineChart
函数进行自定义图表画图的方式。可以参看API文档:https://www.fmz.com/api#klinechart
我们使用JavaScript语言先来写个简单的例子过渡一下。
/*backtest
start: 2022-03-21 09:00:00
end: 2022-06-21 15:00:00
period: 30m
basePeriod: 15m
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/
function main() {
var c = KLineChart()
while (true) {
var bars = _C(exchange.GetRecords)
for (var i = 0 ; i < bars.length ; i++) {
var bar = bars[i]
c.begin(bar)
c.plot(bar.Volume, "volume")
c.close()
}
Sleep(1000)
}
}
这个例子非常简单,只是在策略的自定义画图区域画出K线图,并且在图表的副图位置对应每根K线BAR画出成交量曲线。
代码中我们首先使用var c = KLineChart()
创建图表对象,之后用来调用其方法进行画图。然后循环中我们获取K线数据(数组结构),对K线数组遍历操作。可以如例子中使用简单的for循环遍历,也可以用其它方法遍历。
画图操作要从c.begin(bar)
函数开始,c.close()
函数结束。begin
、close
函数都是图表对象c的方法。接下来使用最常用的画图函数plot
画出每根BAR上的成交量曲线。
假如我们想设计一个附带布林指标的图表。哦对!还要附带每根BAR的成交量线图,可以这样设计:
/*backtest
start: 2022-03-21 09:00:00
end: 2022-06-21 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/
function main() {
var c = KLineChart()
// 策略主循环
while(true) {
// 轮询间隔
Sleep(500)
// 获取K线数据
let bars = exchange.GetRecords()
if (!bars || bars.length <= 20) {
continue
}
// 计算布林指标
var boll = TA.BOLL(bars)
bars.forEach(function(bar, index) {
c.begin(bar)
// 画图操作
c.plot(boll[0][index], "Boll_Up", {overlay: true}) // 画在图表主图
c.plot(boll[1][index], "Boll_Mid", {overlay: true}) // 画在图表主图
c.plot(boll[2][index], "Boll_Down", {overlay: true}) // 画在图表主图
c.plot(bar.Volume, "volume") // 画在图表副图
c.close()
})
// 策略交易逻辑
// ...
// ..
}
}
可以看到在代码中,我们在FMZ平台上的回测配置信息为:
/*backtest
start: 2022-03-21 09:00:00
end: 2022-06-21 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/
这样的配置就是使用Binance现货交易所对象回测测试,回测效果如图:
可见,我们使用Pine的画图方式,在JavaScript语言中大大简便了画图代码。
获取K线数据,计算指标这些操作在平台策略广场的策略范例中和API文档中很常见,也非常简单。
// 获取K线数据
let bars = exchange.GetRecords()
if (!bars || bars.length <= 20) {
// 如果获取K线失败,即!bar为真则执行continue,忽略之后的代码,重新执行循环
// 如果bars.length小于等于20,即K线BAR(柱)数量小于20,无法计算指标,也执行continue
continue
}
// 计算布林指标
var boll = TA.BOLL(bars)
布林指标计算函数TA.BOLL,如果不指定布林指标参数,就使用默认参数BOLL(20, 2)
。布林指标有三条线,所以TA.BOLL函数返回的数据是一个二维数组。boll[0]、boll[1]、boll[2]就是boll数组的三个元素,它们各自代表一条线,是一个数组。
然后我们就看看如何把K线数据的volume即成交量,和计算出来的布林指标数据画在图表上。
我们对K线数据逐个Bar进行画图,所以要遍历整个K线数组,即遍历代码中的bars数组。这里使用forEach
方法遍历,也可以使用for循环的写法遍历。
bars.forEach(function(bar, index) {
c.begin(bar)
// 画图操作
c.plot(boll[0][index], "Boll_Up", {overlay: true}) // {overlay: true}参数控制,画在图表主图
c.plot(boll[1][index], "Boll_Mid", {overlay: true}) // 画在图表主图
c.plot(boll[2][index], "Boll_Down", {overlay: true}) // 画在图表主图
c.plot(bar.Volume, "volume") // 画在图表副图
c.close()
})
需要注意,每当开始一根Bar上的图表绘制操作时,要首先进行c.begin(bar)
函数调用,begin
函数是我们的图表对象c的一个方法。绘制操作结束时,需要调用c.close()
。在begin
函数和close
函数之间就是我们调用的类似Pine语言画图方式的画图函数了。支持:barcolor
bgcolor
plot
fill
hline
plotarrow
plotshape
plotchar
plotcandle
signal
,可以画线、画箭头、标记信息等。这些函数的参数和Pine语言对应的函数参数一致,画图功能也一致。
在以上布林指标的画图例子中再加入一些交易信号箭头,标记,水平线。
/*backtest
start: 2022-03-21 09:00:00
end: 2022-06-21 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/
function main() {
var c = KLineChart()
// 策略主循环
while(true) {
// 轮询间隔
Sleep(500)
// 获取K线数据
let bars = exchange.GetRecords()
if (!bars || bars.length <= 20) {
continue
}
// 计算布林指标
var boll = TA.BOLL(bars)
bars.forEach(function(bar, index) {
c.begin(bar)
// 画图操作
c.plot(boll[0][index], "Boll_Up", {overlay: true}) // 画在图表主图
c.plot(boll[1][index], "Boll_Mid", {overlay: true}) // 画在图表主图
c.plot(boll[2][index], "Boll_Down", {overlay: true}) // 画在图表主图
c.plot(bar.Volume, "volume") // 画在图表副图
c.hline(bar.Open, {overlay: true}) // 水平线
c.plotarrow(bar.Close - bar.Open, {overlay: true}) // 箭头
c.plotshape(bar.Close - bar.Open > 0, {style: 'square', overlay: true}) // 画方块标记
c.plotchar(bar.Close - bar.Open < 0, {char: '❄', size: "20px", overlay: true}) // 画出字符❄
if (boll[0][index] && bar.Close > boll[0][index]) {
c.signal("long", bar.Close, 1.5)
} else if (boll[2][index] && bar.Close < boll[2][index]) {
c.signal("closelong", bar.Close, 1.5)
}
c.close()
})
// 策略交易逻辑
// ...
// ..
}
}
由于Pine语言的策略下单操作是会自动在图表上标记信号的,所以KLineChart
函数创建的图表对象也扩展了一个用于画买入卖出信号标记的函数:c.signal
。
可以声明一个结构用来配置图表样式,例如以下chartCfg变量表示一个网格线的配置信息。
var chartCfg = {
grid: {
show: true,
// 网格水平线
horizontal: {
show: true,
size: 2,
color: '#FF0000', // 水平网格线的颜色
// 'solid'|'dash'
style: 'dash', // 线的类型
dashValue: [2, 2]
},
// 网格垂直线
vertical: {
show: true,
size: 2,
color: '#32CD32',
// 'solid'|'dash'
style: 'solid',
dashValue: [2, 2]
}
},
}
图表配置对象就是设置图表一些属性、外观等的数据结构,如例子中使用的就是一种网格线样式的配置。还有很多可以配置修改的选项,例如X轴、Y轴的相关设置,光标线的设置,提示信息的设置,技术指标样式设置,K线BAR样式的设置等等。
当然如果你对这些还不熟悉,在调用KLineChart
函数创建图表对象时可以不传入图表配置对象。那么创建的图表就是默认样式。使用FMZ平台的API函数KLineChart
函数创建图表对象:
var c = KLineChart(chartCfg)
画出网格线的测试代码:
/*backtest
start: 2022-03-21 09:00:00
end: 2022-06-21 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Binance","currency":"BTC_USDT"}]
*/
function main() {
var chartCfg = {
grid: {
show: true,
// 网格水平线
horizontal: {
show: true,
size: 2,
color: '#FF0000',
// 'solid'|'dash'
style: 'dash',
dashValue: [2, 2]
},
// 网格垂直线
vertical: {
show: true,
size: 2,
color: '#32CD32',
// 'solid'|'dash'
style: 'solid',
dashValue: [2, 2]
}
},
}
var c = KLineChart(chartCfg)
// 策略主循环
while(true) {
// 轮询间隔
Sleep(500)
// 获取K线数据
var bars = _C(exchange.GetRecords)
bars.forEach(function(bar, index) {
c.begin(bar)
c.close()
})
// 策略交易逻辑
// ...
// ..
}
}
可以用来参考图表配置样式的设置。
{
// 网格线
grid: {
show: true,
// 网格水平线
horizontal: {
show: true,
size: 1,
color: '#393939',
// 'solid'|'dash'
style: 'dash',
dashValue: [2, 2]
},
// 网格垂直线
vertical: {
show: false,
size: 1,
color: '#393939',
// 'solid'|'dash'
style: 'dash',
dashValue: [2, 2]
}
},
// 蜡烛图
candle: {
// 蜡烛图上下间距,大于1为绝对值,大于0小余1则为比例
margin: {
top: 0.2,
bottom: 0.1
},
// 蜡烛图类型 'candle_solid'|'candle_stroke'|'candle_up_stroke'|'candle_down_stroke'|'ohlc'|'area'
type: 'candle_solid',
// 蜡烛柱
bar: {
upColor: '#26A69A',
downColor: '#EF5350',
noChangeColor: '#888888'
},
// 面积图
area: {
lineSize: 2,
lineColor: '#2196F3',
value: 'close',
backgroundColor: [{
offset: 0,
color: 'rgba(33, 150, 243, 0.01)'
}, {
offset: 1,
color: 'rgba(33, 150, 243, 0.2)'
}]
},
priceMark: {
show: true,
// 最高价标记
high: {
show: true,
color: '#D9D9D9',
textMargin: 5,
textSize: 10,
textFamily: 'Helvetica Neue',
textWeight: 'normal'
},
// 最低价标记
low: {
show: true,
color: '#D9D9D9',
textMargin: 5,
textSize: 10,
textFamily: 'Helvetica Neue',
textWeight: 'normal',
},
// 最新价标记
last: {
show: true,
upColor: '#26A69A',
downColor: '#EF5350',
noChangeColor: '#888888',
line: {
show: true,
// 'solid'|'dash'
style: 'dash',
dashValue: [4, 4],
size: 1
},
text: {
show: true,
size: 12,
paddingLeft: 2,
paddingTop: 2,
paddingRight: 2,
paddingBottom: 2,
color: '#FFFFFF',
family: 'Helvetica Neue',
weight: 'normal',
borderRadius: 2
}
}
},
// 提示
tooltip: {
// 'always' | 'follow_cross' | 'none'
showRule: 'always',
// 'standard' | 'rect'
showType: 'standard',
labels: ['时间', '开', '收', '高', '低', '成交量'],
values: null,
defaultValue: 'n/a',
rect: {
paddingLeft: 0,
paddingRight: 0,
paddingTop: 0,
paddingBottom: 6,
offsetLeft: 8,
offsetTop: 8,
offsetRight: 8,
borderRadius: 4,
borderSize: 1,
borderColor: '#3f4254',
backgroundColor: 'rgba(17, 17, 17, .3)'
},
text: {
size: 12,
family: 'Helvetica Neue',
weight: 'normal',
color: '#D9D9D9',
marginLeft: 8,
marginTop: 6,
marginRight: 8,
marginBottom: 0
}
}
},
// 技术指标
technicalIndicator: {
margin: {
top: 0.2,
bottom: 0.1
},
bar: {
upColor: '#26A69A',
downColor: '#EF5350',
noChangeColor: '#888888'
},
line: {
size: 1,
colors: ['#FF9600', '#9D65C9', '#2196F3', '#E11D74', '#01C5C4']
},
circle: {
upColor: '#26A69A',
downColor: '#EF5350',
noChangeColor: '#888888'
},
// 最新值标记
lastValueMark: {
show: false,
text: {
show: false,
color: '#ffffff',
size: 12,
family: 'Helvetica Neue',
weight: 'normal',
paddingLeft: 3,
paddingTop: 2,
paddingRight: 3,
paddingBottom: 2,
borderRadius: 2
}
},
// 提示
tooltip: {
// 'always' | 'follow_cross' | 'none'
showRule: 'always',
// 'standard' | 'rect'
showType: 'standard',
showName: true,
showParams: true,
defaultValue: 'n/a',
text: {
size: 12,
family: 'Helvetica Neue',
weight: 'normal',
color: '#D9D9D9',
marginTop: 6,
marginRight: 8,
marginBottom: 0,
marginLeft: 8
}
}
},
// x轴
xAxis: {
show: true,
height: null,
// x轴线
axisLine: {
show: true,
color: '#888888',
size: 1
},
// x轴分割文字
tickText: {
show: true,
color: '#D9D9D9',
family: 'Helvetica Neue',
weight: 'normal',
size: 12,
paddingTop: 3,
paddingBottom: 6
},
// x轴分割线
tickLine: {
show: true,
size: 1,
length: 3,
color: '#888888'
}
},
// y轴
yAxis: {
show: true,
width: null,
// 'left' | 'right'
position: 'right',
// 'normal' | 'percentage' | 'log'
type: 'normal',
inside: false,
reverse: false,
// y轴线
axisLine: {
show: true,
color: '#888888',
size: 1
},
// y轴分割文字
tickText: {
show: true,
color: '#D9D9D9',
family: 'Helvetica Neue',
weight: 'normal',
size: 12,
paddingLeft: 3,
paddingRight: 6
},
// y轴分割线
tickLine: {
show: true,
size: 1,
length: 3,
color: '#888888'
}
},
// 图表之间的分割线
separator: {
size: 1,
color: '#888888',
fill: true,
activeBackgroundColor: 'rgba(230, 230, 230, .15)'
},
// 十字光标
crosshair: {
show: true,
// 十字光标水平线及文字
horizontal: {
show: true,
line: {
show: true,
// 'solid'|'dash'
style: 'dash',
dashValue: [4, 2],
size: 1,
color: '#888888'
},
text: {
show: true,
color: '#D9D9D9',
size: 12,
family: 'Helvetica Neue',
weight: 'normal',
paddingLeft: 2,
paddingRight: 2,
paddingTop: 2,
paddingBottom: 2,
borderSize: 1,
borderColor: '#505050',
borderRadius: 2,
backgroundColor: '#505050'
}
},
// 十字光标垂直线及文字
vertical: {
show: true,
line: {
show: true,
// 'solid'|'dash'
style: 'dash',
dashValue: [4, 2],
size: 1,
color: '#888888'
},
text: {
show: true,
color: '#D9D9D9',
size: 12,
family: 'Helvetica Neue',
weight: 'normal',
paddingLeft: 2,
paddingRight: 2,
paddingTop: 2,
paddingBottom: 2,
borderSize: 1,
borderColor: '#505050',
borderRadius: 2,
backgroundColor: '#505050'
}
}
},
// 图形
shape: {
point: {
backgroundColor: '#2196F3',
borderColor: '#2196F3',
borderSize: 1,
radius: 4,
activeBackgroundColor: '#2196F3',
activeBorderColor: '#2196F3',
activeBorderSize: 1,
activeRadius: 6
},
line: {
// 'solid'|'dash'
style: 'solid'
color: '#2196F3',
size: 1,
dashValue: [2, 2]
},
polygon: {
// 'stroke'|'fill'
style: 'stroke',
stroke: {
// 'solid'|'dash'
style: 'solid',
size: 1,
color: '#2196F3',
dashValue: [2, 2]
},
fill: {
color: 'rgba(33, 150, 243, 0.1)'
}
},
arc: {
// 'stroke'|'fill'
style: 'stroke',
stroke: {
// 'solid'|'dash'
style: 'solid',
size: 1,
color: '#2196F3',
dashValue: [2, 2]
},
fill: {
color: '#2196F3'
}
},
text: {
style: 'fill',
color: '#2196F3',
size: 12,
family: 'Helvetica Neue',
weight: 'normal',
offset: [0, 0]
}
},
annotation: {
// 'top' | 'bottom' | 'point'
position: 'top',
offset: [20, 0]
symbol: {
// 'diamond' | 'circle' | 'rect' | 'triangle' | 'custom' | 'none'
type: 'diamond',
size: 8,
color: '#2196F3',
activeSize: 10,
activeColor: '#FF9600'
}
},
tag: {
// 'top' | 'bottom' | 'point'
position: 'point',
offset: 0,
line: {
show: true,
style: LineStyle.DASH,
dashValue: [4, 2],
size: 1,
color: '#2196F3'
},
text: {
color: '#FFFFFF',
backgroundColor: '#2196F3',
size: 12,
family: 'Helvetica Neue',
weight: 'normal',
paddingLeft: 2,
paddingRight: 2,
paddingTop: 2,
paddingBottom: 2,
borderRadius: 2,
borderSize: 1,
borderColor: '#2196F3'
},
mark: {
offset: 0,
color: '#FFFFFF',
backgroundColor: '#2196F3',
size: 12,
family: 'Helvetica Neue',
weight: 'normal',
paddingLeft: 2,
paddingRight: 2,
paddingTop: 2,
paddingBottom: 2,
borderRadius: 2,
borderSize: 1,
borderColor: '#2196F3'
}
}
}
这样是不是让策略画图设计更加简单了呢!