hqchart
Version:
HQChart - H5, 微信小程序 沪深/港股/数字货币/期货/美股 K线图(kline),走势图,缩放,拖拽,十字光标,画图工具,截图,筹码图. 分析家语法,通达信语法,(麦语法),第3方数据对接
374 lines (306 loc) • 15.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _map = require('babel-runtime/core-js/map');
var _map2 = _interopRequireDefault(_map);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*
Copyright (c) 2018 jones
http://www.apache.org/licenses/LICENSE-2.0
开源项目 https://github.com/jones2000/HQChart
jones_2000@163.com
个股指标回测
*/
/*
指标回测
计算: Trade: {Count 交易次数 Days:交易天数 Success:成功交易次数 Fail:失败交易次数}
Day: {Count:总运行 Max:最长运行 Min:最短运行 Average:平均运行}
Profit: 总收益 StockProfit:个股收益 Excess:超额收益 MaxDropdown:最大回撤 Beta:β(Beta)系数(指标里面需要又大盘数据)
NetValue: [ {Date:日期, Net:净值, Close:股票收盘价, IndexClose:大盘的收盘价}, ]
*/
function RegressionTest() {
//只读数据不能修改
this.HistoryData; //K线数据
this.BuyData; //策略买数据
this.SellData; //策略卖数据
this.IndexClose; //大盘收盘价
this.NetCalculateModel = 0; //净值及收益计算模型 0=使用B点开盘价计算 1=使用B点下一天的开盘价计算
this.InitialCapital = 10000; //初始资金1W
//计算结果数据
this.Data = new _map2.default(); //key:DATA_NAME value:数据
this.SetPolicyData = function (obj) //设置策略结果的数据 {KLineData:个股K线数据, BuyData:策略买数据, SellData:策略卖数据, IndexClose:大盘收盘价}
{
this.HistoryData = obj.KLineData; //K线数据
this.BuyData = obj.BuyData; //策略买数据
this.SellData = obj.SellData; //策略卖数据
if (obj.IndexClose) this.IndexClose = obj.IndexClose; //大盘收盘价 如果没有大盘数据 就不计算β(Beta)系数 和指数涨幅数据
};
this.ClearData = function () //清空所有的结果数据
{
this.Data = new _map2.default();
};
this.GetBSData = function (startDate) //BS点配对 { B:[{Data:K线数据, Count:天数, NextOpen:下一天的开盘价 }], S:{Data:K线数据}}
{
var index = null;
for (var i = 0; i < this.HistoryData.length; ++i) {
var item = this.HistoryData[i];
if (item.Date >= startDate) {
index = i;
break;
}
}
if (index === null) return null;
console.log('[RegressionTest::GetBSData] startDate=' + startDate + ' index=' + index);
var aryBS = [];
var bsItem = null;
for (var i = index; i < this.HistoryData.length; ++i) {
var buyItem = this.BuyData[i];
var sellItem = this.SellData[i];
var kLineItem = this.HistoryData[i];
if (bsItem === null) {
if (buyItem > 0) {
var bItem = { Data: kLineItem, Count: 0 };
if (i + 1 < this.HistoryData.length) bItem.NextOpen = this.HistoryData[i + 1].Open; //获取下一天的开盘价
bsItem = { B: [bItem], S: null }; //B可以多个,S一个
}
} else {
for (var j in bsItem.B) {
++bsItem.B[j].Count;
} //天数+1
if (buyItem > 0) {
var bItem = { Data: kLineItem, Count: 0 };
if (i + 1 < this.HistoryData.length) bItem.NextOpen = this.HistoryData[i + 1].Open; //获取下一天的开盘价
bsItem.B.push(bItem);
} else if (sellItem > 0) {
bsItem.S = { Data: kLineItem };
aryBS.push(bsItem);
bsItem = null;
}
}
}
var data = { StartDate: this.HistoryData[index].Date, StartIndex: index, Count: this.HistoryData.length - index, BSData: aryBS };
console.log('[RegressionTest::GetBSData] data', data);
return data;
};
this.Calculate = function (data) {
var day = { Count: data.Count, Max: null, Min: null, Average: null }; //Count:总运行 Max:最长运行 Min:最短运行 Average:平均运行
var trade = { Count: 0, Days: 0, Success: 0, Fail: 0, SuccessRate: 0 }; //Count 交易次数 Days:交易天数 Success:成功交易次数 Fail:失败交易次数
for (var i in data.BSData) {
var item = data.BSData[i];
for (var j in item.B) {
var bItem = item.B[j];
if (day.Max === null) day.Max = bItem.Count;else if (day.Max < bItem.Count) day.Max = bItem.Count;
if (day.Min === null) day.Min = bItem.Count;else if (day.Min > bItem.Count) day.Min = bItem.Count;
++trade.Count;
trade.Days += bItem.Count;
if (item.S.Data.Close > bItem.Data.Open) ++trade.Success;else ++trade.Fail;
}
}
if (trade.Count > 0) {
day.Average = trade.Days / trade.Count;
trade.SuccessRate = trade.Success / trade.Count;
}
//计算收益(总收益)
var profit = 1,
buyPrice;
for (var i in data.BSData) {
var item = data.BSData[i];
if (this.NetCalculateModel === 1 && item.B[0].NextOpen > 0) buyPrice = item.B[0].NextOpen;else buyPrice = item.B[0].Data.Open;
var sellPrice = item.S.Data.Close;
var value = (sellPrice - buyPrice) / buyPrice + 1;
profit *= value;
}
profit -= 1; //公式:[(1+收益1)*(1+收益2)*(1+收益3)……(1+收益n)-1] x 100%
//标的证券收益
var yClose = this.HistoryData[data.StartIndex].Close; //使用前收盘
var close = this.HistoryData[this.HistoryData.length - 1].Close; //最后一个大盘收盘价
var stockProfit = (close - yClose) / yClose;
console.log('[RegressionTest::Calculate] stock profit first[' + this.HistoryData[data.StartIndex].Date + ', YClose=' + this.HistoryData[data.StartIndex].YClose + '] end[' + this.HistoryData[this.HistoryData.length - 1].Date + ', Close=' + this.HistoryData[this.HistoryData.length - 1].Close + ']');
var netValue = this.CaclulateNetValue(data);
var maxDropdown = null,
beta = null;
if (netValue && netValue.length > 0) {
maxDropdown = this.CaclulateMaxDropdown(netValue);
if (this.IndexClose) beta = this.CaclulateBeta(netValue);
}
//Profit:收益 StockProfit:标的证券收益 Excess:超额收益(加上BS配对的数据)
var result = { Day: day, Trade: trade, Profit: profit, StockProfit: stockProfit, Excess: profit - stockProfit, NetValue: netValue, MaxDropdown: maxDropdown, Beta: beta, BSDataPair: data.BSData };
console.log('[RegressionTest::Calculate] NetCalculateModel, result ', this.NetCalculateModel, result);
return result;
};
this.CaclulateNetValue = function (data) //计算净值
{
var index = data.StartIndex;
var aryDay = []; //{Close:收盘 , Open:开盘, Position:持仓数量, Cache:现金 , MarketValue:总市值}
var lastDayItem = { Position: 0, Cache: this.InitialCapital };
var bsItem = null,
buyPrice;
for (var i = index; i < this.HistoryData.length; ++i) {
var buyItem = this.BuyData[i];
var sellItem = this.SellData[i];
var kLineItem = this.HistoryData[i];
var dayItem = { Date: kLineItem.Date, Position: lastDayItem.Position, Cache: lastDayItem.Cache, Open: kLineItem.Open, Close: kLineItem.Close };
dayItem.MarketValue = dayItem.Position * dayItem.Close + dayItem.Cache; //市值 股票+现金
if (bsItem === null) {
if (buyItem > 0) //买
{
bsItem = { B: { Data: kLineItem }, S: null };
if (this.NetCalculateModel === 1 && i + 1 < this.HistoryData.length && this.HistoryData[i + 1].Open > 0) buyPrice = this.HistoryData[i + 1].Open; //使用B点下一天的开盘价买
else buyPrice = dayItem.Open;
var position = parseInt(dayItem.Cache / buyPrice); //开盘价买
var cache = dayItem.Cache - buyPrice * position; //剩余的现金
dayItem.Position = position;
dayItem.Cache = cache;
dayItem.MarketValue = dayItem.Position * dayItem.Close + dayItem.Cache; //市值 股票+现金
}
} else {
if (sellItem > 0) //卖
{
bsItem.S = { Data: kLineItem };
bsItem = null;
var stockValue = dayItem.Position * dayItem.Close; //卖掉的股票钱
dayItem.Position = 0;
dayItem.Cache += stockValue; //卖掉的钱放到现金里面
dayItem.MarketValue = dayItem.Position * dayItem.Close + dayItem.Cache; //市值 股票+现金
}
}
//缓存上一天的数据
lastDayItem.Position = dayItem.Position;
lastDayItem.Cache = dayItem.Cache;
dayItem.Net = dayItem.MarketValue / this.InitialCapital; //净值
if (this.IndexClose) dayItem.IndexClose = this.IndexClose[i]; //指数收盘价
aryDay.push(dayItem);
}
//console.log('[RegressionTest::CaclulateNetValue] aryDay',aryDay);
if (aryDay.length <= 0) return [];
var netValue = []; //净值 {Date:日期, Net:净值, Close:股票收盘价, IndexClose:大盘的收盘价}
for (var i = 0; i < aryDay.length; ++i) {
var item = aryDay[i];
var dataItem = { Net: item.Net, Date: item.Date, Close: item.Close };
if (item.IndexClose) dataItem.IndexClose = item.IndexClose;
netValue.push(dataItem);
}
//console.log('[RegressionTest::CaclulateNetValue] netValue',netValue);
return netValue;
};
this.CaclulateMaxDropdown = function (data) //最大回撤
{
var maxNet = data[0].Net; //最大净值
var maxValue = 0;
var maxDay;
for (var i = 1; i < data.length; ++i) {
var item = data[i];
var value = 1 - item.Net / maxNet; //1-策略当日净值 / 当日之前策略最大净值
if (maxValue < value) {
maxValue = value;
maxDay = item;
}
if (maxNet < item.Net) maxNet = item.Net; //取当前最大的净值
}
console.log('[RegressionTest::CaclulateMaxDropdown] maxDay', maxDay);
return maxValue;
};
this.CaclulateBeta = function (data) //β(Beta)系数,参数是净值数组NetValue
{
var lastItem = data[0]; //上一天的数据
var indexProfit = []; //大盘涨幅
var bsProfit = []; //策略涨幅
var indexProfitTotal = 0,
bsProfitTotal = 0;
for (var i = 1; i < data.length; ++i) {
indexProfit[i - 1] = 0;
bsProfit[i - 1] = 0;
var item = data[i];
if (item.IndexClose > 0 && lastItem.IndexClose > 0) indexProfit[i - 1] = (item.IndexClose - lastItem.IndexClose) / lastItem.IndexClose;
if (item.Net > 0 && lastItem.Net > 0) bsProfit[i - 1] = (item.Net - lastItem.Net) / lastItem.Net;
//if (item.Close>0 && lastItem.Close>0) bsProfit[i-1]=(item.Close-lastItem.Close)/lastItem.Close;
indexProfitTotal += indexProfit[i - 1];
bsProfitTotal += bsProfit[i - 1];
lastItem = item;
}
var averageIndexProfit = indexProfitTotal / indexProfit.length;
var averageBSProfit = bsProfitTotal / bsProfit.length;
var betaCOV = 0; //协方差
for (var i = 0; i < indexProfit.length; ++i) {
betaCOV += (bsProfit[i] - averageBSProfit) * (indexProfit[i] - averageIndexProfit);
}
var betaVAR = 0; //标准差:方差的开2次方
for (var i = 0; i < indexProfit.length; ++i) {
betaVAR += (indexProfit[i] - averageIndexProfit) * (indexProfit[i] - averageIndexProfit);
}
return betaCOV / betaVAR;
};
this.Execute = function (obj) //开始计算[ { Name:名字 , Date:起始日期 格式(20180101) }, ....]
{
for (var i in obj) {
var item = obj[i];
if (this.Data.has(item.Name)) //已经计算过了不计算
{
console.log('[RegressionTest::Execute] id=' + i + ' Name=' + item.Name + ' Date:' + item.Date + ' is exsit.');
continue;
}
console.log('[RegressionTest::Execute] id=' + i + ' Name=' + item.Name + ' Date:' + item.Date);
var data = this.GetBSData(item.Date);
var result = this.Calculate(data);
this.Data.set(item.Name, result);
}
};
}
//计算叠加数据 (日期必须匹配)
RegressionTest.CaclulateOverlayData = function (obj) //{Main:主数据, Sub:[]//多组叠加数据}
{
if (!obj.Main || !obj.Sub) return null;
var count = obj.Main.length;
for (var i in obj.Sub) {
var item = obj.Sub[i];
if (item.length != count) {
console.log('[RegressionTest::OverlayData] id=' + i + ' data count not match. MainData count=' + count);
return null;
}
}
var result = []; //[0]:主数据 , [1...]:叠加数据
var firstData = { Sub: [] };
firstData.Main = obj.Main[0];
result.push([]);
for (var i = 0; i < obj.Sub.length; ++i) {
var subData = obj.Sub[i];
firstData.Sub[i] = subData[0];
result.push([]);
}
for (var i = 0; i < obj.Main.length; ++i) {
var value = obj.Main[i];
var valuePer = (value - firstData.Main) / firstData.Main;
result[0][i] = valuePer;
for (var j = 0; j < obj.Sub.length; ++j) {
var subData = obj.Sub[j];
var subValue = subData[i];
var subValuePer = (subValue - firstData.Sub[j]) / firstData.Sub[j];
result[j + 1][i] = subValuePer;
}
}
return result;
};
RegressionTest.GetPolicyData = function (data) //获取指标数据里面需要计算回测的数据
{
var policyData = { KLineData: null, IndexClose: null, BuyData: null, SellData: null };
policyData.KLineData = data.HistoryData.Data; //个股K线数据,
for (var i in data.OutVar) {
var item = data.OutVar[i];
if (item.Name == 'INDEXCLOSE') {
policyData.IndexClose = item.Data; //绑定大盘收盘数据
} else if (item.Name == 'DRAWICON') //买卖盘BS函数
{
if (item.Draw && item.Draw.Icon) {
if (item.Draw.Icon.ID === 13) policyData.BuyData = item.Draw.DrawData; //买
else if (item.Draw.Icon.ID === 14) policyData.SellData = item.Draw.DrawData; //卖
}
}
}
if (policyData.KLineData && policyData.BuyData && policyData.SellData) return policyData;
return null;
};
/*暴露外部用的方法*/
exports.default = {
RegressionTest: RegressionTest //个股单策略回测
};