Скоростная обработка тиков. Рекомендации

Собери-будущее

Последнее время приходится много работать с тиковыми данными и, естественно, приходится их обрабатывать в TSLab. Так как свечей много и в каждой свече еще больше тиков, то процесс обработки оных может занимать длительное время. Очень хочется чтобы работало все быстрее и как итог, было решено протестировать основные паттерны работы с тиками. В результате сделаем вывод о том, как реализуется скоростная обработка тиков чтобы было быстро и хорошо. И да, картинок не будет 🙂 .

Скоростная обработка тиков. Постановка задачи.

Схема обработки тиков весьма однообразна и содержит пару базовых паттернов.
— прямой обсчет тиков текущей свечи. Мы работаем прямо с тиками без создание промежуточных списков.
— обсчет тиков свечи через промежуточные коллекции и списки.

Вообще, вся схема обычно происходит путем перебора всех свечей начиная с первой и кончая последней. На каждом баре мы что-то вычисляем, и потом результат куда-то выдаем. В тестовом скрипте роль бара будет выполнять массив с числами double (якобы тики), а процесс перебора баров реализуем через простой цикл.

Сразу оговорюсь, что стоит учесть возможную разницу в скорости работы с коротким и длинным массивом, поэтому тестовый скрипт будет комбинировать длину массива и выдавать итоги для каждой длины. При работе с объектами, будут еще дополнительные задержки на обращение к данным, но пока это мы не будем моделировать.

Для измерения времени работы, будет использовать специальный класс System.Diagnostics.Stopwatch. Он позволит измерить время с высоким уровнем точности. Каждый проход по свечам будем замерять как единую операцию. В итоге получим список измерений для разной длины массива тиков.

Обработка коллекций может происходить с использованием трех типичных инструментов:
— стандартные циклы (for, while, do)
— цикл foreach
— linq команды

Поэтому в тесте будет разбирать производительность каждой из трех, чтобы понять какой вариант нам лучше использовать.

Тестовый скрипт

Скрипт получился не очень большой но покрывающий все наши исходные задачи.

            // чтобы набор чисел всегда был одинаков, используем seed
            var rnd = new Random(100);
            
            // стартовый размер массива данных и делитель
            var arrSize = 100000000;
            var div = 1;

            var currSize = arrSize;
            while (currSize > 10)
            {
                div *= 10;
                currSize = arrSize / div;

                // якобы массив тиков
                var arr = new double[currSize];
                
                for (int i = 0; i < arr.Length; i++)
                    arr[i] = rnd.Next(1, 1000);

                var watch = new Stopwatch();
                watch.Start();
                var count = 0.0;

                // якобы перебор свечек  
                for (var i = 0; i < div; i++)
                {
                    count = 0;
                    // сюда будем вставлять команды работы с тиками
                }


                var msg = string.Format("ArrLength: {0}, Loop: {1}, Time: {2}, count: {3}", currSize, div, watch.Elapsed, count);
                Debug.WriteLine(msg);
            }

Прямой обсчет тиков

Прямой обсчет не использует промежуточных списков и массивов, а напрямую извлекает тики из массива тиков и производит арифметические операции. Примеры такой логики:
- подсчет числа сделок на покупку в свече.
- расчет объема свечи из тиковых сделок.
- подсчет объема всех покупок в свече.
- подсчет сделок больше заданного объема в свече.

В общем, примеры показывают направление нашей мысли. Для теста будем рассчитывать объем всех сделок с объемом больше 500. Максимальный объем тика у нас 1000, как следует из тестового скрипта.

Примеры кода для каждого варианта:

    // linq
    count = arr.Sum(v => v > 500 ? v: 0);

    // for
   for (int k = 0; k < arr.Length; k++)
       count += arr[k] > 500 ? arr[k] : 0;
   
    // foreach
    foreach (var item in arr)
        count += item > 500 ? item : 0;

Результаты следующие:

