步骤 2 : 模仿和排错 步骤 3 : AnnualProfit 步骤 4 : BackTestService 步骤 5 : BackTestController 步骤 6 : view.html 步骤 7 : 图表对应的 html
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
先启动 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);
}
}
获取结果,并返回
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 代码 <div class="label label-warning">收益分布对比图</div> <div class="div4chart" style="margin:0px auto; width:80%"> <canvas class='canvas4AnnualIncome'></canvas> </div>
HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。
![]()
提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
|