步骤 2 : 模仿和排错 步骤 3 : 日期需求 步骤 4 : BackTestController 步骤 5 : view.html 步骤 6 : data4Vue 步骤 7 : ajax 参数 步骤 8 : 获取开始和结束日期 步骤 9 : 调用刷新日期 步骤 10 : 两个changeParam函数 步骤 11 : 更新日期函数 步骤 12 : 日期控件的 html 代码 步骤 13 : 日期控件
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
先启动 EurekaServerApplication 跟着启动 IndexCodesApplication 然后启动 IndexDataApplication 接着启动 TrendTradingBackTestServiceApplication 随后启动 TrendTradingBackTestViewApplication 最后启动 IndexZuulServiceApplication 注: 记得运行redis-server.exe 以启动 redis 服务器 然后访问地址: http://127.0.0.1:8031/api-view/ 点击日期控件,就可以获取对应日期范围的数据了。
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。 采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。 推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。 这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来 这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
这个需求是对日期功能的增加,看上去挺简单,其实还略微复杂,需求包括如下内容
1. 当获取到指数数据后,要把对应的开始日期和结束日期拿到并显示在日期控件上 2. 每次切换指数代码,都会更新开始日期和结束日期。 3. 日期能够选择的范围在 开始日期 到 结束日期之间, 超过了就不能选择了。 4. 当选择了新的日期范围的时候,会自动获取对应的数据出来。 5. 对于服务端,如果没有提供开始和结束日期,则返回所有数据。 如果提供了,则返回指定日期范围的对应数据。 6. 开始日期不能大于结束日期
首先修改 BackTestController 类,有一下几个改动
1. 计算出开始日期和结束日期并返回 String indexStartDate = allIndexDatas.get(0).getDate(); String indexEndDate = allIndexDatas.get(allIndexDatas.size()-1).getDate(); result.put("indexStartDate", indexStartDate); result.put("indexEndDate", indexEndDate); 2. 参数可以接受开始日期和结束日期 ,@PathVariable("startDate") String strStartDate ,@PathVariable("endDate") String strEndDate 3. 根据开始日期和结束日期获取对应日期范围的数据 allIndexDatas = filterByDateRange(allIndexDatas,strStartDate, strEndDate); 4. filterByDateRange 方法 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; } package cn.how2j.trend.web;
import cn.how2j.trend.pojo.IndexData;
import cn.how2j.trend.service.BackTestService;
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);
Map<String,Object> result = new HashMap<>();
result.put("indexDatas", allIndexDatas);
result.put("indexStartDate", indexStartDate);
result.put("indexEndDate", indexEndDate);
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;
}
}
view.html 的改动比较多,这里先全部贴出来,后面挨个挨个讲
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="include/header::html('趋势投资模拟回测')" ></head>
<body >
<script>
var chart4Profit = null;
$(function(){
var data4Vue = {
indexes: [],
currentIndex: '000300',
indexDatas:[],
dates:[],
closePoints:[],
flushDate: true,
indexStartDate: null,
indexEndDate: null,
startDate: null,
endDate: null,
};
//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.indexDatas = response.data.indexDatas;
vue.dates = new Array();
vue.closePoints = new Array();
//日期
vue.indexStartDate = response.data.indexStartDate;
vue.indexEndDate = response.data.indexEndDate;
//指数数据
for(i in vue.indexDatas){
var indexData = vue.indexDatas[i];
vue.dates.push(indexData.date);
vue.closePoints.push(indexData.closePoint);
}
//收益图表
chart4Profit.config.data.labels = vue.dates;
chart4Profit.config.data.datasets[0].label = vue.currentIndex;
chart4Profit.config.data.datasets[0].data = vue.closePoints;
chart4Profit.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,
}
]
},
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;
}
}
}
}
});
});
</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>
<div th:replace="include/footer::html" ></div>
</body>
</html>
1. 增加一个 flushDate 变量,用于判断是否要更新日期。
为什么要这个呢? 现在其实有两种获取数据需求,一种是既获取指数数据,也要获取日期数据,另一个种是仅仅获取指数数据. 比如切换指数代码,就两则都要。 而调整日期,则不需要获取日期,而是仅仅获取指数数据。 2. 日期对象 indexStartDate和indexEndDate 表示当前指数的开始日期和结束日期。 startDate 和 endDate 表示作为参数发到服务端的时候,希望获取的日期范围。 flushDate: true,
indexStartDate: null,
indexEndDate: null,
startDate: null,
endDate: null,
flushDate: true, indexStartDate: null, indexEndDate: null, startDate: null, endDate: null,
把现在和以前做了下对比,请求条件里面增加了开始日期和结束日期
以前的:
var url = "http://127.0.0.1:8031/api-backtest/simulate/"+vue.currentIndex;
现在的:
var url = "http://127.0.0.1:8031/api-backtest/simulate/"+vue.currentIndex+"/"+vue.startDate+"/"+vue.endDate+"/";
以前的: var url = "http://127.0.0.1:8031/api-backtest/simulate/"+vue.currentIndex; 现在的: var url = "http://127.0.0.1:8031/api-backtest/simulate/"+vue.currentIndex+"/"+vue.startDate+"/"+vue.endDate+"/"; //日期
vue.indexStartDate = response.data.indexStartDate;
vue.indexEndDate = response.data.indexEndDate;
//日期 vue.indexStartDate = response.data.indexStartDate; vue.indexEndDate = response.data.indexEndDate;
在simulate 函数中增加如下代码。
表示如果flushDate 是true,就会刷新日期,默认是 true, 即一开始就会刷新日期 if(vue.flushDate)
vue.updateDate();
if(vue.flushDate) vue.updateDate();
两个changeParam, 分别在需要刷新日期,和不需要刷新日期的时候调用
changeParam:function(){
vue.flushDate = false;
vue.simulate();
},
changeParamWithFlushDate:function(){
vue.flushDate = true;
vue.startDate = null;
vue.endDate = null;
vue.simulate();
},
changeParam:function(){ vue.flushDate = false; vue.simulate(); }, changeParamWithFlushDate:function(){ vue.flushDate = true; vue.startDate = null; vue.endDate = null; vue.simulate(); },
这里其实是两个函数,一个是更新日期函数,另一个是更新日期函数里面会调用的 checkDateRange 用于判断开始日期是否超过了结束日期的函数。
我们主要来讲解:updateDate 函数。 1. 用于发送给服务端的开始和结束日期默认就是最开始和最晚的日期。 vue.startDate = vue.indexStartDate; vue.endDate = vue.indexEndDate; 2. 需要先destroy,否则后续新的日期范围如果超出了前面的日期范围,会出冲突 $('#date4Start').datepicker("destroy"); 3. 初始化日期空间,设置了格式,语言,是否自动关闭,日期范围 $('#date4Start').datepicker({ "format": 'yyyy-mm-dd', "language": "zh-CN", autoclose: true, startDate: vue.indexStartDate, endDate: vue.indexEndDate, }) 4. 点击后的相应,通过空间获取到的是 e.date 日期对象,不好用,我们要把它转换为 yyyy-mm-dd 格式放在 vue.startDate 上。 与此同时还要判断日期范围,最后再调用 changeParam 函数。 而 changeParam 函数在两个changeParam函数 中有讲解,是不会要求服务端再次返回日期信息的。 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(); }); 5. 结束日期与上同理不赘述。 6. checkDateRange 函数用于判断开始日期是否超过结束日期了,其做法获取开始和技术的毫秒数: getTime(),看看谁大谁小。 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; } 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;
}
放在 4个 td里, 使用的id分别是 date4Start,date4End。
<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>
日期控件用的是 bootstrap-datepicker, 站长还没来得及做这个的教程。大家可以去官网了解其用法:
https://bootstrap-datepicker.readthedocs.io/en/latest/
HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。
![]()
提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
|