Учитывая, сколько алготрейдеру приходится гонять бэктестер, важно, чтобы он работал быстро.
Первый шаг для увеличения скорости — перевести цены в int. Держать цены в double вообще сомнительная практика, если не хотите, чтобы в глубинах вашего алго возникали значения типа 1724.999999999999997 с непредсказуемыми последствиями. Альтернативой int будет только BigDecimal, что нормально было бы для рабочего кода, но бэктестер выйдет жутко медленным.
Ок, цены в int. Тогда встает второй вопрос — в какой коллекции держать данные. Вроде бы, примитивный array должен быть самым быстрым, но работать с ним неудобно. Есть правда сторонние реализации коллекций на базе примитивов, например FastUtil, и сейчас потестируем, насколько они решают проблему.
Типовая для бэктестера задача — есть пол-миллиона точек, идем окном в 20 000 точек и делаем в окне что-то страшное. В тесте — просто суммируем и отсылаем в функцию.
Тестируем три варианта:
- ArrayList<Integer>.
- Контейнер IntArrayList от FastUtil.
- Примитивный array[int].
Делаем десять проходов для «прогрева» JVM, на последнем проходе получаем следующие результаты в миллисекундах:
(list) work time: 7626
(fastutil) work time: 2347
(array) work time: 2358
Результаты:
1. ArrayList<Integer> втрое медленнее контейнеров на примитивах. Что и следовало ожидать, в общем-то, boxing/unboxing.
2. Fastutil молодцы, задачу решают.
JVM: liberica-15
Код:
import it.unimi.dsi.fastutil.ints.IntArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class TestIntList {
public static final int N = 500000;
public static final int WINDOW = 20000;
public static final int BOUND = 100000;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
testList();
testFastUtil();
testArray();
System.out.println("******");
}
}
private static void testFastUtil() {
final IntArrayList values = new IntArrayList(N);
final Random random = new Random(System.nanoTime());
for (int i = 0; i < N; i++) {
values.add(i, random.nextInt(BOUND));
}
final long startTime = System.nanoTime();
for (int i = WINDOW - 1; i < N; i++) {
int sum = 0;
for (int j = i - WINDOW + 1; j <= i; j++) {
sum += values.getInt(j);
}
doSomething(sum);
}
final long endTime = System.nanoTime();
System.out.println("(fastutil) work time: " + (endTime - startTime) / 1000000);
}
private static void testList() {
final List<Integer> values = new ArrayList<>(N);
final Random random = new Random(System.nanoTime());
for (int i = 0; i < N; i++) {
values.add(i, random.nextInt(BOUND));
}
final long startTime = System.nanoTime();
for (int i = WINDOW - 1; i < N; i++) {
int sum = 0;
for (int j = i - WINDOW + 1; j <= i; j++) {
sum += values.get(j);
}
doSomething(sum);
}
final long endTime = System.nanoTime();
System.out.println("(list) work time: " + (endTime - startTime) / 1000000);
}
private static void testArray() {
final int[] values = new int[N];
final Random random = new Random(System.nanoTime());
for (int i = 0; i < N; i++) {
values[i] = random.nextInt(BOUND);
}
final long startTime = System.nanoTime();
for (int i = WINDOW - 1; i < N; i++) {
int sum = 0;
for (int j = i - WINDOW + 1; j <= i; j++) {
sum += values[j];
}
doSomething(sum);
}
final long endTime = System.nanoTime();
System.out.println("(array) work time: " + (endTime - startTime) / 1000000);
}
public static void doSomething(int sum) {
if (sum < 0) System.out.println("wtf?");
}
}