nodetrader
Version:
trader framework based on nodectp
315 lines (259 loc) • 10.4 kB
JavaScript
// 策略基类, 所有自定义策略需继承此类
const sma = require('ta-lib.sma');
const macd = require('ta-lib.macd');
const logger = require('../../lib/logger').strategy;
const moment = require('moment');
const dict = require('./dict');
const constant = require('./constant');
const BarModel = require('../../db/model/bar');
const Bar= require('./bar');
const PositionBuffer = require('./positionbuffer');
// 策略的字段应如下:
// {
// strategyName: 'DefaultStrategy',
// tradeInstrumentIDList: [], // 要交易的合约 ['ru1709', 'zn1707']
// initDays: 22, // 需要预加载存储在数据库Bar的天数, 在交易开始之前需要先获取前initDays天的Bar数据用于交易时分析
// subscribeInstrumentIDList: [], // 订阅行情的合约, 之所有和交易的合约不完全一样, 是因为可能需要额外的合约作为参考 ['ru1709', 'rb1710', 'zn1707']
// param: {}
// }
function Strategy(strategy) {
this.strategyName = dict.StrategyName_Base;
this.logger = logger;
this.tradeInstrumentIDList = strategy.tradeInstrumentIDList || [];
this.subscribeInstrumentIDList = strategy.subscribeInstrumentIDList || strategy.tradeInstrumentIDList || [];
this.initDays = strategy.initDays || constant.strategy_defaultInitDays;
this.periodValue = strategy.periodValue || constant.strategy_defaultPeriodValue;
this.param = strategy.param || {};
this.instrumentMap = {};
// 产品信息, 交易引擎启动时会根据引擎的不同加载product或localproduct
this.product = null;
this.orderMap = {}; // 已提交订单的缓存
this.tradeMap = {}; // 成交回报的缓存
this.positionBuffer = new PositionBuffer();
this.initContext();
}
(function() {
this.initContext = function() {
var me = this;
this.subscribeInstrumentIDList.forEach(function(instrumentID) {
me.instrumentMap[instrumentID] = {
// 1分钟指标
lastbar: null, // 上一根bar
bar: new Bar(), // 当前bar
barList: [],
closeList: [],
// periodValue分钟指标
lastPeriodBar: null,
periodBar: new Bar(),
periodBarList: [],
periodBarCloseList: []
};
me.orderMap[instrumentID] = {};
me.tradeMap[instrumentID] = {};
me.positionBuffer.init(instrumentID);
});
};
this.init = function(engine) {
this.loadProduct(engine);
this.preload(engine);
};
this.preload = function(engine) {
var endTime = engine.engineName === dict.EngineName_Backtest
? moment(engine.startDateTime, constant.pattern_datetime).valueOf()
: new Date().getTime();
var startTime = endTime - this.initDays * 86400000;
startTime = moment(startTime).format(constant.pattern_minuteBarPeriod);
endTime = moment(endTime).format(constant.pattern_minuteBarPeriod);
var instrumentMap = this.instrumentMap;
var me = this;
BarModel.findAll({
where: {
InstrumentID: {
$in: this.subscribeInstrumentIDList
},
periodDatetime: {
$gte: startTime,
$lt: endTime
}
},
order: 'id DESC'
})
.then(function(list) {
if (!list.length) {
return;
}
list.forEach(function(bar) {
bar = bar.toJSON();
var instmap = instrumentMap[bar.instrumentID];
instmap.barList.unshift(bar);
});
me.periodValue > 1
&& list.reverse().forEach(function(bar) {
bar = bar.toJSON();
me.buildLastPeriodBar(bar);
});
});
};
this.loadProduct = function(engine) {
var productModulePath =
engine.engineName === dict.EngineName_Firm || engine.engineName === dict.EngineName_Market
? '../product' : '../localproduct';
this.product = require(productModulePath);
};
/**
* @param {Bar} bar 当前分钟bar
* @param {Bar} lastbar 前一分钟bar, 注意每天第一根bar生成的时候lastbar还没有生成
* @param {Object} tick 从交易所服务器推送而来的tick
* @param {Array} barList bar列表
* @param {Engine} engine 交易引擎, 可能是实盘也可能是回测
* 到这一步时, 前一分钟bar所有需要的技术指标都已计算完成
* 具体交易逻辑应该写在这里
*/
this.onCurrentBarAndTick = function(bar, lastbar, tick, barList, engine) {
};
/**
* 1分钟周期指标, 前一分钟bar
* 这里已经完成了对lastbar各指标的计算
*/
this.onLastBar = function(lastbar, barList, engine) {
};
/**
* 根据指标周期periodValue, 使用1分钟bar生成所需周期的bar
*/
this.buildLastPeriodBar = function(lastbar) {
// 只有大于1分钟的周期(例如3分钟或5分钟)才生成相应的周期Bar
if (this.periodValue > 1) {
var periodDatetime = this.convertPeriodDatetime(lastbar.periodDatetime, this.periodValue);
var instmap = this.instrumentMap[lastbar.instrumentID];
var periodBar = instmap.periodBar;
if (periodDatetime != periodBar.periodDatetime) {
if (periodBar.periodDatetime) {
instmap.lastPeriodBar = periodBar;
this._onLastPeriodBar(periodBar);
}
periodBar = new Bar();
this.instrumentMap[lastbar.instrumentID].periodBar = periodBar;
periodBar.instrumentID = lastbar.instrumentID;
periodBar.productID = lastbar.productID;
periodBar.exchangeID = lastbar.exchangeID;
periodBar.periodDatetime = periodDatetime;
periodBar.open = lastbar.open;
periodBar.high = lastbar.high;
periodBar.low = lastbar.low;
periodBar.close = lastbar.close;
periodBar.volume = lastbar.volume;
periodBar.openInterest = lastbar.openInterest;
periodBar.settlement = lastbar.settlement;
}
else {
periodBar.high = Math.max(periodBar.high, lastbar.high);
periodBar.low = Math.min(periodBar.low, lastbar.low);
periodBar.close = lastbar.close;
periodBar.volume += lastbar.volume;
periodBar.openInterest = lastbar.openInterest;
periodBar.settlement = lastbar.settlement;
}
}
};
this._onLastPeriodBar = function(lastPeriodBar) {
var instmap = this.instrumentMap[lastPeriodBar.instrumentID];
instmap.periodBarList.push(lastPeriodBar);
instmap.periodBarCloseList.push(lastPeriodBar.close);
var ma5List = sma(instmap.periodBarCloseList, 5);
var ma10List = sma(instmap.periodBarCloseList, 10);
var ma20List = sma(instmap.periodBarCloseList, 20);
var ma40List = sma(instmap.periodBarCloseList, 40);
var ma60List = sma(instmap.periodBarCloseList, 60);
lastPeriodBar.ma5 = ma5List[ma5List.length - 1] || null;
lastPeriodBar.ma10 = ma10List[ma10List.length - 1] || null;
lastPeriodBar.ma20 = ma20List[ma20List.length - 1] || null;
lastPeriodBar.ma40 = ma40List[ma40List.length - 1] || null;
lastPeriodBar.ma60 = ma60List[ma60List.length - 1] || null;
var msh = macd(instmap.periodBarCloseList);
var macdList = msh.macd;
var signalLineList = msh.signalLine;
var histogramList = msh.histogram;
lastPeriodBar.macd = macdList[macdList.length - 1] || null;
lastPeriodBar.signalLine = signalLineList[signalLineList.length - 1] || null;
lastPeriodBar.histogram = histogramList[histogramList.length - 1] || null;
lastPeriodBar.histogram && (lastPeriodBar.histogram = lastPeriodBar.histogram * 2);
this.onLastPeriodBar(lastPeriodBar);
};
/**
* periodValue分钟周期指标, 前一个periodBar
*/
this.onLastPeriodBar = function(lastPeriodBar) {
};
this.convertPeriodDatetime = function(periodDatetime, periodValue) {
var date = new Date(moment(periodDatetime, constant.pattern_minuteBarPeriod).valueOf());
var minutes = date.getMinutes();
var minuteInteger = parseInt(minutes / periodValue);
date.setMinutes(minuteInteger * periodValue);
return moment(date).format(constant.pattern_minuteBarPeriod);
};
// 供交易引擎调用
this.updateOrderMap = function(data) {
var orderMap = this.orderMap[data.InstrumentID];
orderMap[data.OrderRef] = data;
};
// 供交易引擎调用
this.updateTradeMap = function(data) {
var tradeMap = this.tradeMap[data.InstrumentID];
tradeMap[data.TradeID] = data;
this.positionBuffer.add(data);
};
/**
* @param {String|Object} data 合约代码或包含合约代码的对象
*/
this.getInstrumentMap = function(data) {
var instrumentID = lang.isString(data) ? data : data.InstrumentID;
return this.instrumentMap[instrumentID];
};
/**
* @param {String|Object} data 合约代码或包含合约代码的对象
*/
this.getOrderMap = function(data) {
var instrumentID = lang.isString(data) ? data : data.InstrumentID;
return this.orderMap[instrumentID];
};
/**
* @param {String|Object} data 合约代码或包含合约代码的对象
*/
this.getTradeMap = function(data) {
var instrumentID = lang.isString(data) ? data : data.InstrumentID;
return this.tradeMap[instrumentID];
};
/**
* @param {String|Object} data 合约代码或包含合约代码的对象
* @return {Object} 某个合约的净持仓和持仓均价, 字段说明如下:
* {
* pos: 2 || -2, // 持仓数量, 正数指净持仓为多单, 负数指净持仓为空单, 0表示空仓
* price: 21220 // 持仓均价
* }
*/
this.getPosition = function(data) {
this.positionBuffer.get(data);
};
/**
* 报单通知, 订单状态发生变化时的响应
* 要区分是下单成功、还是撤单、还是委托成功
*/
this.onOrder = function(data) {
};
/**
* 成交通知, 订单成交时的响应
*/
this.onTrade = function(data) {
};
/**
* 请求查询资金账户响应
*/
this.onAccount = function(data, rsp, nRequestID, bIsLast) {
};
/**
* 请求查询投资者持仓响应
*/
this.onPosition = function(data, rsp, nRequestID, bIsLast) {
};
}).call(Strategy.prototype);
module.exports = Strategy;