【acuistock】开发日志
一款股票行情软件,主要有3种类型的窗口
- 股票列表(可以根据各种条件筛选)
- 具体某一个股票信息(分时、k线、盘中信息)
- 其他(跟股票无关信息,例如聊天窗口)
acuistock面向A股,核心数据结构包括
- Stock(静态信息和动态信息)
股票市场market
股票代码code
股票名字name
上市时间listTime
是否退市delisting
上市时间戳listTimestamp - DataSet(K线)
- 分时
- 明确一下本地磁盘缓存到底起什么作用,如果有用,考虑用derby替换掉,如果没用,就从代码中清掉
fetchStockFromCache 没什么用
fetchDatasetFromCache 没什么用
getLastChartFrameId 有点用,只是保留了最后打开的ChartFrame的id,这个其实也没必要非要持久化到磁盘
综上,目前这个磁盘缓存真的意义不大,可以去掉 - 明确使用FTAPI作为唯一数据源,简化程序设计
这个可以在代码上做一些调整,首先由于目前不支持多数据源,可以将DataProvider简化为一个。 - 去掉股票代码切换
因为可以在股票列表中打开新的股票窗口
20191124
移除CacheManager
移除ProxyManager
移除commons-httpclient-3.1、commons-codec-1.4、commons-logging-1.1.1三个模块
移除测试用的Tushare Data Provider模块
20191203
另一个思路:考虑不要在应用初始化时,连接FutuOpenD;而是在程序主窗口显示后,通过工具栏按钮或菜单,启动图形化的FutuOpenD;然后再一个按钮连接FutuOpenD;
20191206
启动的时候用牛牛号登录,用户可以选择连接本地或远程FutuOpenD

20191207
重复打开ChartFrame后,指标小窗口会重复增加