for ArrLength: 3000000, Loop: 100, Time: 00:00:03.9433100, count: 1122845420
ArrLength: 300000, Loop: 1000, Time: 00:00:03.9302639, count: 112374876
ArrLength: 30000, Loop: 10000, Time: 00:00:03.9892026, count: 11198670
ArrLength: 3000, Loop: 100000, Time: 00:00:03.8094046, count: 1111242
ArrLength: 300, Loop: 1000000, Time: 00:00:03.7044354, count: 116344
ArrLength: 30, Loop: 10000000, Time: 00:00:03.5904666, count: 12634
ArrLength: 3, Loop: 100000000, Time: 00:00:02.6527128, count: 2169
foreach ArrLength: 30000000, Loop: 10, Time: 00:00:04.2863756, count: 11237564998
ArrLength: 3000000, Loop: 100, Time: 00:00:04.2342477, count: 1122845420
ArrLength: 300000, Loop: 1000, Time: 00:00:04.2351377, count: 112374876
ArrLength: 30000, Loop: 10000, Time: 00:00:04.1641656, count: 11198670
ArrLength: 3000, Loop: 100000, Time: 00:00:03.7539018, count: 1111242
ArrLength: 300, Loop: 1000000, Time: 00:00:03.6106301, count: 116344
ArrLength: 30, Loop: 10000000, Time: 00:00:03.3811633, count: 12634
ArrLength: 3, Loop: 100000000, Time: 00:00:02.4263865, count: 2169
linq ArrLength: 30000000, Loop: 10, Time: 00:00:10.2037127, count: 11237564998
ArrLength: 3000000, Loop: 100, Time: 00:00:10.1790545, count: 1122845420
ArrLength: 300000, Loop: 1000, Time: 00:00:10.6790103, count: 112374876
ArrLength: 30000, Loop: 10000, Time: 00:00:10.1260764, count: 11198670
ArrLength: 3000, Loop: 100000, Time: 00:00:10.1368777, count: 1111242
ArrLength: 300, Loop: 1000000, Time: 00:00:09.6797452, count: 116344
ArrLength: 30, Loop: 10000000, Time: 00:00:09.5827869, count: 12634
ArrLength: 3, Loop: 100000000, Time: 00:00:16.0744673, count: 2169

Как видно, linq остает почти в 3 раза от других вариантов цикла. При этом у linq какое-то неадекватное увеличение времени выполнения при размере массива 3. Это не ошибка, результат повторяется стабильно. Видимо накладные расходы на использование linq здесь вылезают по полной программе и время резко увеличивается. Так что, хоть и красиво и удобно, но для обработки тиков linq не подходит при прямой работе с тиками. Увы и ах.
for и foreach идут ноздря в ноздрю и между ними выбирать не стоит. Какой вариант удобнее такой и используйте, но если вы патологический кодер, то используйте всегда базовые циклы, ведь они немного быстрее 🙂 .

Обсчет тиков через промежуточные коллекции.

Данный паттерн встречается не менее часто чем предыдущий. Примеры реализации:
- график footprint который можно увидеть в TSLab
- неоднократное обращение к определенной выборке тиков, например к тикам покупок.
- любой другой случай где мы часть тиков помещаем в отдельную коллекцию.

Так как выше выяснили что foreach так же быстр как for, то не будем задействовать for в тестах.

Примеры кода для каждого варианта:

    // linq без конвертации в другой вид коллекции
    var result = arr.Where(v => v > 500);
    
    // linq с корвертацией в массив
    var result = arr.Where(v => v > 500).ToArray();

    // foreach с созданием отдельного списка
    var result = new List();
    foreach (var item in arr)
    {
        if (item > 500)
            result.Add(item);
    }

После получения коллекции, будем производить с ней какую либо операцию, например, подсчет объема тиков.

    // проводим операцию над коллекцией полученной выше одним из способов
    foreach (var item in result)
       count += item;

Результаты следующие:

foreach ArrLength: 10000000, Loop: 10, Time: 00:00:02.4438366, count: 3746495393
ArrLength: 1000000, Loop: 100, Time: 00:00:02.3367783, count: 374321244
ArrLength: 100000, Loop: 1000, Time: 00:00:02.4252638, count: 37297133
ArrLength: 10000, Loop: 10000, Time: 00:00:02.4478711, count: 3756152
ArrLength: 1000, Loop: 100000, Time: 00:00:01.4827645, count: 355438
ArrLength: 100, Loop: 1000000, Time: 00:00:01.7289573, count: 39551
ArrLength: 10, Loop: 10000000, Time: 00:00:01.7987754, count: 2111
linq ArrLength: 10000000, Loop: 10, Time: 00:00:02.9777427, res.count: 4995004
ArrLength: 1000000, Loop: 100, Time: 00:00:02.9503232, res.count: 499069
ArrLength: 100000, Loop: 1000, Time: 00:00:02.9179539, res.count: 49788
ArrLength: 10000, Loop: 10000, Time: 00:00:02.9169527, res.count: 5013
ArrLength: 1000, Loop: 100000, Time: 00:00:02.7192137, res.count: 478
ArrLength: 100, Loop: 1000000, Time: 00:00:02.5827635, res.count: 50
ArrLength: 10, Loop: 10000000, Time: 00:00:02.8403376, res.count: 3
linq array ArrLength: 10000000, Loop: 10, Time: 00:00:03.8770327, count: 3746495393
ArrLength: 1000000, Loop: 100, Time: 00:00:03.8029816, count: 374321244
ArrLength: 100000, Loop: 1000, Time: 00:00:03.8641673, count: 37297133
ArrLength: 10000, Loop: 10000, Time: 00:00:03.9113998, count: 3756152
ArrLength: 1000, Loop: 100000, Time: 00:00:03.2722251, count: 355438
ArrLength: 100, Loop: 1000000, Time: 00:00:03.9661614, count: 39551
ArrLength: 10, Loop: 10000000, Time: 00:00:04.2493606, count: 2111

