how2j.cn

步骤 1 : 先运行,看到效果,再学习   
步骤 2 : 模仿和排错   
步骤 3 : AnnualProfit   
步骤 4 : BackTestService   
步骤 5 : BackTestController   
步骤 6 : view.html   
步骤 7 : 图表对应的 html   

步骤 1 :

先运行,看到效果,再学习

edit
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
先启动 EurekaServerApplication
跟着启动 IndexCodesApplication
然后启动 IndexDataApplication
接着启动 TrendTradingBackTestServiceApplication
随后启动 TrendTradingBackTestViewApplication
最后启动 IndexZuulServiceApplication
注: 记得运行redis-server.exe 以启动 redis 服务器
然后访问地址:

http://127.0.0.1:8031/api-view/

不同年份的收益图。 可以看到这么一个特点,虽然大的行情趋势投资的收益不如一直拿着指数,但是当发生下跌的收,趋势投资的跌幅会小很多。
先运行,看到效果,再学习
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。

推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
每年收益实体类,其中的字段有: 年份,指数收益,趋势收益。
package cn.how2j.trend.pojo; public class AnnualProfit { private int year; private float indexIncome; private float trendIncome; public int getYear() { return year; } public void setYear(int year) { this.year = year; } public float getIndexIncome() { return indexIncome; } public void setIndexIncome(float indexIncome) { this.indexIncome = indexIncome; } public float getTrendIncome() { return trendIncome; } public void setTrendIncome(float trendIncome) { this.trendIncome = trendIncome; } @Override public String toString() { return "AnnualProfit [year=" + year + ", indexIncome=" + indexIncome + ", trendIncome=" + trendIncome + "]"; } }
1. 增加一个 getYear 方法, 获取某个日期如 2019-05-21 里的年份:

private int getYear(String date) {
String strYear= StrUtil.subBefore(date, "-", false);
return Convert.toInt(strYear);
}

2. 计算某一年的的指数收益

private float getIndexIncome(int year, List<IndexData> indexDatas) {
IndexData first=null;
IndexData last=null;

for (IndexData indexData : indexDatas) {
String strDate = indexData.getDate();
// Date date = DateUtil.parse(strDate);
int currentYear = getYear(strDate);

if(currentYear == year) {
if(null==first)
first = indexData;
last = indexData;
}
}
return (last.getClosePoint() - first.getClosePoint()) / first.getClosePoint();
}

3. 计算某一年的趋势投资收益

private float getTrendIncome(int year, List<Profit> profits) {
Profit first=null;
Profit last=null;

for (Profit profit : profits) {
String strDate = profit.getDate();
int currentYear = getYear(strDate);

if(currentYear == year) {
if(null==first)
first = profit;
last = profit;
}
if(currentYear > year)
break;
}
return (last.getValue() - first.getValue()) / first.getValue();
}

4. 计算完整时间范围内,每一年的指数投资收益和趋势投资收益

private List<AnnualProfit> caculateAnnualProfits(List<IndexData> indexDatas, List<Profit> profits) {
List<AnnualProfit> result = new ArrayList<>();
String strStartDate = indexDatas.get(0).getDate();
String strEndDate = indexDatas.get(indexDatas.size()-1).getDate();

Date startDate = DateUtil.parse(strStartDate);
Date endDate = DateUtil.parse(strEndDate);

int startYear = DateUtil.year(startDate);
int endYear = DateUtil.year(endDate);

for (int year =startYear; year <= endYear; year++) {
AnnualProfit annualProfit = new AnnualProfit();
annualProfit.setYear(year);

float indexIncome = getIndexIncome(year,indexDatas);
float trendIncome = getTrendIncome(year,profits);
annualProfit.setIndexIncome(indexIncome);
annualProfit.setTrendIncome(trendIncome);
result.add(annualProfit);

}
return result;
}

5. 调用上面的方法,并把结果返回

