はじめに
過去記事は「auカブコム証券のkabuステーションREST APIに関する記事一覧」。
マージツール(バッチ用とリアルタイム監視)とテクニカル指標計算ツールを別々にリビジョンを振って管理していたが、2つのツールを統合する。
ベースとなるソース
v36._r5とv37._r3とv37._9のソースをベースに、v38._r10を作成する。
ゴール
クラスをr10に統一する。
入出力ファイルはr5のまま変更しない。
main()は当面はこのまま変更しないで使ってみて、統合するか決める。
クラス名の一覧を文字列定数でソース内に埋め込んでいるので、propertiesファイルに設定する。
パッケージ構成
いままでフラットにv36.*にクラスを並べていたが、プラグインなどをサブパッケージに分ける。
- bean: エンティティ
- c: テクニカル指標計算プラグイン
- factory: プラグインインスタンス生成の管理
- i: インターフェイス
- m: マージプラグイン
実装
propertiesファイル
Propertiesクラスにロードすると、Hashtableで管理されているため、キー順がバラバラになってしまう。
そこで、キーに整数(ソートキーのため、連続してなくてもよい)を振り、読み込んだ後に、TreeMapでキーを整数順に並べて、配列に代入する。
1=1m
2=3m
3=5m
4=10m
5=15m
6=20m
7=30m
8=60m
private static String[] barNames;
static {
try {
InputStream is = BarNameFactory_r10.class.getResourceAsStream("BarNameFactory_r10.properties");
Properties prop = new Properties();
prop.load(is);
Map<Integer, String> map = new TreeMap<>();
for (String key : prop.stringPropertyNames()) {
String val = prop.getProperty(key);
map.put(StringUtil.parseInt(key), val);
}
barNames = new String[map.size()];
int i = 0;
for (Integer key : map.keySet()) {
String val = map.get(key);
barNames[i++] = val;
}
} catch (IOException e) {
e.printStackTrace();
}
}
マージクラスは2つの配列の順番を揃えてソースに記述していたが、key=valueで記述する。
これもPropertiesにロードすると、キー順はバラバラになるので、上記のbarNamesを使う。
classNameMapに代入しなくてもgetProperty()を直接呼び出してもよかったが、propertiesファイルの記述方法を変更したときのために、解析部分と利用部分を分けておく。
1m=v38.m.MergeChartData1m_r10
3m=v38.m.MergeChartData3m_r10
5m=v38.m.MergeChartData5m_r10
10m=v38.m.MergeChartData10m_r10
15m=v38.m.MergeChartData15m_r10
20m=v38.m.MergeChartData20m_r10
30m=v38.m.MergeChartData30m_r10
60m=v38.m.MergeChartData60m_r10
private static Map<String, String> classNameMap = new TreeMap<>();
static {
try {
InputStream is = BarNameFactory_r10.class.getResourceAsStream("MergeChartDataFactory_r10.properties");
Properties prop = new Properties();
prop.load(is);
for (String key : prop.stringPropertyNames()) {
String val = prop.getProperty(key);
classNameMap.put(key, val);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static Map<String, Constructor<MergeChartData_r10>> consMap = new TreeMap<>();
static {
String[] barNames = BarNameFactory_r10.getBarNames();
for (int i = 0; i < barNames.length; i++) {
String key = barNames[i];
String name = classNameMap.get(key);
try {
@SuppressWarnings("unchecked")
Constructor<MergeChartData_r10> cons = (Constructor<MergeChartData_r10>) Class.forName(name).getDeclaredConstructor(String.class);
consMap.put(key, cons);
} catch (NoSuchMethodException | SecurityException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
テスト
eclipseから実行すると、問題なくpropertiesファイルを読んだが、バッチから起動するとInputStream isがnullでNPEが発生する。
getResourceAsStream()はクラスパスから、パッケージパス(v38.factory)の階層を探すので、クラスパスがclassesならば、classes\v38\factoryに3つのpropertiesファイルを配置する。
追記:DB_FILENAME,CHART_TXT_FILENAMEをMergeChartDataCommon_r10で扱う
既にCalcCoordinator_r10はCHART_TXT_FILENAME = "ChartData%s_r5.txt"のように動的にファイルを生成しているので、マージツール側もMergeChartDataCommon_r10側でDB_FILENAME = "ChartData%s.db"やCHART_TXT_FILENAME = "ChartData%s_r5.txt"のように動的にファイル名を生成する。
コンストラクタMergeChartDataCommon_r10(String name, String bar)に変更する。
これにより、各具象クラスのファイル名の文字列定数を削除する。
public class MergeChartData1m_r10 extends MergeChartDataCommon_r10 implements MergeChartData_r10 {
public MergeChartData1m_r10(String name, String bar) {
super(name, bar);
}
protected String search(String time) {
String startTime = ChartTime1mLogic.search(time);
return startTime;
}
}
追記:現物のデータに対応
先物OPしか対象としていなかったので、あまり現物のデータの中身を確認していなかったが、
先物は8:45-15:15に対して、現物や指数は9:00-15:00のため、分足の種類によって、誤ったマージデータとなる。
- 10分足:8:45,8:55,9:05のため、9:00は8:55となる
- 20分足:8:45,9:05のため、9:00は8:45となる
- 30分足:8:45,9:15のため、9:00は8:45となる
- 60分足:8:45,9:45のため、9:00は8:45となる
現物用に別途テーブルを用意し、search()にsuper.isFuture()を渡して区別する。
追記:CalcIndicatorCommon_r10に共通化
マージツールのMergeChartDataCommon_r10クラスと同様に、共通メソッドをCalcIndicatorCommon_r10クラスにまとめる。
追記:MainChartCalendar_r10に統一
v27.MainChartCalendar_r3をベースに、v38.MainChartCalendar_r10を作成する。
MainChartDataDaily_r3、MainChartDataHourly_r3も同様にMainChartDataDaily_r10、MainChartDataHourly_r10を作成する。
追記:MainChartDataMigrate_r10
短いx分足のDBデータをもとに、整数倍のy分足のDBデータを生成する。
xとyの組み合わせ(x -> y)は以下のとおり。
30m -> 60mのファイル名を、ChartData60m_30m.dbのように命名する。
- 1m -> 3m,5m,10m,15m,20m,30m,60m
- 3m -> 15m,30m,60m
- 5m -> 10m,15m,20m,30m,60m
- 10m -> 20m,30m,60m
- 15m -> 30m,60m
- 30m -> 60m
追記:MainChartDataIntegrate_r10
MainChartDataMigrate_r10で生成された複数のDBデータをマージして、ChartData60m_r10.dbファイルを作成する。
ChartData60m.dbを優先し、存在しない日時のデータをChartData60m_1m.dbなどからコピーする。
これを読み込むマージツール側がChartData60m.dbの代わりにChartData60m_r10.dbに変更する。
マージ後ファイル名もr5からr10に変更する。
追記:古いデータを削除
マージツールは24*60件、テクニカル指標は60件の最新データのみをファイル保存する。
追記:CalcIndicatorCommon_r10のcalcIndicator(),printIndicator()に統一
7種類のテクニカル指標を作成して、execute()が同じ形のため、CalcIndicatorCommon_r10クラスへ移動し、フレームワーク化する。
個別のcalcSma()やprintSma()をcalcIndicator()とprintIndicator()に統一する。
追記:IndicatorCodeによる紐づけ
この後、イベントトリガーを各テクニカル指標ごとに用意しようとすると、それぞれにIDがないと紐づけができない。
マージクラスは、MergeChartDataFactory_r10.propertiesのキーの値を、BarNameFactory_r10.propertiesに定義していたが、同様にpropertiesファイルを追加するのは面倒なので、列挙体を定義し、具象クラス内に埋め込むことにする。
public enum IndicatorCode {
SMA(1), BollingerBands(2), MACD(3), HV(4), Pivot(5), DMI(6), Parabolic(7);
public abstract class CalcIndicatorCommon_r10 implements CalcIndicator_r10 {
abstract public IndicatorCode getCode();
public class CalcIndicator1_r10 extends CalcIndicatorCommon_r10 implements CalcIndicator_r10 {
public IndicatorCode getCode() {
return IndicatorCode.SMA;
}
追記:BarCode列挙体をマージプラグインに埋め込む
IndicatorCode列挙体と同様にマージプラグインにもBarCode列挙体を埋め込む。
v40以降でイベントトリガーのメタ情報にマージプラグイン(足名)を追加する際に使う。
すでに定義されているMergeChartDataFactory_r10.propertiesのキー(=String bar)がBarCode.codeと同じ文字列となっているので、矛盾がないことをファクトリーでチェックする。将来、MergeChartDataFactory_r10.propertiesのキーを連番(1, 2, 3,...)に変更してもよい。
追記:MainChartDataMigrate_r10が出力するファイルの拡張子を変更
MainChartDataMigrate_r10がChartData10m_1m.dbのようなファイル名で出力していたが、中間ファイルなので、拡張子をtmpに変更する。
MainChartDataIntegrate_r10は*.tmpを処理対象とする。
(なお修正前の、拡張子がdbだった場合、ChartData10m.dbを最初に読み、ChartData10m_1m.dbなどを読みマージするが、出力ファイルChartData10m_r10.dbも読んでマージしてしまっていた。拡張子tmpに限定することで、出力ファイルは読まなくなる)
追記:テクニカル指標計算クラスにファイル出力でなくレコード文字列の変換を任せる
CalcIndicatorCommon_r10.printIndicator(PrintWriter)をabstractにして、各具象クラスにファイル出力をさせていたが、各レコードが独立しているため、1レコードづつCalcIndicatorInfo_r10から文字列を変換させ、CalcIndicatorCommon_r10側がファイルに保存する。
この修正をrestserverに持っていき、ファイル保存をRepositoryクラスに変えられるようにする。
追記:同じChartData.csvの読み込みを1回にする
マージツールは、4本値データとTickデータを読んで処理するが、Tickデータは同じ銘柄(ディレクトリ)内のChartData.csvを、各x分足を処理するごとに毎回読んでいたが、同じ内容なので最初の1回だけにする。
List<String> lines = null;
for (String key : BarNameFactory_r10.getBarNames()) {
MergeChartData_r10 merge = mergeMap.get(key);
lines = merge.execute(lines);
}
日付誤りのstdout logを置換前と置換後の2行にする。prevは前のレコードの日付であり、置換前の日付ではない。
修正前では、このログがx分足の数だけ複数回出力されていた。
Warning: REPLACE line=2022-08-09 00:00:00,27855.0,181357.0, new=2022-08-10 00:00:00, prev=2022-08-09 23:59:58
Warning: NEW line=2022-08-10 00:00:00,27855.0,181357.0
追記:MergeChartInfo_r10をv38.beanからbeanに移動
今後、おそらく変わらないので、v38からルート直下のbeanにクラスを移動する。
v45.bean.MergeChartInfo_r17は変更ないため、v45もMergeChartInfo_r10を参照する。
追記:CalcIndicatorInfo_r10をv38.beanからbeanに移動
MergeChartInfo_r10と同様にCalcIndicatorInfo_r10もbeanへ移動する。
v38.beanパッケージ内のクラスは無くなる。
追記:CalcCoordinator_r10のマージデータ受け入れをMergeChartData_r10からMapに変更
execute(MergeChartData_r10)を後から追加して、MainWatchChartDataでマージ後のデータを受け渡していたが、インターフェイスを渡すと、revが上がっていくと、どちらも上げていく必要がある。最終的にreadChartData()の中で、getChartMap()で全データを取得するので、MainWatchChartDataがgetChartMap()を呼んで、Mapを渡す。
本来ならば、MergeChartInfo_r10をMap, Listなどで管理するクラスがあった方がよい。
追記:ソースをarchiveブランチへ移動
最新版に移行し、もう使われることはないので、アーカイブする。
githubソース