はじめに
過去記事は「auカブコム証券のkabuステーションREST APIに関する記事一覧」。
x分足の計算をする際に、モヤモヤしていたものを改善してみた。
いきさつ
チャートデータをx分足を計算する際に、例えば、現物午前ならば、9:00-11:30の範囲の時間に5分足なら、9:00:00-9:04:59が9:00:00にまとめることになります。
ここで、x分足が、1分足ならば、HH: mm:ssのss秒を00秒にするだけでまとめられます。
これが、xが1, 3, 5, 10, 15, 20, 30, 60と作っていくと、いろいろと面倒なことが起きます。
当初の時刻文字列テーブル
現物だけならば、09:00-11:30と12:30-15:00なので、それほど大きな問題はないですが、先物の場合、08:45-15:15と16:30-翌06:00のため、いろいろ問題があります。
特に、日付をまたぐところが、x=1, 3, 5, 10, 15, 30は、ちょうど24:00となりますが、x=20の場合は23:50から24:10、x=60の場合は23:30から24:30と24:00をまたぐため、前半が当日のみ、後半の翌日が当日分に割り当てられます。
正直、よく分からないのと、終了時刻の境界値もまたぐ場合があり、文字列でテーブルを作りました。
/**
* 先物日中(08:45-15:20)の時刻一覧。
*/
private static String[] time1 = new String[79 + 1]; // 79 = 12 * 6.5 + 1
/**
* 先物夜間(16:30-24:00)の時刻一覧。
*/
private static String[] time2 = new String[90 + 1]; // 90 = 12 * 7.5
/**
* 先物夜間(00:00-06:05)の時刻一覧。
*/
private static String[] time3 = new String[73 + 1]; // 73 = 12 * 6 + 1
/**
* 現物午前(09:00-11:35)の時刻一覧。
*/
private static String[] time4 = new String[31 + 1]; // 31 = 12 * 2.5 + 1
/**
* 現物午後(12:30-15:05)の時刻一覧。
*/
private static String[] time5 = new String[31 + 1]; // 31 = 12 * 2.5 + 1
/**
* 先物日中(08:45-15:20)の時刻一覧の初期化。
*/
private static void init1() {
int hour = 8;
int min = 45;
for (int i = 0; i < time1.length; i++) {
String time = String.format("%02d:%02d:00", hour, min);
time1[i] = time;
min += 5;
if (min >= 60) {
hour++;
min -= 60;
}
}
}
/**
* 先物夜間(16:30-24:00)の時刻一覧。
*/
private static void init2() {
int hour = 16;
int min = 30;
for (int i = 0; i < time2.length; i++) {
String time = String.format("%02d:%02d:00", hour, min);
time2[i] = time;
min += 5;
if (min >= 60) {
hour++;
min -= 60;
}
}
}
/**
* 先物夜間(00:00-06:05)の時刻一覧。
*/
private static void init3() {
int hour = 0;
int min = 0;
for (int i = 0; i < time3.length; i++) {
String time = String.format("%02d:%02d:00", hour, min);
time3[i] = time;
min += 5;
if (min >= 60) {
hour++;
min -= 60;
}
}
}
/**
* 現物午前(09:00-11:35)の時刻一覧。
*/
private static void init4() {
int hour = 9;
int min = 0;
for (int i = 0; i < time4.length; i++) {
String time = String.format("%02d:%02d:00", hour, min);
time4[i] = time;
min += 5;
if (min >= 60) {
hour++;
min -= 60;
}
}
}
/**
* 現物午後(12:30-15:05)の時刻一覧。
*/
private static void init5() {
int hour = 12;
int min = 30;
for (int i = 0; i < time5.length; i++) {
String time = String.format("%02d:%02d:00", hour, min);
time5[i] = time;
min += 5;
if (min >= 60) {
hour++;
min -= 60;
}
}
}
time_valの置き換え
ここで、hour, minの2つのint変数(秒足ならば、さらにsec)でループする際に、C/C++のswap(&a, &b)のようなポインタ渡しや、C++の参照渡しができないと、minが60を超えたらhour++するために、クラスを用意したり、int[]で渡すのは・・・ということで、考えたらが、時刻のみのtime_tで、"00:00:00"からの経過時間(秒)を管理するint値でループすれば、60秒で1分繰り上がりや、60分で1時間を考えることなく加算していけるかなと。
/**
* 00:00:00を基準とした経過時間(秒)を計算する。
*
* @param hour 時。
* @param min 分。
* @param sec 秒。
* @return 経過時間。
*/
public static int time_val(int hour, int min, int sec) {
return hour * 60 * 60 + min * 60 + sec;
}
/**
* 00:00:00を基準とした経過時間(秒)を計算する。
*
* @param s 時刻文字列(HH:mm:ss)。
* @return 経過時間。
*/
public static int time_val(String s) {
if (s == null || s.length() < 8 || s.charAt(2) != ':' || s.charAt(5) != ':') {
return 0;
}
if (s.length() > 8) {
s = s.substring(0, 8);
}
int hour = StringUtil.parseInt(s.substring(0, 2));
int min = StringUtil.parseInt(s.substring(3, 5));
int sec = StringUtil.parseInt(s.substring(6, 8));
return time_val(hour, min, sec);
}
/**
* 00:00:00を基準とした経過時間(秒)を文字列で取得する。
*
* @param time_val 経過時間。
* @return 時刻文字列(00:00:00-23:59:59)。
*/
public static String toString(int time_val) {
while (time_val < 0) {
time_val += 24 * 60 * 60;
}
int hour = time_val / 3600 % 24;
int min = time_val / 60 % 60;
int sec = time_val % 60;
String time = String.format("%02d:%02d:%02d", hour, min, sec);
return time;
}
/**
* 00:00:00を基準とした経過時間(秒)を文字列で取得する。
*
* @param time_val 経過時間。
* @param b99h true:99時まで、false:23時まで。
* @return 時刻文字列(00:00:00-23:59:59または99:59:59)。
*/
public static String toString(int time_val, boolean b99h) {
while (time_val < 0) {
time_val += 24 * 60 * 60;
}
int hour = time_val / 3600;
if (b99h) {
while (hour > 99) {
hour -= 24;
}
} else {
hour %= 24;
}
int min = time_val / 60 % 60;
int sec = time_val % 60;
String time = String.format("%02d:%02d:%02d", hour, min, sec);
return time;
}
このstaticメソッドを用意しただけで、init1()からinit5()が1つにまとめられました。
time2のときだけtrueを渡しているのは、20分足のとき、24時を超えた00:10では大小関係が23:50 <= x < 00:10となってしまうため、23:50 <= x < 24:10とするためです。
int st;
int ed;
int dt = DELTA5;
{
st = TimeUtil.time_val(8, 45, 0);
ed = TimeUtil.time_val(15, 15, 0);
ChartTimeLogic.init(time1, st, ed, dt, false);
}
{
st = TimeUtil.time_val(16, 30, 0);
ed = TimeUtil.time_val(23, 59, 59);
ChartTimeLogic.init(time2, st, ed, dt, true);
}
{
st = TimeUtil.time_val(0, 0, 0);
ed = TimeUtil.time_val(6, 0, 0);
ChartTimeLogic.init(time3, st, ed, dt, false);
}
{
st = TimeUtil.time_val(9, 0, 0);
ed = TimeUtil.time_val(11, 30, 0);
ChartTimeLogic.init(time4, st, ed, dt, false);
}
{
st = TimeUtil.time_val(12, 30, 0);
ed = TimeUtil.time_val(15, 00, 0);
ChartTimeLogic.init(time5, st, ed, dt, false);
}
/**
* 時刻一覧の初期化。
*
* @param times 初期化する時刻一覧。
* @param st 開始時刻。
* @param ed 終了時刻。
* @param dt 間隔。
* @param b99h true:99時まで、false:23時まで。
*/
public static void init(String[] times, int st, int ed, int dt, boolean b99h) {
int tim = st;
int cnt = (ed - tim) / dt + 1;
if (times.length != cnt + 1) {
throw new RuntimeException("times.length=" + times.length + ", cnt+1=" + (cnt + 1));
}
for (int i = 0; i < cnt + 1; i++) {
String time = TimeUtil.toString(tim, b99h);
times[i] = time;
tim += dt;
}
}
もう文字列を止める
CSVファイルから読んだ文字列から処理していたので、文字列のテーブルでグループ化していましたが、もう時刻の文字列からtime_valに変換して、数値演算だけでグループ化します。
すると、
public static final int 日付変更線 = TimeUtil.time_val(8, 0, 0);
public static final int 先物日中開始時刻 = TimeUtil.time_val(8, 45, 0);
public static final int 先物日中終了時刻 = TimeUtil.time_val(15, 15, 0);
public static final int 先物夜間開始時刻 = TimeUtil.time_val(16, 30, 0);
public static final int 先物夜間終了時刻 = TimeUtil.time_val(30, 0, 0);
public static final int 現物午前開始時刻 = TimeUtil.time_val(9, 0, 0);
public static final int 現物午前終了時刻 = TimeUtil.time_val(11, 30, 0);
public static final int 現物午後開始時刻 = TimeUtil.time_val(12, 30, 0);
public static final int 現物午後終了時刻 = TimeUtil.time_val(15, 0, 0);
/**
* 指定した時刻が所属する時刻を検索する。
*
* @param dt 間隔。
* @param bFuture true:先物/OP、false:現物/指数
* @param time 現値の時刻。
* @return 所属する時刻。範囲外の場合はnull。
*/
public static String search(int dt, boolean bFuture, String time) {
if (time == null) {
return null;
}
int off = 0;
int ed = -1;
int tim = TimeUtil.time_val(time);
if (bFuture) {
if (tim < ChartTimeLogic.日付変更線) {
tim += ChartTimeLogic.DELTA1d;
}
if (tim >= ChartTimeLogic.先物夜間開始時刻) {
off = ChartTimeLogic.先物夜間開始時刻;
ed = ChartTimeLogic.先物夜間終了時刻;
} else if (tim >= ChartTimeLogic.先物日中開始時刻) {
off = ChartTimeLogic.先物日中開始時刻;
ed = ChartTimeLogic.先物日中終了時刻;
}
} else {
if (tim >= ChartTimeLogic.現物午後開始時刻) {
off = ChartTimeLogic.現物午後開始時刻;
ed = ChartTimeLogic.現物午後終了時刻;
} else if (tim >= ChartTimeLogic.現物午前開始時刻) {
off = ChartTimeLogic.現物午前開始時刻;
ed = ChartTimeLogic.現物午前終了時刻;
}
}
tim -= off;
tim = tim / dt * dt;
tim += off;
if (tim > ed) {
return null;
}
time = TimeUtil.toString(tim);
return time;
}
すべて開始時刻(off)を引いて、ここを基準に、tim / dt * dtでdt幅で丸めて、offを戻して、終了時刻以内ならば、文字列に変換する。
ファイル上では日付が変わりますが、日付変更線を08:00:00として、00:00:00-07:59:59の時刻は24:00:00-31:59:59として扱うことで、16:30-30:00まで連続した時刻として計算できます。
自動テスト
自動テストは、初期のときから作成し、24時間×60分×60秒のすべてをチェックしてましたが、デグレードチェックに役に立ちました。
5分足ならば、常にminを5で丸めるだけですが、他のx分足だとexpected()が複数パターン作ることになります。
@Test
public void searchFalseTest() {
boolean bFuture = false;
int i = 0;
for (int h = 0; h <= 8; h++) {
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
assertNull(time, a1);
i++;
}
}
}
for (int h = 9; h <= 10; h++) {
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
String e1 = expected(h, m);
assertEquals(time, e1, a1);
i++;
}
}
}
{
int h = 11;
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
if (m <= 34) {
String e1 = expected(h, m);
assertEquals(time, e1, a1);
} else {
assertNull(time, a1);
}
i++;
}
}
}
{
int h = 12;
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
if (m <= 29) {
assertNull(time, a1);
} else {
String e1 = expected(h, m);
assertEquals(time, e1, a1);
}
i++;
}
}
}
for (int h = 13; h <= 14; h++) {
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
String e1 = expected(h, m);
assertEquals(time, e1, a1);
i++;
}
}
}
{
int h = 15;
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
if (m <= 4) {
String e1 = expected(h, m);
assertEquals(time, e1, a1);
} else {
assertNull(time, a1);
}
i++;
}
}
}
for (int h = 16; h <= 23; h++) {
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
assertNull(time, a1);
i++;
}
}
}
assertEquals(24 * 60 * 60, i); // テスト件数
}
@Test
public void searchTrueTest() {
boolean bFuture = true;
int i = 0;
for (int h = 0; h <= 5; h++) {
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
String e1 = expected(h, m);
assertEquals(time, e1, a1);
i++;
}
}
}
{
int h = 6;
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
if (m <= 4) {
String e1 = expected(h, m);
assertEquals(time, e1, a1);
} else {
assertNull(time, a1);
}
i++;
}
}
}
{
int h = 7;
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
assertNull(time, a1);
i++;
}
}
}
{
int h = 8;
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
if (m <= 44) {
assertNull(time, a1);
} else {
String e1 = expected(h, m);
assertEquals(time, e1, a1);
}
i++;
}
}
}
for (int h = 9; h <= 14; h++) {
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
String e1 = expected(h, m);
assertEquals(time, e1, a1);
i++;
}
}
}
{
int h = 15;
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
if (m <= 19) {
String e1 = expected(h, m);
assertEquals(time, e1, a1);
} else {
assertNull(time, a1);
}
i++;
}
}
}
{
int h = 16;
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
if (m <= 29) {
assertNull(time, a1);
} else {
String e1 = expected(h, m);
assertEquals(time, e1, a1);
}
i++;
}
}
}
for (int h = 17; h <= 23; h++) {
for (int m = 0; m <= 59; m++) {
for (int s = 0; s <= 59; s++) {
String time = testdata(h, m, s);
String a1 = search(bFuture, time);
String e1 = expected(h, m);
assertEquals(time, e1, a1);
i++;
}
}
}
assertEquals(24 * 60 * 60, i); // テスト件数
}
private String testdata(int h, int m, int s) {
String time = String.format("%02d:%02d:%02d", h, m, s);
return time;
}
private String search(boolean bFuture, String time) {
String a1 = ChartTime5mLogic.search(bFuture, time);
return a1;
}
private String expected(int h, int m) {
int m1 = m / 5 * 5;
String e1 = String.format("%02d:%02d:00", h, m1);
return e1;
}
追記:むかしのソースをr1で残す
後から参照するため、int hour, min, sec;のソースをr1で戻す。
ただし、現物対応する前のため、引数が異なる。
r1: String search(String time)
none: String search(boolean bFuture, String time)
githubソース