目录

我的学习分享

记录精彩的程序人生

X

【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
15755710071.png

20191207

重复打开ChartFrame后,指标小窗口会重复增加
15756765611.png

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)

增加了股票快照窗口,秀一下(有些数据太大,要转换一下)
image.png

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();
    }