Блог им. __rtx
Алексей продолжает серию юмористических постов о том как они идут в колокацию и HFT. Ок, давайте тогда немного поможем коллегам из OsEngine сократив им этот не лёгкий путь накидав немного советов. И возможно(но это не точно) качество кода вырастет(этот пост удалять не буду как обычно) в общем мотивация для команды OsEngine и Алексея развиваться. Не благодарите за бесплатную помощь, я от всей души.
Беглый взгляд на код на гитхабе сразу цепляет мой «токсичный» глаз и не отпускает его почти всё время пока смотришь их код. Куча потенциальных(и не потенциальных) проблем.
Совет номер раз.
— перечитать умные книги по программированию и архитектуре ПО. В частности полюбить один из постулатов «правильных движений» таких как «DRY» или «донт репит ёрселф» или «не повторяйся» тогда кол-во [эскузэмуя]го.н.кода станет сильно меньше чем у Вас сейчас(и 30 000 строк кода у Вас в тестере превратится в кратно меньшее число уменьшив вероятность потенциальных ошибок, упростив поддержку кода для Ваших программистов(которых Вы выручали с иллюстрациями как этот процесс проходил«smart-lab.ru/company/os_engine/blog/1042133.php»)). «DRY» это примерно так:
Ваш код сейчас:
… DataGridViewColumn newColumn0 = new DataGridViewColumn();
newColumn0.CellTemplate = cellParam0;
newColumn0.HeaderText = «Order is active?»;
_gridDataGrid.Columns.Add(newColumn0);
newColumn0.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn1 = new DataGridViewColumn();
newColumn1.CellTemplate = cellParam0;
newColumn1.HeaderText = «Order direction»;
_gridDataGrid.Columns.Add(newColumn1);
newColumn1.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn2 = new DataGridViewColumn();
newColumn2.CellTemplate = cellParam0;
newColumn2.HeaderText = «First order price»;
_gridDataGrid.Columns.Add(newColumn2);
newColumn2.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn3 = new DataGridViewColumn();
newColumn3.CellTemplate = cellParam0;
newColumn3.HeaderText = «Orders count»;
_gridDataGrid.Columns.Add(newColumn3);
newColumn3.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn4 = new DataGridViewColumn();
newColumn4.CellTemplate = cellParam0;
newColumn4.HeaderText = «Step type»;
_gridDataGrid.Columns.Add(newColumn4);
newColumn4.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn5 = new DataGridViewColumn();
newColumn5.CellTemplate = cellParam0;
newColumn5.HeaderText = «Step»;
_gridDataGrid.Columns.Add(newColumn5);
newColumn5.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn6 = new DataGridViewColumn();
newColumn6.CellTemplate = cellParam0;
newColumn6.HeaderText = «Profit type»;
_gridDataGrid.Columns.Add(newColumn6);
newColumn6.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn9 = new DataGridViewColumn();
newColumn9.CellTemplate = cellParam0;
newColumn9.HeaderText = «Profit»;
_gridDataGrid.Columns.Add(newColumn9);
newColumn9.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn7 = new DataGridViewColumn();
newColumn7.CellTemplate = cellParam0;
newColumn7.HeaderText = «Volume type»;
_gridDataGrid.Columns.Add(newColumn7);
newColumn7.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;DataGridViewColumn newColumn8 = new DataGridViewColumn();
newColumn8.CellTemplate = cellParam0;
newColumn8.HeaderText = «Volume»;
_gridDataGrid.Columns.Add(newColumn8);
newColumn8.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; ...
...Action<string> addColumn = headerText => {
var col = new DataGridViewColumn {
CellTemplate = cellParam0,
HeaderText = headerText,
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
};
_gridDataGrid.Columns.Add(col);
};addColumn(«Order is active?»);
addColumn(«Order direction»);
addColumn(«First order price»);
addColumn(«Orders count»);
addColumn(«Step type»);
addColumn(«Step»);
addColumn(«Profit type»);
addColumn(«Profit»);
addColumn(«Volume type»);
addColumn(«Volume»);...
Ну не красота ли? Почему это важно если убрать что сильно сокращает кол-во кода и делает его поддержку сильно проще и приятней? Допустим при очередном рефакторинге или добавлении/удалении какой-то функциональности кто-то из Ваших коллег забыл поменять цифру и появилась ошибка. У Вас по всему коду такое. Вот например ещё(просто пальцем ткни и попадёшь на такие моменты)
… for (int i = 0; i < Lines.Count; i++)
{
DataGridViewRow rowLine = new DataGridViewRow();rowLine.Cells.Add(new DataGridViewTextBoxCell());
rowLine.Cells[0].Value = i + 1;DataGridViewComboBoxCell cell1 = new DataGridViewComboBoxCell();
cell1.Items.Add(true.ToString());
cell1.Items.Add(false.ToString());
cell1.Value = Lines[i].IsOn.ToString();
cell1.ReadOnly = false;
rowLine.Cells.Add(cell1);rowLine.Cells.Add(new DataGridViewTextBoxCell());
rowLine.Cells[2].Value = Math.Round(Lines[i].PriceEnter, 10);
rowLine.Cells[2].ReadOnly = false;rowLine.Cells.Add(new DataGridViewTextBoxCell());
rowLine.Cells[3].Value = Math.Round(Lines[i].PriceExit, 10);
rowLine.Cells[3].ReadOnly = false;rowLine.Cells.Add(new DataGridViewTextBoxCell());
rowLine.Cells[4].Value = Math.Round(Lines[i].Volume, 10);
rowLine.Cells[4].ReadOnly = false;rowLine.Cells.Add(new DataGridViewTextBoxCell());
rowLine.Cells[5].Value = Lines[i].Side;rowLine.Cells.Add(new DataGridViewCheckBoxCell());
rowLine.Cells[6].Value = Lines[i].checkStateLine;
rowLine.Cells[6].ReadOnly = false;_gridDataGrid.Rows.Add(rowLine);
}...
Это ботва называется.))) Knight Capital потеряла 400 млн. долларов из-за того что кто-то забыл обновить ПО на одном из серверов. Т.е. чтобы сократить вероятность ошибок люди используют «DRY». А Вы?
Совет номер два.
— не ленитесь использовать лямбда функции. Их использование делает код элегентным и читаемым(это как инвестиция в него т.к. потраченное время сейчас чтобы подумать в будущем позволяет не тратить на это время чтобы понять что тут делается, отловить ошибки и т.д.) + лямбда функции не засоряют документацию т.к. используются ровно там где нужны и не больше. Т.е. на гитхабе будет меьше названий методов а в документации не надо думать и описывать метод которые имеет локальное назначение в несколько «микромоментов» и не несёт никакой пользы тому кто читает про него и видит его описание. Когда кол-во кода(как у Вас) увеличивается в геометрической прогрессии такой код надо по максимуму ограждать от потенциальных проблем. На заметку Вам и Вашим «технологическим спецназовцам»(как Вы сами у себя в постах себя пытаетесь преподнести) --> Если при написании кода руки хотят «замиксовать» число со словом сначала подумайте возможно тут надо замутить лямбда функцию либо обычную если то что делает код делает ещё какой-то кусок в коде(в другом месте). Числа и слова так лучше не делать.
Совет номер три.
— не работайте со строками в местах где не надо с ними работать например в одном из коннекторов(где в моменты движений будет очень горячо) у Вас сравниваются строки:
...List<OrderChange> bidsByPrice = orderChanges.FindAll(p => p.OrderType == «bid»);...
как я писал в прошлом посте всего лишь надо сделать(в с++ например так)
...enum class Direction: uint8_t {
BID = 1,
ASK = 2
}
и List<OrderChange> bidsByPrice = orderChanges.FindAll(p => p.OrderType == BID);
Ок что там у нас по плазе интересно?))) Заходим в код и начинается праздник.
...if (replmsg.MsgName == «deal»)
… //- if id_ord_sell < id_ord_buy, то Side = buy/если id_ord_sell < id_ord_buy, то Операция = Купля
//- if id_ord_sell > id_ord_buy, то Side = sell/если id_ord_sell > id_ord_buy, то Операция = Продажа...
...ActiveSide 0x20000000000 Активная сторона в сделке. Заявка, приведшая к сделке при добавлении в стакан.
...PassiveSide 0x40000000000 Пассивная сторона в сделке. Заявка из стакана, участвующая в сделке.
Двигаемся дальше. Тут видно что в OsEngine пишут код очень весёлые ребята и любят конвертацию(прямо какая то маниакальная тяга к конвертации типов и работе со строками)
всё бы ничего только вот эта строка кажется как-то странно выглядит:
...trade.Price = Convert.ToDecimal(replmsg[«price»].asDecimal());
Как будто бы и так сгодилось:
...trade.Price = replmsg[«price»].asDecimal();
Коллеги которые пользуются софтом OsEngine сейчас должны хорошо взвесить все за и против т.к. просто взглянув на код тут такая дичь творится(а Алексей на голубом глазу шагает в колокацию за HFT как настоящий алгоспецназ(картинка помнится мне была такая лень искать ссылку но все наверное видели и так))
{
// ticks/тики
try
{
//- if id_ord_sell < id_ord_buy, то Side = buy/если id_ord_sell < id_ord_buy, то Операция = Купля
//- if id_ord_sell > id_ord_buy, то Side = sell/если id_ord_sell > id_ord_buy, то Операция = Продажаbyte isSystem = replmsg[«nosystem»].asByte();
// если сделка внесистемная, пропускаем ее
if (isSystem == 1)
{
return 0;
}Trade trade = new Trade();
trade.Price = Convert.ToDecimal(replmsg[«price»].asDecimal());
trade.Id = replmsg[«id_deal»].asLong().ToString();
trade.Time = replmsg[«moment»].asDateTime();
trade.Volume = replmsg[«xamount»].asInt();
string securityNameCode = replmsg[«isin_id»].asInt().ToString();
...ToString()… Просто обожают коллеги работу со строками даже в «горячих потоках» что выглядит странно(почему? ответ на эту загадку в конце поста).
вот это иначе как извращение никак не назовёшь:
приходят данные(уже куча косяков которые оставили OsEngine далеко позади в плане реакции на событие) что делает код коллег он число конвертирует в строку(напомню что более 25 млн. тиков проходит эту и то что описано выше) и сравнивает
...
string securityNameCode = replmsg[«isin_id»].asInt().ToString();
...
Security security = _securities.Find(s => s.NameId == securityNameCode);
почему не сделать s.NameId числом чтобы просто получать и сравнивать а не заниматься постоянно конвертацией строк и их сравнением. Это же дикие тормоза для софта.
это
...
if (numberBuyOrder > numberSellOrder)
{
trade.Side = Side.Buy;
}
else
{
trade.Side = Side.Sell;
}...
надо сделать так
trade.Side = numberBuyOrder > numberSellOrder? Side.Buy: Side.Sell;
( в одну строку вместо 8)
…
portfolio.ValueBegin = Convert.ToDecimal(replmsg[«money_old»].asDecimal());
portfolio.ValueCurrent = Convert.ToDecimal(replmsg[«money_amount»].asDecimal());
portfolio.ValueBlocked = Convert.ToDecimal(replmsg[«money_blocked»].asDecimal());
зачем Convert.ToDecimal если оно уже возращает asDecimal? Добавьте ещё цикл и так раз 50 из одного типа в другой попереводите и когда эта весёлая игра закончится можно и поторговать дальше «OsEngine HFT». Почему сразу не сделать в portfolio у всех тип Decimal вместо того чтобы лишние вызовы делать?
Совет номер три.
— перечитайте книги по архитектуре ПО в местах где написано про «модель — вью — контроллер». У Вас в коде работа с данными ГУЯ напрямую. Даже если бы было через Invoke и даже если бы всё в одном потоке(а у Вас в отдельном) то всё равно чтобы данные были актуальными нужно использовать атомарные операции т.к. обычные переменные если их передавать на ГУЙ будут сильно(в контексте HFT) отличаться т.е. аск на ГУЕ и аск на бэкэнде это будут совсем разные аски(даже со всеми асинхронными «Invoke делами»). В с++ для того чтобы было видно между потоками есть atomic, volatile. В си шарп думаю тоже есть аналоги.
...
private void CanselAllOrders()
{
List<Position> openPositions = _tab.PositionsOpenAll;Position[] poses = openPositions.ToArray();
for (int i = 0; poses != null && i < poses.Length;i++)
{
if(poses[i].State != PositionStateType.Open)
{
_tab.CloseAllOrderToPosition(poses[i]);
}
Thread.Sleep(200);
}
Thread.Sleep(1000);
...
думаю тут всё очевидно но позволю себе просмаковать этот момент. После такого кода вывеска «OsEngine HFT» достойна стать мемом коллеги.))) Слип есть даже в цикле.))) И в догонку при выходе из цикла ещё на секунду припаркуемся. «OsEngine HFT» ведь в колокации, стало быть торопиться некуда. Это наверное сделано для того чтобы с запасом(подождать ответов на транзакции). Но для понимания пока идёт это ожидание коллега из Владивостока уже сможет отправить заявки и получить на них ответ несколько кругов. Почему это делается тоже понять можно 7 000 000 кода с кучей преобразований(которые не нужны) в строку и обратно, работа со строками там где это противопоказано, кучей io bound операций, переключением контекста(там у них ведь потоков наверное ещё больше чем дублирования кода) и т.д. делает код дико тормозным и если не делать Thread.Sleep(1000) то терминал «замёрзнет» в ожидании пока процессор всё что написано сделает. Т.е. как бы ни собирался Алексей в походы за HFT в контексте лоу латенсей это никак не получится по определению, даже если поправит и сильно улучшит качество кода т.к. «за скоростью» надо идти «на легке» а не со всей приблудой да ещё так коряво написанной. Я когда писал коннектор к TWIME мог взять примеры на с++ на гитхаб но я этого не делал т.к. меня интересует не накопление чужого го.н.кода а качественный свой софт. Поэтому всегда сам и пишу. Например TWIME был написан с помощью снифера, блокнота, документации и с++ чтобы не было ничего лишнего, так дольше чем взять и переделать но зато я знаю каждый знак для чего нужен в своём коде.
Ладно удачи Алексею и пользователям его софта в HFT(присоединяюсь к его поздравлениям самим себе вот тут в конце поста(«smart-lab.ru/company/os_engine/blog/1178501.php»))
… Ещё раз поздравляю нас с выходом в зону HFT...
Ок.))) Жгите коллеги.
Но если честно немного жаль тех кто пользует OsEngine для того чтобы скорость, HFT и всё такое т.к. даже Thread.Sleep(1000) уже закрывает эту возможность напрочь. А ведь у кого-то могут быть действительно правильные мысли по поводу хфт но использование OsEngine делает невозможным это реализовать. Поэтому коллеги кто действительно хочет хфт пишите свой софт. Качество софта OsEngine относительно «накидываемых понтов» может вызывать только такие ассоциации.