Здесь, работа с linq себя неплохо зарекомендовала, но только если не переводить коллекцию в массив или список. Если будете конвертировать то сразу теряете в два раза производительность. Возможно, конвертация даст плюс если нужно много раз обратиться к коллекции, тогда мы избавимся от накладных расходов linq но получим замедление на создание массива. В общем – не лучший вариант.

В лидерах у нас создание коллекции. Память, конечно, это будет жрать, но чаще всего скорость важнее. Поэтому работаем через стандартные циклы и через создание списка или массива.

Тяжело в учении – легко в бою

В завершение, хотелось проверить как будет работать скоростная обработка тиков в реальных условиях, то есть, в TSLab. Тиковых данных у меня достаточно, засим переходим в реальные полевые условия (можно накачать тики с http://ftp.moex.ru или достать где-то еще). Тестировать буду на простом примере из библиотеки индикаторов RusAlgo. По умолчанию, там используется linq в виду его красоты и удобства написания. Кубик расчета объема всех сделок на покупку и продажу, подойдет как нельзя кстати. Создадим новую версию кубика, с применением стандартных циклов и будем сравнивать скорость обработки всех баров. Для того чтобы время было ощутимое, в коде скрипта завернем цикл, в котором будем многократно повторять обсчет тиков, что даст нам более адекватные цифры для сравнения.

Важное замечание: нужно один раз прогнать скрипт, чтобы тики подрузились с диска, тогда время обсчета будет зависеть только от скорости скрипта, а не от скорости вашего диска.

Приводить код тестового скрипта не буду, сразу переходим к итогам:

13:39:49.98 : Direct: for: 00:00:14.5599726
13:40:13.65 : Direct: linq: 00:00:23.6646459

Как видим, linq оказался в 2 раза тормознее (ну не совсем в 2, но разница серьезная). Ранее, мы получали еще большую разницу. Почему? Очевидно, что работа с объектами сделок и запрос свойств объектов, дает затраты и эти затраты одинаковы в обоих случаях, но сильно влияют на общую скорость работы.

Давайте для окончательной проверки, возьмем кубик суммирующий объемы всех сделок на покупку/продажу. Его особенность в том, что linq используется правильно, а именно в виде связки

value = trades.Where(trd => trd.Direction == Direction).Sum(trd => trd.Quantity);

Такая связка позволяет избежать создания промежуточных списков и отработать достаточно быстро и удобно.
Посмотрим что у нас получится для обоих вариантов:

13:53:32.71 : Direct: linq: 00:00:24.4801047
13:53:08.23 : Direct: for: 00:00:17.3492000

Разрыв стал меньше, но все равно существенный. Если скрипт работает 17 минут вместо 24-х, это заметно.

Выводы

Статья была написана не с целью написания, а в процессе проведения тестов и оптимизации своих кодов работы с тиками. Это отразилось и на сборке индикаторов RusAlgo, в виде ускорения работы некоторых индикаторов. Ну, и память не вечна, лучше всегда иметь шпаргалку на будущее 🙂 .

Собственно итоги в куче:
- Везде где можно используем стандартные циклы (for,while,do) или foreach. Ускорение до двух раз.
- Если нужна промежуточная коллекция, лучше создать ее в виде списка или массива чем использовать linq. Памяти сожрет больше, скорость будет тоже больше. Ускорение небольшое, но есть, особенно если запрос к коллекции будет не один а много.
- Скоростная обработка тиков это не миф, а реальность 🙂 .

PS: Всем добра. И не стоит заниматься оптимизацией скорости, если не возникла проблема выполнения скриптов в течение минут десяти и более 🙂 .

Родион Скуратовский - ra81

 
 
 
 
 

Вы уже сейчас можете записаться на Видео курс и научиться самому делать зарабатывающих торговых роботов!

Можно записаться на следующий поток ОнЛайн курса, информацию по которому можно посмотреть тут:

Если же вам не хочется тратить время на обучение, то вы просто можете выбрать уже готовые роботы из тех, что представлены у нас!

Также можете посмотреть совершенно бесплатные наработки для МТ4, Квика, МТ5. Данный раздел также постоянно пополняется.

Не откладывайте свой шанс заработать на бирже уже сегодня!

СКОРО СТАРТУЕТ
онлайн-2.0
 
СКИДКИ
VDS-Hosting
 
Банер-скидки-на-коннектор-тслаб
 
Готовые торговые роботы
Спартак1-коробка
 
UPGRADED-FRACTAL
 
SkyLine-коробка
 
На-старт
 
Tunnel-
 
Коробка-коробка
 
купец-бок-коробка
 
наклонный-фрактал-коробка
 
Psar_Adapt-коробка
 
AutoLogin-коробка
 
Адапт-Параболик-коробка
Архив записей

© 2019 Школа по созданию торговых роботов  Войти