概述
Gekko是一个由Node编写的基础程序化交易应用。本文主要介绍如何在Gekko的基础上编写最基本的策略已经进行回测的过程。
Gekko的交易基础是根据策略发出的long()做多和short()做空机制进行交易的。
主要功能包括了:
- 抓取各个市场上实时数据
- 计算指标
- 交易机器人执行交易
- 纸盘交易模拟
- 回测分析,收益及风险评估
- 结果可视化
一般来说有了抓取交易、策略引擎和交易机器人基本上就算一个基础的程序化交易应用了。
策略与指标
Gekko的使用和一般常用的TB、MC大体都差不多。主要由指标、接受Bar事件和发出交易信号几部分组成。
//官方文档中的示例代码
// Let's create our own strategy
var strat = {};
// Prepare everything our strat needs
strat.init = function() {
// your code!
}
// What happens on every new candle?
strat.update = function(candle) {
// your code!
}
// For debugging purposes.
strat.log = function() {
// your code!
}
// Based on the newly calculated
// information, check if we should
// update or not.
strat.check = function(candle) {
// your code!
}
// Optional for executing code
// after completion of a backtest.
// This block will not execute in
// live use as a live gekko is
// never ending.
strat.end = function() {
// your code!
}
module.exports = strat;
指标
指标即技术指标,用于对单独的Bar的离散信息做量化的转换。
举个最简单的例子SMA,简单移动均线。如果我们需要使用简单移动均线作为策略的技术指标。则首先我们需要在Gekko中实现指标(其实也可以使用ta-lib提供的现有指标)。
SMA的计算比较简单,选择一个价格作为指标,然后计算其在最近的N个周期内的价格简单数学平均数即可。
故函数化后,我们先硬编码价格属性为price,其方法签名为:
number sma(length);
结合官方示例代码进行说明:
// SMA(windowLength)
// @Param : Lenght 计算的周期范围
var Indicator = function(windowLength) {
this.input = 'price';
this.windowLength = windowLength;
//历史价格矩阵,会根据length的大小变化
this.prices = [];
// SMA的值
this.result = 0;
// Bar的索引,第几根
this.age = 0;
//价格简单数学总和
this.sum = 0;
}
// 获取到一个新的Bar价格后
Indicator.prototype.update = function(price) {
//最老的一根Bar价格
var tail = this.prices[this.age] || 0; // oldest price in window
//进行替换更新
this.prices[this.age] = price;
//更新prices矩阵数值之和,怕prices窗口太大遍历效率太低吧
this.sum += price - tail;
// sum / length 即简单平均数
this.result = this.sum / this.prices.length;
// 当前索引值+1
this.age = (this.age + 1) % this.windowLength
}
module.exports = Indicator;
策略
官方演示的均线策略是使用双均线突破趋势进行演示,其主要流程需要注意:
- 引入库文件
- 引入指标
- 至少实现check(candle)
- 放出交易信号advise()
官方完整的Dual EMA策略源代码
// helpers
var _ = require('lodash');
var log = require('../core/log.js');
// let's create our own method
var method = {};
// prepare everything our method needs
method.init = function() {
this.name = 'DEMA';
this.currentTrend;
this.requiredHistory = this.tradingAdvisor.historySize;
// define the indicators we need
this.addIndicator('dema', 'DEMA', this.settings);
}
// what happens on every new candle?
method.update = function(candle) {
// nothing!
}
// for debugging purposes: log the last calculated
// EMAs and diff.
method.log = function() {
var dema = this.indicators.dema;
log.debug('calculated DEMA properties for candle:');
log.debug('\t', 'long ema:', dema.long.result.toFixed(8));
log.debug('\t', 'short ema:', dema.short.result.toFixed(8));
log.debug('\t diff:', dema.result.toFixed(5));
log.debug('\t DEMA age:', dema.short.age, 'candles');
}
method.check = function(candle) {
var dema = this.indicators.dema;
var diff = dema.result;
var price = candle.close;
var message = '@ ' + price.toFixed(8) + ' (' + diff.toFixed(5) + ')';
if(diff > this.settings.thresholds.up) {
log.debug('we are currently in uptrend', message);
if(this.currentTrend !== 'up') {
this.currentTrend = 'up';
this.advice('long');
} else
this.advice();
} else if(diff < this.settings.thresholds.down) {
log.debug('we are currently in a downtrend', message);
if(this.currentTrend !== 'down') {
this.currentTrend = 'down';
this.advice('short');
} else
this.advice();
} else {
log.debug('we are currently not in an up or down trend', message);
this.advice();
}
}
module.exports = method;
分部解说
init初始化
策略的init初始化方法主要做以下几个任务:
- 定义策略的显示名称,需要注意唯一
- 定义策略的实例变量
- 通过addIndicator引入外部指标
method.init = function() {
this.name = 'DEMA';
this.currentTrend;
this.requiredHistory = this.tradingAdvisor.historySize;
// define the indicators we need
this.addIndicator('dema', 'DEMA', this.settings);
}
check函数:放出交易信号
所有的技术指标分析最终都是为了进行交易而编写的。
我们当前的策略大核心是当两个EMA均线,一根快线,一个慢线价格进行比较:
- 如快线向下穿越慢线,则卖出
- 如快线向上穿越慢线,则买入
再加上一些数学化去除噪音信号,优化方程,则有设立指标:
EMD_DIFF = (慢线 - 快线)/ ( (慢线 + 快线) / 2 )
其函数分为两部分, - 当慢线在快线下方,即价格低时候,EMA_DIFF总有 < 0;
- 当慢线在快线上方,即价格高时候,EMA_DIFF总有 > 0;
我们通过正负可以知道快慢线的运行情况。
在设立一个指标用快慢线之差除以快慢线当前的平均是来反应其偏离的百分比来过滤一些无效的震荡情况,当然这个参数不总是越大越好,需要通过回测进行确定。
官方的DEMA已经做了一个双均线的指标,快线和慢线的EMA指标分别为指标的short和long属性。
\\DEMA指标源代码
var Indicator = function(config) {
this.input = 'price'
this.result = false;
this.short = new EMA(config.short);
this.long = new EMA(config.long);
}
Indicator.prototype.calculateEMAdiff = function() {
var shortEMA = this.short.result;
var longEMA = this.long.result;
this.result = 100 * (shortEMA - longEMA) / ((shortEMA + longEMA) / 2);
}
看完指标代码实现后,根据策略核心来进行交易发出实际的代码编写:
首先初始化赋值所有需要的变量,包括dema指标、ema的diff值,当前最新Bar的收盘价格
method.check = function(candle) {
var dema = this.indicators.dema;
var diff = dema.result;
var price = candle.close;
//...
}
判断当前diff是否在过滤震荡区间内(现货无做空平空部分)
if(diff > this.settings.thresholds.up) {
log.debug('we are currently in uptrend', message);
// 策略买入部分
} else if(diff < this.settings.thresholds.down) {
// 策略卖出部分
}else {
log.debug('we are currently not in an up or down trend', message);
this.advice();
}
做多逻辑
//如果上一次判断的形态是慢线在下,则更新为up即慢线在上,且发出买入信号advice('long')
if(this.currentTrend !== 'up') {
this.currentTrend = 'up';
this.advice('long');
} else
this.advice();
}
做空逻辑同理
if(this.currentTrend !== 'down') {
this.currentTrend = 'down';
this.advice('short');
} else
this.advice();
}
TA-Lib指标拓展
TA-Lib是在Python量化做金融技术分析常用的一套指标库,我们可以通过npm来安装引入,来节约每个技术指标都需要用js重写一遍的麻烦,也方便从python的分析平台上迁移策略过来。
安装
在服务器的gekko目录下通过npm安装
npm install talib
在策略中使用TA-Lib的指标
策略中引入即可
method.init = function() {
var customMACDSettings = {
optInFastPeriod: 10,
optInSlowPeriod: 21,
optInSignalPeriod: 9
}
// add the indicator to the strategy
this.addTalibIndicator('mymacd', 'macd', customMACDSettings);
}
method.check = function() {
// use indicator results
var result = this.talibIndicators.mymacd.result;
var macddiff = result['outMACD'] - result['outMACDSignal'];
// do something with macdiff
}