Теперь буду ждать цикл постов о том как OsEngine делает рефакторинг кода. Это будет намного более увлекательней чем те которые были до этого. А может и не будет таких постов т.к. Алексей ведь больше про маркетинг чем про всё остальное.
ну и критичный к скорости код я бы писал на си а не на с++…
Andy, Единая система программной документации — Википедия
там их много… но знать нужно все...
вообще я видел только одну контору которая соблюдала этот гост...
помню пришел в одну контору а там прогеры чтоб их не уволили стерли все коменты и из документации помимо 60к строк кода на ассемблере была блок схема на формате а1 желтая от времени в пятнах от пролитого кофе порванная на складках и подклеенная изнутри газеткой аиф… это прям был как священный артефакт...
я начальству прямо задал вопрос а где все остальное… вообщем не взяли мя на работу...
Критичный к скорости код следует писать прежде всего понимая, что делаешь, а не наоборот.
Да проекту трындец. ИИ окончательно обнулил все эти усилия. Кто в здравом уме теперь будет вибирать что-то такое, когда ИИ пишет и коннектор, и стратегию, и даже тесты выдает. Как раз самое то, кто в поисках граалей в готовом коде. Основная категория — кто не знает даже что такое компиляция — уходит от таких проектов к Соннету и ГПТ. Этот тренд уже даже не в трейдинге. Он вообще в целом. Видно затухание активности Опен Сорса. ИИ бьет по SOE и самой парадигме коворкинга в опен сорс репах.
Я правильно понял, что у них там общение между потоками через простые переменные? О сколько нам открытий чудных...
std::atomic<> кстати не поможет, тем более volatile (он вообще для другого).
Слипы — прикольно )
С пенальти странно, конечно. Можно просто добавить пенальти к now и проверять на входе без блокировок.