摘要 这是在绘制股票交易K线图时遇到的一个问题,有关问题的优化方案,与一些具体的实现
方法,以及在这个过程中的心得。
关键词JFreeChart、SegmentTimeline
问题
在绘制股票交易K线图时,遇到如何去除停牌日期的交易数据问题。此问题涉及到两个方面,
周六周日股票停牌无交易日期和特殊假日股票停牌无交易日期。对于周末这个固定的休息日,股
票是不存在交易数据的,即停牌,在K线图上显示的话就是空白。同样,对于特殊的时间节点
也不存在交易数据,在K线图上也是空白。因此在绘制K线图时,为了保证数据显示的连续性
和美化视角,我们要将这些特殊的日期的横轴坐标从原数据坐标中去掉,或者说隐藏不显示。
现状分析
在设置K线图的X轴时间线性集合时没有将异常交易日期(即停牌缺口)从时间线性集合
中去除掉。要想实现停牌日期缺口优化,就要设法将缺口日期从时间线性集合去除或使该缺口日
期隐藏不显示,从而达到K线图的连续不间断,便于统计分析。
意义
主要是对停牌日期缺口进行异常处理,以去除掉无交易的日期,保证X轴时间上的连续
性(有交易),使K线图显示数据变化是连续不间断的,有利于数据分析。
要将停牌缺口从时间线性集合中去除掉,必须涉及到下列相关对象、属性和方法的应用:
SegmentTimeline: 时间线性集合对象,包含已定义的交易时间轴的下限和上限
timeline.setStartTime(SegmentedTimeline.FIRST_MONDAY_AFTER_1900);//设置
时间线起点为1900年(一定要设置起始年份,否则时间轴没有基点,会错乱)
addException(Date date):从已定义的时间轴范围内,去除参数指定日期函数
DateAxis:时间线性X轴对象
x1Axis.setAutoRange(false);//设置不采用自动设置时间范围
x1Axis.setRange(date1,date2);//设置时间范围
x1Axis.setTimeline(timeline);//设置X轴时间线性集合
x1Axis.setAutoTickUnitSelection(false);//设置不采用自动选择刻度值
x1Axis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);//设置标记的位置
x1Axis.setStandardTickUnits(DateAxis.createStandardDateTickUnits());//设
置标准的时间刻度单位
x1Axis.setTickUnit(newDateTickUnit(DateTickUnit.DAY,1));//设置时间刻度的
间隔,这里以天为单位
x1Axis.setDateFormatOverride(newSimpleDateFormat("yyyy-MM-dd"));//设置
显示时间的格式
x1DateAxis.setVerticalTickLabels(true);//设置X轴时间标记是否垂直显示
上述是在设置X轴时间线性显示时涉及到的相关对象,设置好相关对象的属性值,就可以
将交易停牌缺口从K线图中去除,从而使得K线图保持连续性且美观。
思路分析
思路一
第一步:首先应用SegmentTimeline类默认的去除周末日期缺口的函数来构造SegmentTimeline
对象,即SegmentedTimeline.newMondayThroughFridayTimeline();
第二步:进一步从去除周末缺口的时间线性集合中出去特殊无交易日期缺口。
思路二
对SegmentTimeline时间线性集合对象的Calendar(日历对象)进行重新定义,即对时间
日历对象的time属性进行赋值(calendar.setTime(Date date);),当然date的取值来自数据
源提供的和SegmentTimeline对象的Date相匹配的date属性值,如果不匹配,调用
SegmentTimeline对象的addException(Date date)方法将其保存在exceptionSegments集合中
且设置该日期对应的无交易时间刻度在X轴显示时隐藏起来,并从Calendar对象去除该日期对
应的时间,从而实
现缺口优化。
解决过程
分两步:第一步,首先重载SegmentTimeline类的方法,调用默认的构造函数,以去除周
六日这些有周期的日期缺口;第二部,在前一步的基础上,再判断某个某个日期(特殊日期)是
否有交易,有则绘制交易数据,无则隐藏此数据交易不现实。从而达到K线图的数据连续性。
DateAxisx1Axis=new DateAxis();//设置x轴,也就是时间轴
x1Axis.setAutoRange(false);//设置不采用自动设置时间范围
x1Axis.setRange(dateFormat.parse("2007-08-20"),dateFormat.parse("2007-10-11
"));//设置时间范围,注意时间的最大值要比已有的时间最大值要多一天
//x1Axis.setTimeline(SegmentedTimeline.newMondayThroughFridayTimeline());设
置时间线显示的规则,用这个方法就摒除掉了周六和周日这些没有交易的日期(很多人都不知道
有此方法),使图形看上去连续
Map
式存放到Map集合中(后面要用来验证时间是否匹配)
for(intindex = 0;index <series.getItemCount();index++) {
RegularTimePeriodperiod =series.getPeriod(index);
Stringss =new SimpleDateFormat("yyyy-MM-dd").format(period.getStart());
map.put(ss,period.getStart());
}
//自定义时间线性集合,作为X轴显示的时间集合(匿名内部类调用其父类的方法)
SegmentedTimelinetimeline = new
SegmentedTimeline(SegmentedTimeline.DAY_SEGMENT_SIZE, 5, 2){
publicboolean containsDomainValue(Datedate) {
StringstringDate =date.toString();
System.out.println(date);
String key=new SimpleDateFormat("yyyy-MM-dd").format(date);
Date d=map.get(key);
if(d ==null) {//判断当前日期是否存在交易
this.addException(date);//不存在交易即为异常日期,添加到异常日期
集合中,并在Calendar对象中去除该时间
}
returnsuper.containsDomainValue(date);//判断当前日期是否包含在
SegmentTimeline的日期集合中
}
};
timeline.setStartTime(SegmentedTimeline.FIRST_MONDAY_AFTER_1900);
x1Axis.setTimeline(timeline);//设置X轴时间线性集合
x1Axis.setAutoTickUnitSelection(false);//设置不采用自动选择刻度值
x1Axis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);//设置标记的位置
x1Axis.setStandardTickUnits(DateAxis.createStandardDateTickUnits());//设置标
准的时间刻度单位
x1Axis.setTickUnit(newDateTickUnit(DateTickUnit.DAY,1));//设置标准的时间刻
度间隔单位(此处以一天为例则是1,如果以一周为例则是7)
x1Axis.setDateFormatOverride(newSimpleDateFormat("yyyy-MM-dd"));//设置显示时间的格式
NumberAxisy1Axis=new NumberAxis();//设定y轴,就是数字轴
y1Axis.setAutoRange(false);//设置是否采用自动设置Y轴数值范围
y1Axis.setRange(minValue*0.9,highValue*1.1);//设定y轴值的范围,比最大值要大一些,
这样图形看起来会美观些
y1Axis.setTickUnit(newNumberTickUnit((highValue*1.1-minValue*0.9)/10));//设置刻度
显示的密度
XYPlotplot1=new XYPlot(seriesCollection,x1Axis,y1Axis,candlestickRender);//设置画图区域对象
CombinedDomainXYPlotcombineddomainxyplot =new
CombinedDomainXYPlot(x1Axis);//建立一个联合图形区域对象,以x轴为共享轴
combineddomainxyplot.add(plot1);
combineddomainxyplot.add(plot1);//添加图形区域对象,后面的数字(如果有数字)是计算这个区域对象应该占据多大的区域2/3
JFreeChartchart =newJFreeChart("xx公司股票", JFreeChart.DEFAULT_TITLE_FONT,combineddomainxyplot,false);
ChartFrameframe =newChartFrame("xx公司xx股票",chart);
frame.pack();
frame.setVisible(true);
解决后的效果图
总结
addException(Date date)函数的具体实现步骤过程中涉及到如下图所示的相关函数的实现:
调用异常日期保存方法将date保存在list集合中,并从SegmentTimeline中对当前异常时间线段进行截取标记(即将这一段从SegmentTime中去除掉)后面再次验证时就不会存在该日期的段。
在addException()函数中调用inIncludeSegments()是验证date是否包含在周一至周五(这个时间段)。包含的话就调binarySearchExceptionSegments(Segmentsegment)方法返回一 个int类型的值,用来作为add(index ,segement)的index值。
对当前的segment对象进行是否属于验证(调用inExceptionSegments()函数验证其是否是异 常段),通过验证就说明segmentTimeline中包含该时间段,对应的坐标标记和数据可以显示。
验证segment是否包含在exceptionSegments集合中,同时为在addException()时的index索 引赋值 。
画K线图对于不包含在SegmentTimeline时间线性集合中日期进行隐藏设置,使其不显示在X轴上。super.containsDomainValue(date);里调inIncludeSegments()验证是否是工作日;是工作日在进一步验证是否是异常集合中的元素,是的话,设置X轴时间标记标签时执行回滚操作(rollDate())。
执行回滚时是刻度单位(DateTickUnit)对象执行回滚操作(rollDate()),进而得到有效的标 记标签的文本值和对应的x变量值。
设置时间线段时使date在起始日期的基础上以天为单位进行叠加,直至达到时间线段的上 限值(upperDate)。
当isHiddenValue(long millis)返回true时,TickUnit(时间刻度单位)对象调rollDate()方法将Calendar对象的日期按卷单元指定的数量(2天)滚动日期(如当前日期为2017-04-30,下一个日期2017-05-01无交易,需要跳过,即下一个日期应该为2017-05-02,此处调用calendar.add(intFiled , count)函数后,日期会设置为2007-05-02);
当返回false时,TickUnit调用addToDate()方法对date进行“1天”时间线段的设置。
rollDate()和addToDate()这两个函数是在绘制X轴的timelineLabel标记标签时用到的函 数,目的是在segmentTimeline中将异常日期的标签按指定单位和数量回滚过去,以达到在X 轴不显示缺口标签的目的。
addException()则是通过添加exceptionSegments集合元素来验证segmentTimeline中的 段(缺口)是否是异常段,异常就会在绘制X轴标记标签时进行回滚。