20191208
unsubRegKL 中国卫星
Heap dump file created [931529257 bytes in 20.354 secs]
WARNING [org.netbeans.core.TimableEventQueue]: too much time in AWT thread org.netbeans.modules.sampler.InternalSampler@c3f685
SEVERE [global]
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at com.acuistock.macd.MACD.getPriceValues(MACD.java:245)
at com.acuistock.main.axis.Grid.paint(Grid.java:102)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
at com.acuistock.main.MainPanel.paint(MainPanel.java:103)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at org.netbeans.swing.tabcontrol.TabbedContainer.paint(TabbedContainer.java:994)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5210)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1579)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1502)
at javax.swing.RepaintManager.paint(RepaintManager.java:1272)
at javax.swing.JComponent._paintImmediately(JComponent.java:5158)
at javax.swing.JComponent.paintImmediately(JComponent.java:4969)
at javax.swing.JComponent.paintImmediately(JComponent.java:4950)
at javax.swing.RepaintManager$4.run(RepaintManager.java:831)
at javax.swing.RepaintManager$4.run(RepaintManager.java:814)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
[catch] at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:814)
WARNING [org.netbeans.core.TimableEventQueue]: no snapshot taken
Reply: Sub: 51 retType: -100
unsubRegKL 皖通高速
java.lang.OutOfMemoryError: Java heap space
Dumping heap to D:\GitHub\acuistock\build\testuserdir\var\log\heapdump.hprof ...
Heap dump file created [817040722 bytes in 24.569 secs]
Reply: Sub: 16 retType: -100
Qot onDisConnect: 47244640256
WARNING [org.openide.util.lookup.AbstractLookup]: Too long (111,735 ms and 20 references) cleanUpResult for class org.openide.modules.ModuleInfo
SEVERE [global]
java.lang.OutOfMemoryError: Java heap space
at java.io.BufferedOutputStream.<init>(BufferedOutputStream.java:76)
at org.netbeans.Stamps$Store.store(Stamps.java:727)
[catch] at org.netbeans.Stamps$Worker.run(Stamps.java:899)
WARNING [org.openide.util.lookup.AbstractLookup]: Too long (26,092 ms and 20 references) cleanUpResult for class com.acuistock.main.chart.Annotation
WARNING [org.openide.util.lookup.AbstractLookup]: Too long (33,903 ms and 20 references) cleanUpResult for class org.netbeans.modules.masterfs.providers.Notifier
SEVERE [global]
java.lang.OutOfMemoryError: Java heap space
WARNING [org.netbeans.core.TimableEventQueue]: too much time in AWT thread org.netbeans.modules.sampler.InternalSampler@1385de8
SEVERE [global]
java.lang.OutOfMemoryError: GC overhead limit exceeded
WARNING [org.netbeans.core.TimableEventQueue]: no snapshot taken
貌似因为年K线的条数太少导致的,年K线禁用吧!!!
20200421
解决一个异常
package com.acuistock.main.data;
/**
* 图数据
* @author acuilab.com
*/
public class ChartData implements ChartFrameListener {
public double[] getYValues(Rectangle rectangle, Range range, int fontHeight) {
// count表示要画几条水平线
int count = 15;
// TODO: 这个循环条件有可能出现除零错误java.lang.ArithmeticException: / by zero
// 水平线之间的高度不能太高,要大于或等于 (字体高度+20)像素
// rectangle是绘制k线那块区域,不包括指标那部分
// count > -2改为count > 0(同时防止出现除零错误): 最极端的情况就是count=0,此时空间太小,无水平线可画
// while (((rectangle.height / count) < (fontHeight + 20)) && (count > -2)) {
while ((count > 0) && ((rectangle.height / count) < (fontHeight + 20))) {
count--;
}
...
}
}
20200422
// TODO:
// 1 自选股改为本地存储 未完成
// 2 增加快照视图,默认显示在工作区右侧 已完成
// 3 增加分时视图(放入单独的TopComponent) 未完成
// 4 模板相关操作放入菜单 未完成
// 5 实时扫描框架 未完成
// 6 用AutoHotKey写一个脚本来控制交易窗口,进行大A全自动/半自动交易;结合富途行情API,信号发出时调用脚本下单
// 打开601808中海油服异常
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at com.acuistock.main.data.Dataset.getLastDataItem(Dataset.java:266)
at com.acuistock.main.managers.DatasetManager.onPush_UpdateKL(DatasetManager.java:259)
at com.acuistock.main.managers.QotAPIManager.onPush_UpdateKL(QotAPIManager.java:348)
at com.futu.openapi.FTAPI_Conn_Qot.onPush(FTAPI_Conn_Qot.java:866)
at com.futu.openapi.FTAPI_Conn$4.invoke(FTAPI_Conn.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
[catch] at com.sun.jna.CallbackReference$DefaultCallbackProxy.invokeCallback(CallbackReference.java:520)
at com.sun.jna.CallbackReference$DefaultCallbackProxy.callback(CallbackReference.java:551)
增加了股票快照窗口,秀一下(有些数据太大,要转换一下)

20200423
增加一个股票实时扫描框架(A股4000余只股票,扫描一次大概2分钟)
可以灵活的扩展,随时增加或减少扫描条件
自选股包含若干分组。
20200613
分时窗口单独占用一个TopComponent,默认位置在右下角。
分时数据跟ChartFrame关联,每个ChartFrame对应一个分时数据。
可以在一个TopComponent中罗列当前已打开的所有ChartFrame的分时数据。
与DatasetManager类似,分时有自己的管理器TimeShareManager,其实现方式也类似。
2020717
好嘛,过去一个多月了
今天说一个有意思的设计思路
我们知道Netbeans Platform的注册发现机制,以Indicator为例:
首先是IndicatorManager,这是一个单例模式
package com.acuistock.main.managers;
import com.acuistock.main.chart.Indicator;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import org.openide.util.Lookup;
/**
* 指标管理器
*
* @author acuilab.com
*/
public class IndicatorManager {
private static IndicatorManager instance;
private LinkedHashMap<String, Indicator> indicators;
public static IndicatorManager getDefault() {
if (instance == null) {
instance = new IndicatorManager();
}
return instance;
}
private IndicatorManager() {
indicators = new LinkedHashMap<>();
Collection<? extends Indicator> list = Lookup.getDefault().lookupAll(Indicator.class);
for (Indicator i : list) {
indicators.put(i.getName(), i);
}
sort();
}
private void sort() {
List<String> mapKeys = new ArrayList<>(indicators.keySet());
Collections.sort(mapKeys);
LinkedHashMap<String, Indicator> someMap = new LinkedHashMap<>();
for (int i = 0; i < mapKeys.size(); i++) {
someMap.put(mapKeys.get(i), indicators.get(mapKeys.get(i)));
}
indicators = someMap;
}
public Indicator getIndicator(String key) {
return indicators.get(key);
}
public List<Indicator> getIndicatorsList() {
List<Indicator> list = Lists.newArrayList();
Iterator<String> it = indicators.keySet().iterator();
while (it.hasNext()) {
list.add(indicators.get(it.next()));
}
return list;
}
public List<String> getIndicators() {
List<String> list = new ArrayList<>(indicators.keySet());
Collections.sort(list);
return list;
}
}
在其私有构造函数中,通过Lookup机制获得了系统中注册的所有Indicator
在指标选择窗口中,通过IndicatorManager获得所有Indicator,并用其初始化选择列表
public void initForm() {
btnAdd.setEnabled(false);
btnRemove.setEnabled(false);
selected = Lists.newArrayList();
unselected = Lists.newArrayList();
unselected = IndicatorManager.getDefault().getIndicatorsList();
selected = parent.getSplitPanel().getIndicatorsPanel().getIndicatorsList();
initial = selected;
scrollPane.setEnabled(false);
scrollPane.setLayout(new BorderLayout());
lstSelected.setListData(getArray(selected, true));
lstUnselected.setListData(getArray(unselected, false));
}
下面是其增加按钮的实现逻辑
private void btnAddActionPerformed(java.awt.event.ActionEvent evt) {
int i = lstUnselected.getSelectedIndex();
if (i != -1) {
Indicator ind = unselected.get(i).newInstance();
selected.add(ind);
lstSelected.setListData(getArray(selected, true));
setPanel(ind);
}
}
注意Indicator ind = unselected.get(i).newInstance();, newInstance()方法创建了当前实例的一个副本,这样保证ChartFrame都有一个自己版本的。
下面是PlusDI指标的newInstance()方法的实现代码
@Override
public Indicator newInstance() {
return new PlusDI();
}