umychart.regressiontest.wechat.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /*
  2. Copyright (c) 2018 jones
  3. http://www.apache.org/licenses/LICENSE-2.0
  4. 开源项目 https://github.com/jones2000/HQChart
  5. jones_2000@163.com
  6. 个股指标回测
  7. */
  8. /*
  9. 指标回测
  10. 计算: Trade: {Count 交易次数 Days:交易天数 Success:成功交易次数 Fail:失败交易次数}
  11. Day: {Count:总运行 Max:最长运行 Min:最短运行 Average:平均运行}
  12. Profit: 总收益 StockProfit:个股收益 Excess:超额收益 MaxDropdown:最大回撤 Beta:β(Beta)系数(指标里面需要又大盘数据)
  13. NetValue: [ {Date:日期, Net:净值, Close:股票收盘价, IndexClose:大盘的收盘价}, ]
  14. */
  15. function RegressionTest()
  16. {
  17. //只读数据不能修改
  18. this.HistoryData; //K线数据
  19. this.BuyData; //策略买数据
  20. this.SellData; //策略卖数据
  21. this.IndexClose; //大盘收盘价
  22. this.NetCalculateModel=0; //净值及收益计算模型 0=使用B点开盘价计算 1=使用B点下一天的开盘价计算
  23. this.InitialCapital=10000; //初始资金1W
  24. //计算结果数据
  25. this.Data=new Map(); //key:DATA_NAME value:数据
  26. this.SetPolicyData=function(obj) //设置策略结果的数据 {KLineData:个股K线数据, BuyData:策略买数据, SellData:策略卖数据, IndexClose:大盘收盘价}
  27. {
  28. this.HistoryData=obj.KLineData; //K线数据
  29. this.BuyData=obj.BuyData; //策略买数据
  30. this.SellData=obj.SellData; //策略卖数据
  31. if (obj.IndexClose) this.IndexClose=obj.IndexClose; //大盘收盘价 如果没有大盘数据 就不计算β(Beta)系数 和指数涨幅数据
  32. }
  33. this.ClearData=function() //清空所有的结果数据
  34. {
  35. this.Data=new Map()
  36. }
  37. this.GetBSData=function(startDate) //BS点配对 { B:[{Data:K线数据, Count:天数, NextOpen:下一天的开盘价 }], S:{Data:K线数据}}
  38. {
  39. var index=null;
  40. for(var i=0;i<this.HistoryData.length;++i)
  41. {
  42. var item=this.HistoryData[i];
  43. if (item.Date>=startDate)
  44. {
  45. index=i;
  46. break;
  47. }
  48. }
  49. if (index===null) return null;
  50. console.log(`[RegressionTest::GetBSData] startDate=${startDate} index=${index}`);
  51. var aryBS=[];
  52. var bsItem=null;
  53. for(var i=index;i<this.HistoryData.length;++i)
  54. {
  55. var buyItem=this.BuyData[i];
  56. var sellItem=this.SellData[i];
  57. var kLineItem=this.HistoryData[i];
  58. if (bsItem===null)
  59. {
  60. if (buyItem>0)
  61. {
  62. var bItem={Data:kLineItem, Count:0 };
  63. if (i+1<this.HistoryData.length) bItem.NextOpen=this.HistoryData[i+1].Open; //获取下一天的开盘价
  64. bsItem={ B:[bItem], S:null}; //B可以多个,S一个
  65. }
  66. }
  67. else
  68. {
  69. for(var j in bsItem.B) ++bsItem.B[j].Count; //天数+1
  70. if (buyItem>0)
  71. {
  72. var bItem={Data:kLineItem, Count:0};
  73. if (i+1<this.HistoryData.length) bItem.NextOpen=this.HistoryData[i+1].Open; //获取下一天的开盘价
  74. bsItem.B.push(bItem);
  75. }
  76. else if (sellItem>0)
  77. {
  78. bsItem.S={Data:kLineItem};
  79. aryBS.push(bsItem);
  80. bsItem=null;
  81. }
  82. }
  83. }
  84. var data={StartDate:this.HistoryData[index].Date, StartIndex:index, Count:this.HistoryData.length-index, BSData:aryBS };
  85. console.log('[RegressionTest::GetBSData] data',data);
  86. return data;
  87. }
  88. this.Calculate=function(data)
  89. {
  90. var day={ Count:data.Count, Max:null, Min:null, Average:null }; //Count:总运行 Max:最长运行 Min:最短运行 Average:平均运行
  91. var trade={Count:0, Days:0, Success:0 , Fail:0, SuccessRate:0}; //Count 交易次数 Days:交易天数 Success:成功交易次数 Fail:失败交易次数
  92. for(var i in data.BSData)
  93. {
  94. var item=data.BSData[i];
  95. for(var j in item.B)
  96. {
  97. var bItem=item.B[j];
  98. if (day.Max===null) day.Max=bItem.Count;
  99. else if (day.Max<bItem.Count) day.Max=bItem.Count;
  100. if (day.Min===null) day.Min=bItem.Count;
  101. else if (day.Min>bItem.Count) day.Min=bItem.Count;
  102. ++trade.Count;
  103. trade.Days+=bItem.Count;
  104. if (item.S.Data.Close>bItem.Data.Open) ++trade.Success;
  105. else ++trade.Fail;
  106. }
  107. }
  108. if (trade.Count>0)
  109. {
  110. day.Average=trade.Days/trade.Count;
  111. trade.SuccessRate=trade.Success/trade.Count;
  112. }
  113. //计算收益(总收益)
  114. var profit=1,buyPrice;
  115. for(var i in data.BSData)
  116. {
  117. var item=data.BSData[i];
  118. if (this.NetCalculateModel===1 && item.B[0].NextOpen>0 ) buyPrice=item.B[0].NextOpen;
  119. else buyPrice=item.B[0].Data.Open;
  120. var sellPrice=item.S.Data.Close;
  121. var value=(sellPrice-buyPrice)/buyPrice+1;
  122. profit*=value;
  123. }
  124. profit-=1; //公式:[(1+收益1)*(1+收益2)*(1+收益3)……(1+收益n)-1] x 100%
  125. //标的证券收益
  126. var yClose=this.HistoryData[data.StartIndex].Close; //使用前收盘
  127. var close=this.HistoryData[this.HistoryData.length-1].Close; //最后一个大盘收盘价
  128. var stockProfit=(close-yClose)/yClose;
  129. 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}]`);
  130. var netValue=this.CaclulateNetValue(data);
  131. var maxDropdown=null, beta=null;
  132. if (netValue && netValue.length>0)
  133. {
  134. maxDropdown=this.CaclulateMaxDropdown(netValue);
  135. if (this.IndexClose) beta=this.CaclulateBeta(netValue);
  136. }
  137. //Profit:收益 StockProfit:标的证券收益 Excess:超额收益(加上BS配对的数据)
  138. var result={ Day:day, Trade:trade, Profit:profit, StockProfit:stockProfit, Excess:profit-stockProfit, NetValue:netValue, MaxDropdown:maxDropdown, Beta:beta,BSDataPair:data.BSData};
  139. console.log('[RegressionTest::Calculate] NetCalculateModel, result ',this.NetCalculateModel, result);
  140. return result;
  141. }
  142. this.CaclulateNetValue=function(data) //计算净值
  143. {
  144. var index=data.StartIndex;
  145. var aryDay=[]; //{Close:收盘 , Open:开盘, Position:持仓数量, Cache:现金 , MarketValue:总市值}
  146. var lastDayItem={Position:0, Cache:this.InitialCapital };
  147. var bsItem=null, buyPrice;
  148. for(var i=index;i<this.HistoryData.length;++i)
  149. {
  150. var buyItem=this.BuyData[i];
  151. var sellItem=this.SellData[i];
  152. var kLineItem=this.HistoryData[i];
  153. var dayItem={Date:kLineItem.Date, Position:lastDayItem.Position, Cache:lastDayItem.Cache, Open:kLineItem.Open, Close:kLineItem.Close};
  154. dayItem.MarketValue=dayItem.Position*dayItem.Close+dayItem.Cache; //市值 股票+现金
  155. if (bsItem===null)
  156. {
  157. if (buyItem>0) //买
  158. {
  159. bsItem={ B:{Data:kLineItem}, S:null};
  160. if (this.NetCalculateModel===1 && i+1<this.HistoryData.length && this.HistoryData[i+1].Open>0)
  161. buyPrice=this.HistoryData[i+1].Open; //使用B点下一天的开盘价买
  162. else
  163. buyPrice=dayItem.Open;
  164. let position=parseInt(dayItem.Cache/buyPrice); //开盘价买
  165. let cache=dayItem.Cache-buyPrice*position; //剩余的现金
  166. dayItem.Position=position;
  167. dayItem.Cache=cache;
  168. dayItem.MarketValue=dayItem.Position*dayItem.Close+dayItem.Cache; //市值 股票+现金
  169. }
  170. }
  171. else
  172. {
  173. if (sellItem>0) //卖
  174. {
  175. bsItem.S={Data:kLineItem};
  176. bsItem=null;
  177. let stockValue=dayItem.Position*dayItem.Close; //卖掉的股票钱
  178. dayItem.Position=0;
  179. dayItem.Cache+=stockValue; //卖掉的钱放到现金里面
  180. dayItem.MarketValue=dayItem.Position*dayItem.Close+dayItem.Cache; //市值 股票+现金
  181. }
  182. }
  183. //缓存上一天的数据
  184. lastDayItem.Position=dayItem.Position;
  185. lastDayItem.Cache=dayItem.Cache;
  186. dayItem.Net=dayItem.MarketValue/this.InitialCapital; //净值
  187. if (this.IndexClose) dayItem.IndexClose=this.IndexClose[i]; //指数收盘价
  188. aryDay.push(dayItem);
  189. }
  190. //console.log('[RegressionTest::CaclulateNetValue] aryDay',aryDay);
  191. if (aryDay.length<=0) return [];
  192. var netValue=[]; //净值 {Date:日期, Net:净值, Close:股票收盘价, IndexClose:大盘的收盘价}
  193. for(var i=0;i<aryDay.length;++i)
  194. {
  195. var item=aryDay[i];
  196. var dataItem={ Net:item.Net, Date:item.Date, Close:item.Close };
  197. if (item.IndexClose) dataItem.IndexClose=item.IndexClose;
  198. netValue.push(dataItem);
  199. }
  200. //console.log('[RegressionTest::CaclulateNetValue] netValue',netValue);
  201. return netValue;
  202. }
  203. this.CaclulateMaxDropdown=function(data) //最大回撤
  204. {
  205. var maxNet=data[0].Net; //最大净值
  206. var maxValue=0;
  207. var maxDay;
  208. for(var i=1;i<data.length;++i)
  209. {
  210. var item=data[i];
  211. var value=1-(item.Net/maxNet); //1-策略当日净值 / 当日之前策略最大净值
  212. if (maxValue<value)
  213. {
  214. maxValue=value;
  215. maxDay=item;
  216. }
  217. if (maxNet<item.Net) maxNet=item.Net; //取当前最大的净值
  218. }
  219. console.log('[RegressionTest::CaclulateMaxDropdown] maxDay',maxDay);
  220. return maxValue;
  221. }
  222. this.CaclulateBeta=function(data) //β(Beta)系数,参数是净值数组NetValue
  223. {
  224. var lastItem=data[0]; //上一天的数据
  225. var indexProfit=[]; //大盘涨幅
  226. var bsProfit=[]; //策略涨幅
  227. var indexProfitTotal=0, bsProfitTotal=0;
  228. for(var i=1; i<data.length; ++i)
  229. {
  230. indexProfit[i-1]=0;
  231. bsProfit[i-1]=0;
  232. var item=data[i];
  233. if (item.IndexClose>0 && lastItem.IndexClose>0) indexProfit[i-1]=(item.IndexClose-lastItem.IndexClose)/lastItem.IndexClose;
  234. if (item.Net>0 && lastItem.Net>0) bsProfit[i-1]=(item.Net-lastItem.Net)/lastItem.Net;
  235. //if (item.Close>0 && lastItem.Close>0) bsProfit[i-1]=(item.Close-lastItem.Close)/lastItem.Close;
  236. indexProfitTotal+=indexProfit[i-1];
  237. bsProfitTotal+=bsProfit[i-1];
  238. lastItem=item;
  239. }
  240. var averageIndexProfit=indexProfitTotal/indexProfit.length;
  241. var averageBSProfit=bsProfitTotal/bsProfit.length;
  242. var betaCOV=0; //协方差
  243. for(var i=0;i<indexProfit.length;++i)
  244. {
  245. betaCOV+=(bsProfit[i]-averageBSProfit)*(indexProfit[i]-averageIndexProfit);
  246. }
  247. var betaVAR=0; //标准差:方差的开2次方
  248. for(var i=0;i<indexProfit.length;++i)
  249. {
  250. betaVAR+=(indexProfit[i]-averageIndexProfit)*(indexProfit[i]-averageIndexProfit);
  251. }
  252. return betaCOV/betaVAR;
  253. }
  254. this.Execute=function(obj) //开始计算[ { Name:名字 , Date:起始日期 格式(20180101) }, ....]
  255. {
  256. for(var i in obj )
  257. {
  258. var item=obj[i];
  259. if (this.Data.has(item.Name)) //已经计算过了不计算
  260. {
  261. console.log(`[RegressionTest::Execute] id=${i} Name=${item.Name} Date:${item.Date} is exsit.`);
  262. continue;
  263. }
  264. console.log(`[RegressionTest::Execute] id=${i} Name=${item.Name} Date:${item.Date}`);
  265. var data=this.GetBSData(item.Date);
  266. var result=this.Calculate(data);
  267. this.Data.set(item.Name, result);
  268. }
  269. }
  270. }
  271. //计算叠加数据 (日期必须匹配)
  272. RegressionTest.CaclulateOverlayData=function(obj) //{Main:主数据, Sub:[]//多组叠加数据}
  273. {
  274. if (!obj.Main || !obj.Sub) return null;
  275. var count=obj.Main.length;
  276. for(var i in obj.Sub)
  277. {
  278. var item=obj.Sub[i];
  279. if (item.length!=count)
  280. {
  281. console.log(`[RegressionTest::OverlayData] id=${i} data count not match. MainData count=${count}`);
  282. return null;
  283. }
  284. }
  285. var result=[]; //[0]:主数据 , [1...]:叠加数据
  286. var firstData={Sub:[]};
  287. firstData.Main=obj.Main[0];
  288. result.push([]);
  289. for(var i=0;i<obj.Sub.length;++i)
  290. {
  291. var subData=obj.Sub[i];
  292. firstData.Sub[i]=subData[0];
  293. result.push([]);
  294. }
  295. for(var i=0;i<obj.Main.length;++i)
  296. {
  297. var value=obj.Main[i];
  298. var valuePer=(value-firstData.Main)/firstData.Main;
  299. result[0][i]=valuePer;
  300. for(var j=0;j<obj.Sub.length;++j)
  301. {
  302. var subData=obj.Sub[j];
  303. var subValue=subData[i];
  304. var subValuePer=(subValue-firstData.Sub[j])/firstData.Sub[j];
  305. result[j+1][i]=subValuePer;
  306. }
  307. }
  308. return result;
  309. }
  310. RegressionTest.GetPolicyData=function(data) //获取指标数据里面需要计算回测的数据
  311. {
  312. var policyData={KLineData:null, IndexClose:null, BuyData:null, SellData:null};
  313. policyData.KLineData=data.HistoryData.Data; //个股K线数据,
  314. for(var i in data.OutVar)
  315. {
  316. var item=data.OutVar[i];
  317. if (item.Name=='INDEXCLOSE')
  318. {
  319. policyData.IndexClose=item.Data; //绑定大盘收盘数据
  320. }
  321. else if (item.Name=='DRAWICON') //买卖盘BS函数
  322. {
  323. if (item.Draw && item.Draw.Icon)
  324. {
  325. if (item.Draw.Icon.ID===13) policyData.BuyData=item.Draw.DrawData; //买
  326. else if (item.Draw.Icon.ID===14) policyData.SellData=item.Draw.DrawData; //卖
  327. }
  328. }
  329. }
  330. if (policyData.KLineData && policyData.BuyData && policyData.SellData) return policyData;
  331. return null;
  332. }
  333. //导出
  334. module.exports =
  335. {
  336. JSCommonTest:
  337. {
  338. RegressionTest: RegressionTest, //个股单策略回测
  339. },
  340. };