List<AnnualProfit> annualProfits = caculateAnnualProfits(indexDatas, profits);
map.put("annualProfits", annualProfits);
package cn.how2j.trend.service; import cn.how2j.trend.client.IndexDataClient; import cn.how2j.trend.pojo.AnnualProfit; import cn.how2j.trend.pojo.IndexData; import cn.how2j.trend.pojo.Profit; import cn.how2j.trend.pojo.Trade; import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; @Service public class BackTestService { @Autowired IndexDataClient indexDataClient; public List<IndexData> listIndexData(String code){ List<IndexData> result = indexDataClient.getIndexData(code); Collections.reverse(result); // for (IndexData indexData : result) { // System.out.println(indexData.getDate()); // } return result; } public Map<String,Object> simulate(int ma, float sellRate, float buyRate, float serviceCharge, List<IndexData> indexDatas) { List<Profit> profits =new ArrayList<>(); List<Trade> trades = new ArrayList<>(); float initCash = 1000; float cash = initCash; float share = 0; float value = 0; int winCount = 0; float totalWinRate = 0; float avgWinRate = 0; float totalLossRate = 0; int lossCount = 0; float avgLossRate = 0; float init =0; if(!indexDatas.isEmpty()) init = indexDatas.get(0).getClosePoint(); for (int i = 0; i<indexDatas.size() ; i++) { IndexData indexData = indexDatas.get(i); float closePoint = indexData.getClosePoint(); float avg = getMA(i,ma,indexDatas); float max = getMax(i,ma,indexDatas); float increase_rate = closePoint/avg; float decrease_rate = closePoint/max; if(avg!=0) { //buy 超过了均线 if(increase_rate>buyRate ) { //如果没买 if(0 == share) { share = cash / closePoint; cash = 0; Trade trade = new Trade(); trade.setBuyDate(indexData.getDate()); trade.setBuyClosePoint(indexData.getClosePoint()); trade.setSellDate("n/a"); trade.setSellClosePoint(0); trades.add(trade); } } //sell 低于了卖点 else if(decrease_rate<sellRate ) { //如果没卖 if(0!= share){ cash = closePoint * share * (1-serviceCharge); share = 0; Trade trade =trades.get(trades.size()-1); trade.setSellDate(indexData.getDate()); trade.setSellClosePoint(indexData.getClosePoint()); float rate = cash / initCash; trade.setRate(rate); if(trade.getSellClosePoint()-trade.getBuyClosePoint()>0) { totalWinRate +=(trade.getSellClosePoint()-trade.getBuyClosePoint())/trade.getBuyClosePoint(); winCount++; } else { totalLossRate +=(trade.getSellClosePoint()-trade.getBuyClosePoint())/trade.getBuyClosePoint(); lossCount ++; } } } //do nothing else{ } } if(share!=0) { value = closePoint * share; } else { value = cash; } float rate = value/initCash; Profit profit = new Profit(); profit.setDate(indexData.getDate()); profit.setValue(rate*init); profits.add(profit); } avgWinRate = totalWinRate / winCount; avgLossRate = totalLossRate / lossCount; List<AnnualProfit> annualProfits = caculateAnnualProfits(indexDatas, profits); Map<String,Object> map = new HashMap<>(); map.put("profits", profits); map.put("trades", trades); map.put("winCount", winCount); map.put("lossCount", lossCount); map.put("avgWinRate", avgWinRate); map.put("avgLossRate", avgLossRate); map.put("annualProfits", annualProfits); return map; } private static float getMax(int i, int day, List<IndexData> list) { int start = i-1-day; if(start<0) start = 0; int now = i-1; if(start<0) return 0; float max = 0; for (int j = start; j < now; j++) { IndexData bean =list.get(j); if(bean.getClosePoint()>max) { max = bean.getClosePoint(); } } return max; } private static float getMA(int i, int ma, List<IndexData> list) { int start = i-1-ma; int now = i-1; if(start<0) return 0; float sum = 0; float avg = 0; for (int j = start; j < now; j++) { IndexData bean =list.get(j); sum += bean.getClosePoint(); } avg = sum / (now - start); return avg; } public float getYear(List<IndexData> allIndexDatas) { float years; String sDateStart = allIndexDatas.get(0).getDate(); String sDateEnd = allIndexDatas.get(allIndexDatas.size()-1).getDate(); Date dateStart = DateUtil.parse(sDateStart); Date dateEnd = DateUtil.parse(sDateEnd); long days = DateUtil.between(dateStart, dateEnd, DateUnit.DAY); years = days/365f; return years; } private List<AnnualProfit> caculateAnnualProfits(List<IndexData> indexDatas, List<Profit> profits) { List<AnnualProfit> result = new ArrayList<>(); String strStartDate = indexDatas.get(0).getDate(); String strEndDate = indexDatas.get(indexDatas.size()-1).getDate(); Date startDate = DateUtil.parse(strStartDate); Date endDate = DateUtil.parse(strEndDate); int startYear = DateUtil.year(startDate); int endYear = DateUtil.year(endDate); for (int year =startYear; year <= endYear; year++) { AnnualProfit annualProfit = new AnnualProfit(); annualProfit.setYear(year); float indexIncome = getIndexIncome(year,indexDatas); float trendIncome = getTrendIncome(year,profits); annualProfit.setIndexIncome(indexIncome); annualProfit.setTrendIncome(trendIncome); result.add(annualProfit); } return result; } private float getIndexIncome(int year, List<IndexData> indexDatas) { IndexData first=null; IndexData last=null; for (IndexData indexData : indexDatas) { String strDate = indexData.getDate(); // Date date = DateUtil.parse(strDate); int currentYear = getYear(strDate); if(currentYear == year) { if(null==first) first = indexData; last = indexData; } } return (last.getClosePoint() - first.getClosePoint()) / first.getClosePoint(); } private float getTrendIncome(int year, List<Profit> profits) { Profit first=null; Profit last=null; for (Profit profit : profits) { String strDate = profit.getDate(); int currentYear = getYear(strDate); if(currentYear == year) { if(null==first) first = profit; last = profit; } if(currentYear > year) break; } return (last.getValue() - first.getValue()) / first.getValue(); } private int getYear(String date) { String strYear= StrUtil.subBefore(date, "-", false); return Convert.toInt(strYear); } }
步骤 5 :

BackTestController

edit
获取结果,并返回

List<AnnualProfit> annualProfits = (List<AnnualProfit>) simulateResult.get("annualProfits");
result.put("annualProfits", annualProfits);
package cn.how2j.trend.web; import cn.how2j.trend.pojo.AnnualProfit; import cn.how2j.trend.pojo.IndexData; import cn.how2j.trend.pojo.Profit; import cn.how2j.trend.pojo.Trade; import cn.how2j.trend.service.BackTestService; import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.*; @RestController public class BackTestController { @Autowired BackTestService backTestService; @GetMapping("/simulate/{code}/{startDate}/{endDate}") @CrossOrigin public Map<String,Object> backTest( @PathVariable("code") String code ,@PathVariable("startDate") String strStartDate ,@PathVariable("endDate") String strEndDate ) throws Exception { List<IndexData> allIndexDatas = backTestService.listIndexData(code); String indexStartDate = allIndexDatas.get(0).getDate(); String indexEndDate = allIndexDatas.get(allIndexDatas.size()-1).getDate(); allIndexDatas = filterByDateRange(allIndexDatas,strStartDate, strEndDate); int ma = 20; float sellRate = 0.95f; float buyRate = 1.05f; float serviceCharge = 0f; Map<String,?> simulateResult= backTestService.simulate(ma,sellRate, buyRate,serviceCharge, allIndexDatas); List<Profit> profits = (List<Profit>) simulateResult.get("profits"); List<Trade> trades = (List<Trade>) simulateResult.get("trades"); float years = backTestService.getYear(allIndexDatas); float indexIncomeTotal = (allIndexDatas.get(allIndexDatas.size()-1).getClosePoint() - allIndexDatas.get(0).getClosePoint()) / allIndexDatas.get(0).getClosePoint(); float indexIncomeAnnual = (float) Math.pow(1+indexIncomeTotal, 1/years) - 1; float trendIncomeTotal = (profits.get(profits.size()-1).getValue() - profits.get(0).getValue()) / profits.get(0).getValue(); float trendIncomeAnnual = (float) Math.pow(1+trendIncomeTotal, 1/years) - 1; int winCount = (Integer) simulateResult.get("winCount"); int lossCount = (Integer) simulateResult.get("lossCount"); float avgWinRate = (Float) simulateResult.get("avgWinRate"); float avgLossRate = (Float) simulateResult.get("avgLossRate"); List<AnnualProfit> annualProfits = (List<AnnualProfit>) simulateResult.get("annualProfits"); Map<String,Object> result = new HashMap<>(); result.put("indexDatas", allIndexDatas); result.put("indexStartDate", indexStartDate); result.put("indexEndDate", indexEndDate); result.put("profits", profits); result.put("trades", trades); result.put("years", years); result.put("indexIncomeTotal", indexIncomeTotal); result.put("indexIncomeAnnual", indexIncomeAnnual); result.put("trendIncomeTotal", trendIncomeTotal); result.put("trendIncomeAnnual", trendIncomeAnnual); result.put("winCount", winCount); result.put("lossCount", lossCount); result.put("avgWinRate", avgWinRate); result.put("avgLossRate", avgLossRate); result.put("annualProfits", annualProfits); return result; } private List<IndexData> filterByDateRange(List<IndexData> allIndexDatas, String strStartDate, String strEndDate) { if(StrUtil.isBlankOrUndefined(strStartDate) || StrUtil.isBlankOrUndefined(strEndDate) ) return allIndexDatas; List<IndexData> result = new ArrayList<>(); Date startDate = DateUtil.parse(strStartDate); Date endDate = DateUtil.parse(strEndDate); for (IndexData indexData : allIndexDatas) { Date date =DateUtil.parse(indexData.getDate()); if( date.getTime()>=startDate.getTime() && date.getTime()<=endDate.getTime() ) { result.add(indexData); } } return result; } }
1. 准备4个数据
annualProfits 就是从服务器返回的每年收益数组
annuals 年数组,图表里的横轴
indexIncomes 指数投资收益数组,图表里的纵轴
trendIncomes 趋势投资收益数组,图表里的纵轴

annualProfits: [],
annuals:[],
indexIncomes:[],
trendIncomes:[],

2. 清除数据

vue.annualProfits = [];
vue.annuals = [];
vue.indexIncomes = [];
vue.trendIncomes = [];

3. 获取数据

vue.annualProfits = response.data.annualProfits;

4. 计算出图表需要的横纵和纵轴数据

for(i in vue.annualProfits){
vue.annuals.push(vue.annualProfits[i].year);
vue.indexIncomes.push(vue.annualProfits[i].indexIncome*100);
vue.trendIncomes.push(vue.annualProfits[i].trendIncome*100);
}

5. 每年收益图表更新

chart4AnnualIncome.config.data.labels = vue.annuals;
chart4AnnualIncome.config.data.datasets[0].label = vue.currentIndex;
chart4AnnualIncome.config.data.datasets[0].data = vue.indexIncomes;
chart4AnnualIncome.config.data.datasets[1].data = vue.trendIncomes;
chart4AnnualIncome.update();

6. 配置图表,和收益图表差不多,不过类型是 条状图

var ctx4AnnualIncome = $(".canvas4AnnualIncome")[0].getContext('2d');
chart4AnnualIncome = new Chart(ctx4AnnualIncome, {
type: 'bar',
data: {
labels: '',
datasets:
data: [
,
borderColor: '#FF4040',
backgroundColor: '#FF4040',
borderWidth: 1,
pointRadius: 0,
fill: false,
lineTension: 0,
},
{
label: '趋势投资',
data: [],
borderColor: '#5D98C8',
backgroundColor: '#5D98C8',
borderWidth: 1,
pointRadius: 0,
fill: false,
lineTension: 0,
}
]
},
options: {
title: {
display: true,
text: '指数/趋势收益分布对比图'
},
responsive: true,
responsiveAnimationDuration:3000,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
// suggestedMin: -10,
// suggestedMax: 200,
}
}]
},
tooltips: {
intersect: false,
mode: 'index',

callbacks: {
label: function(tooltipItem, myData) {
var label = myData.datasets[tooltipItem.datasetIndex].label || '';
if (label) {
label += ': ';
}
label += parseFloat(tooltipItem.value).toFixed(2);
label += "%";
return label;
}
}
}
}
});
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head th:include="include/header::html('趋势投资模拟回测')" ></head> <body > <script> var chart4Profit = null; var chart4AnnualIncome = null; $(function(){ var data4Vue = { indexes: [], currentIndex: '000300', indexDatas:[], dates:[], closePoints:[], flushDate: true, indexStartDate: null, indexEndDate: null, startDate: null, endDate: null, profits:[], profitValues:[], trades:[], years:0, indexIncomeTotal:0, indexIncomeAnnual:0, trendIncomeTotal:0, trendIncomeAnnual:0, winCount:0, lossCount:0, avgWinRate:0, avgLossRate:0, annualProfits: [], annuals:[], indexIncomes:[], trendIncomes:[], }; //ViewModel var vue = new Vue({ el: '#workingArea', data: data4Vue, mounted:function(){ //mounted 表示这个 Vue 对象加载成功了 this.init(); $("[data-toggle='tooltip']").tooltip(); }, methods: { init:function(){ var url = "http://127.0.0.1:8031/api-codes/codes"; axios.get(url).then(function(response) { vue.indexes = response.data; vue.$nextTick(function(){ vue.simulate(); }); }); }, simulate:function(){ var url = "http://127.0.0.1:8031/api-backtest/simulate/"+vue.currentIndex+"/"+vue.startDate+"/"+vue.endDate+"/"; axios.get(url).then(function(response) { //清空原数据 vue.indexDatas = []; vue.closePoints = []; vue.dates = []; vue.profits = []; vue.profitValues =[]; vue.trades = []; vue.annualProfits = []; vue.annuals = []; vue.indexIncomes = []; vue.trendIncomes = []; //获取返回数据 vue.indexDatas = response.data.indexDatas; vue.dates = new Array(); vue.closePoints = new Array(); //日期 vue.indexStartDate = response.data.indexStartDate; vue.indexEndDate = response.data.indexEndDate; //收益 vue.profits = response.data.profits; //交易明细 vue.trades = response.data.trades; //收益一览 vue.years = response.data.years; vue.indexIncomeTotal = response.data.indexIncomeTotal; vue.indexIncomeAnnual = response.data.indexIncomeAnnual; vue.trendIncomeTotal = response.data.trendIncomeTotal; vue.trendIncomeAnnual = response.data.trendIncomeAnnual; //交易统计 vue.winCount = response.data.winCount; vue.lossCount = response.data.lossCount; vue.avgWinRate = response.data.avgWinRate; vue.avgLossRate = response.data.avgLossRate; //每年收益 vue.annualProfits = response.data.annualProfits; //指数数据 for(i in vue.indexDatas){ var indexData = vue.indexDatas[i]; vue.dates.push(indexData.date); vue.closePoints.push(indexData.closePoint); var profit = vue.profits[i]; vue.profitValues.push(profit.value); } for(i in vue.annualProfits){ vue.annuals.push(vue.annualProfits[i].year); vue.indexIncomes.push(vue.annualProfits[i].indexIncome*100); vue.trendIncomes.push(vue.annualProfits[i].trendIncome*100); } //收益图表 chart4Profit.config.data.labels = vue.dates; chart4Profit.config.data.datasets[0].label = vue.currentIndex; chart4Profit.config.data.datasets[0].data = vue.closePoints; chart4Profit.config.data.datasets[1].data = vue.profitValues; chart4Profit.update(); chart4AnnualIncome.config.data.labels = vue.annuals; chart4AnnualIncome.config.data.datasets[0].label = vue.currentIndex; chart4AnnualIncome.config.data.datasets[0].data = vue.indexIncomes; chart4AnnualIncome.config.data.datasets[1].data = vue.trendIncomes; chart4AnnualIncome.update(); if(vue.flushDate) vue.updateDate(); }); }, changeParam:function(){ vue.flushDate = false; vue.simulate(); }, changeParamWithFlushDate:function(){ vue.flushDate = true; vue.startDate = null; vue.endDate = null; vue.simulate(); }, updateDate:function(){ vue.startDate = vue.indexStartDate; vue.endDate = vue.indexEndDate; console.log("vue.indexStartDate:"+vue.indexStartDate); //需要先destroy,否则后续新的日期范围如果超出了前面的日期范围,会出冲突 $('#date4Start').datepicker("destroy"); $('#date4Start').datepicker({ "format": 'yyyy-mm-dd', "language": "zh-CN", autoclose: true, startDate: vue.indexStartDate, endDate: vue.indexEndDate, }).on("changeDate",function(e){ var month = (e.date.getMonth()+1); if(month <10) month = '0'+month; var day = (e.date.getDate()); if(day <10) day = '0'+day; vue.startDate = e.date.getFullYear()+"-"+month+"-"+day; if(!vue.checkDateRange()){ $('#date4Start').datepicker('update', vue.indexStartDate); return; } vue.changeParam(); }); $('#date4End').datepicker("destroy"); $('#date4End').datepicker({ "format": 'yyyy-mm-dd', "language": "zh-CN", autoclose: true, startDate: vue.indexStartDate, endDate: vue.indexEndDate, }).on("changeDate",function(e){ var month = (e.date.getMonth()+1); if(month <10) month = '0'+month; var day = (e.date.getDate()); if(day <10) day = '0'+day; vue.endDate = e.date.getFullYear()+"-"+month+"-"+day; if(!vue.checkDateRange()){ $('#date4End').datepicker('update', vue.indexEndDate); return; } vue.changeParam(); }); $('#date4Start').datepicker('update', vue.indexStartDate); $('#date4End').datepicker('update', vue.indexEndDate); }, checkDateRange:function(){ if(null==vue.startDate || null==vue.endDate) return true; var strStartDate = vue.startDate.replace(/-/g, '/') var startTime = new Date(strStartDate).getTime(); var strEndDate = vue.endDate.replace(/-/g, '/') var endTime = new Date(strEndDate).getTime(); if(startTime>endTime){ alert("开始日期不能大于日期!"); return false; } return true; } } }); var ctx4Profit = $(".canvas4Profit")[0].getContext('2d'); chart4Profit = new Chart(ctx4Profit, { type: 'line', data: { labels: '', datasets: [ { label: '', data: [], borderColor: '#FF4040', backgroundColor: '#FF4040', borderWidth: 1.2, pointRadius: 0, fill: false, lineTension: 0, }, { label: '趋势投资', data: [], borderColor: '#5D98C8', backgroundColor: '#5D98C8', borderWidth: 1.2, pointRadius: 0, fill: false, lineTension: 0, } ] }, options: { title: { display: true, text: '指数趋势投资收益对比图' }, responsive: true, responsiveAnimationDuration:3000, scales: { yAxes: [{ ticks: { beginAtZero: false, } }] }, tooltips: { intersect: false, mode: 'index', // axis: 'y', callbacks: { label: function(tooltipItem, myData) { var label = myData.datasets[tooltipItem.datasetIndex].label || ''; if (label) { label += ': '; } label += parseFloat(tooltipItem.value).toFixed(2); return label; } } } } }); var ctx4AnnualIncome = $(".canvas4AnnualIncome")[0].getContext('2d'); chart4AnnualIncome = new Chart(ctx4AnnualIncome, { type: 'bar', data: { labels: '', datasets: [ { label: '', data: [], borderColor: '#FF4040', backgroundColor: '#FF4040', borderWidth: 1, pointRadius: 0, fill: false, lineTension: 0, }, { label: '趋势投资', data: [], borderColor: '#5D98C8', backgroundColor: '#5D98C8', borderWidth: 1, pointRadius: 0, fill: false, lineTension: 0, } ] }, options: { title: { display: true, text: '指数/趋势收益分布对比图' }, responsive: true, responsiveAnimationDuration:3000, scales: { yAxes: [{ ticks: { beginAtZero: true, // suggestedMin: -10, // suggestedMax: 200, } }] }, tooltips: { intersect: false, mode: 'index', callbacks: { label: function(tooltipItem, myData) { var label = myData.datasets[tooltipItem.datasetIndex].label || ''; if (label) { label += ': '; } label += parseFloat(tooltipItem.value).toFixed(2); label += "%"; return label; } } } } }); }); </script> <style> table.inputTable{ width:100%; } table.inputTable td{ padding:20px 20px; } table{ margin:20px; } div#workingArea{ margin:50px; } </style> <div id="workingArea"> <span class="label label-info">回测参数</span> <table class="inputTable "> <tr> <td width="25%"> <span data-toggle="tooltip" data-placement="top" title="选择某一个指数进行模拟回测"> 请选择指数:<span class="glyphicon glyphicon-question-sign" > </span> </span> </td> <td width="25%"> <select @change="changeParamWithFlushDate" v-model="currentIndex" class="indexSelect form-control"> <option v-for="bean in indexes " :value="bean.code">{{bean.name}} - ( {{bean.code}} )</option> </select> </td> <td width="25%"></td> <td width="25%"></td> </tr> <tr> <td> <span data-toggle="tooltip" data-placement="top" title="指定模拟回测的开始日期,默认是当前指数最开始的日期"> 开始日期:<span class="glyphicon glyphicon-question-sign " > </span> </span> </td> <td> <div class="form-group"> <div class="input-group date" id="date4Start"> <input type="text" readOnly="readOnly" class="form-control" ><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span> </div> </div> </td> <td> <span data-toggle="tooltip" data-placement="top" title="指定模拟回测的结束日期,默认是当前指数最后的日期"> 结束日期:<span class="glyphicon glyphicon-question-sign " > </span> </span> </td> <td> <div class="form-group"> <div class="input-group date" id="date4End"> <input type="text" readOnly="readOnly" class="form-control" ><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span> </div> </div> </td> </tr> </table> <div class="label label-warning">收益对比图</div> <div class="div4chart" style="margin:0px auto; width:80%"> <canvas class='canvas4Profit'></canvas> </div> <div class="label label-warning">收益一览</div> <table class="table table-striped table-bordered table-condensed" > <thead> <th>投资类型</th> <th>投资时长 (年)</th> <th>1000元投资收益</th> <th>总收益率</th> <th>年化收益率</th> </thead> <tbody> <tr> <td>指数投资</td> <td>{{years|formatNumberFilter(2)}} </td> <td>{{(indexIncomeTotal+1)*1000|formatMoneyFilter}}</td> <td>{{indexIncomeTotal*100|formatNumberFilter(2)}}%</td> <td>{{indexIncomeAnnual*100|formatNumberFilter(2)}}%</td> </tr> <tr> <td>趋势投资</td> <td>{{years|formatNumberFilter(2)}} </td> <td>{{(trendIncomeTotal+1)*1000|formatMoneyFilter}}</td> <td>{{trendIncomeTotal*100|formatNumberFilter(2)}}%</td> <td>{{trendIncomeAnnual*100|formatNumberFilter(2)}}%</td> </tr> <tr> <td>相对收益</td> <td>n/a</td> <td>{{(trendIncomeTotal-indexIncomeTotal)*1000|formatMoneyFilter}}</td> <td>{{(trendIncomeTotal-indexIncomeTotal)*100|formatNumberFilter(2)}}%</td> <td>{{(trendIncomeAnnual-indexIncomeAnnual)*100|formatNumberFilter(2)}}%</td> </tr> </tbody> </table> <div class="label label-warning">交易统计</div> <table class="table table-bordered table-condensed" > <thead> <th align="center" colspan="2">趋势投资盈亏统计</th> </thead> <tbody> <tr> <td width="50%">总共交易次数</td> <td>{{winCount+lossCount}}</td> </tr> <tr> <td>盈利交易次数</td> <td>{{winCount}}</td> </tr> <tr> <td>平均盈利比率</td> <td>{{avgWinRate*100|formatNumberFilter(2)}}%</td> </tr> <tr> <td>亏损交易次数</td> <td>{{lossCount}}</td> </tr> <tr> <td>平均亏损比率</td> <td>{{avgLossRate*100|formatNumberFilter(2)}}%</td> </tr> <tr> <td>胜率</td> <td>{{(winCount/(winCount+lossCount))*100|formatNumberFilter(2)}}% </td> </tr> </tbody> </table> <div class="label label-warning">收益分布对比图</div> <div class="div4chart" style="margin:0px auto; width:80%"> <canvas class='canvas4AnnualIncome'></canvas> </div> <div class="label label-warning">交易明细</div> <table class="table table-striped table-bordered table-condensed table-hover" > <thead> <th>盈/亏</th> <th>购买日期</th> <th>购买盘点</th> <th>出售日期</th> <th>出售盘点</th> <th>盈亏比率</th> <th>1000元投资收益</th> </thead> <tbody> <tr v-for="bean in trades"> <td> <span v-if="bean.sellClosePoint>bean.buyClosePoint" class="label label-danger">盈利</span> <span v-if="bean.sellClosePoint<=bean.buyClosePoint" class="label label-success">亏损</span> </td> <td>{{bean.buyDate}}</td> <td>{{bean.buyClosePoint}}</td> <td>{{bean.sellDate}}</td> <td> <span v-if="bean.sellClosePoint==0">n/a</span> <span v-if="bean.sellClosePoint!=0">{{bean.sellClosePoint}}</span> </td> <td> <span v-if="bean.sellClosePoint==0">n/a</span> <span class="label" v-bind:class="{ 'label-danger' : bean.sellClosePoint>bean.buyClosePoint, 'label-success' : bean.sellClosePoint<=bean.buyClosePoint }" v-if="bean.sellClosePoint!=0">{{(bean.sellClosePoint-bean.buyClosePoint)*100/bean.buyClosePoint | formatNumberFilter(2)}}%</span> </td> <td> <span v-if="bean.sellClosePoint==0">n/a</span> <span v-if="bean.sellClosePoint!=0">{{bean.rate*1000 | formatMoneyFilter }}</span> </td> </tr> </tbody> </table> </div> <div th:replace="include/footer::html" ></div> </body> </html>
步骤 7 :

图表对应的 html

edit
接着上个步骤,上面写不下了,所以只好写道这里来了。。。
7. 图表需要的html 代码

<div class="label label-warning">收益分布对比图</div>

<div class="div4chart" style="margin:0px auto; width:80%">
<canvas class='canvas4AnnualIncome'></canvas>
</div>


HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。


提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
关于 回收站-------不要的------模拟回测-收益分布对比图 的提问

尽量提供截图代码异常信息,有助于分析和解决问题。 也可进本站QQ群交流: 578362961
提问尽量提供完整的代码,环境描述,越是有利于问题的重现,您的问题越能更快得到解答。
对教程中代码有疑问,请提供是哪个步骤,哪一行有疑问,这样便于快速定位问题,提高问题得到解答的速度
在已经存在的几千个提问里,有相当大的比例,是因为使用了和站长不同版本的开发环境导致的,比如 jdk, eclpise, idea, mysql,tomcat 等等软件的版本不一致。
请使用和站长一样的版本,可以节约自己大量的学习时间。 站长把教学中用的软件版本整理了,都统一放在了这里, 方便大家下载: https://how2j.cn/k/helloworld/helloworld-version/1718.html

上传截图