Stock# с несколькими квиками
Atom Ответить
03.08.2010


Со сколькими копиями квика можно безболезненно запускать одного
робота?
Как происходит экспорт через DDE в Stock# - одинаковые данные, я так
понимаю, фильтруются?

Вопрос возник не случайно - сейчас с 7ми квиками роботы съедают до
50-60% от нашего довольно мощного сервера (на каждом квике запущен 1-2
робота, каждый робот запускается 1 секунду). Стоит ли искать ошибку,
пытаться оптимизировать самого робота или лучше закинуть часть квиков
на другой сервер?

Теги:


Спасибо:



Поздравляем именинников: Станислав Гайворонский

48 Ответов
1 2  >
Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 04.08.2010
Ответить


1. Да, одинаковые данные отбрасываются. Если это разные Квики, то это
будут все сделки и инструменты (если конечно у Вас не шарица еще что-
то). Хотя и сделки могут быть уникальными, если каждый Квик торгует
только свой диапазон инструментов.
2. А кто съедает? Робот или Квики? Роботов несколько или же он только
один? Распределение 50-60 по прогам можете привести?

Спасибо:

Alexander

Фотография
Дата: 05.08.2010
Ответить


Съедает сам робот. Он запущен только 1, просто там идёт работа со
многими квиками.
Что это за распределение и как его сделать? :)

Ещё я заметил следующее при работе с несколькими квиками - возьмём, к
примеру, таблицу Всех сделок. Она передаётся насколько я понимаю из
какого-то одного квика (к примеру, который был первым добавлен в
AggregatedTrades), из остальных игнорируется. Как только теряется
связь с сервером у первого квика (остальные при этом работают) -
таблица всех сделок перестаёт экспортироваться (хотя могла бы из
других квиков) - получается нельзя таким образом подстраховаться...

Автор топика
Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 05.08.2010
Ответить


Можно через Process Explorer. Он покажет, какие потоки тормозят. А
потокам можно понять, что в них делается. Все S# потоки именованы.
Робота, если Вы этого не делали, не именованы.

Насчет прекращения экспорта. Схема не такая. Роботу льется все. А он
уже смотрит на уникальность. Так что, если какой-то из Квиков лили
дубли и он упал, то на экспорт это не должно отразиться. Тут случаем
ReConnectionManager не вступает в работу? Вот он может все остальные
Квики перезапускать.

Спасибо:

Alexander

Фотография
Дата: 05.08.2010
Ответить


ðÏËÁÚÁÌ, ÔÏÒÍÏÚÉÔ LogHelp, ×ÏÔ StackTrace:
ntkrnlpa.exe!NtBuildNumber+0xe
ntkrnlpa.exe!RtlUpcaseUnicodeString+0xa2
ntkrnlpa.exe!ZwYieldExecution+0x4f3
ntkrnlpa.exe!MmIsDriverVerifying+0xbb2
hal.dll+0x2ef2
clr.dll+0x1a5e
clr.dll!CoUninitializeEE+0x378e
clr.dll!CoUninitializeEE+0x7663
clr.dll!LogHelp_TerminateOnAssert+0x6a1fd
clr.dll!LogHelp_TerminateOnAssert+0x3c642
clr.dll!LogHelp_TerminateOnAssert+0x3c560
clr.dll!LogHelp_TerminateOnAssert+0x3c6de
clr.dll!LogHelp_TerminateOnAssert+0x40b22
clr.dll!LogHelp_TerminateOnAssert+0x3aab2
mscorlib.ni.dll+0x2aae5b
mscorlib.ni.dll+0x237ff4
mscorlib.ni.dll+0x237f34
mscorlib.ni.dll+0x2aade8
clr.dll+0x21db
clr.dll!CoUninitializeEE+0x6862
clr.dll!CoUninitializeEE+0x6a04
clr.dll!CoUninitializeEE+0x6a39
clr.dll!GetPrivateContextsPerfCounters+0x8a13
clr.dll!LogHelp_TerminateOnAssert+0x591ad
clr.dll!LogHelp_TerminateOnAssert+0x5922f
clr.dll!LogHelp_TerminateOnAssert+0x592ea
clr.dll!LogHelp_TerminateOnAssert+0x59381
clr.dll!GetPrivateContextsPerfCounters+0x88e6
clr.dll!GetPrivateContextsPerfCounters+0x87e1
clr.dll!LogHelp_TerminateOnAssert+0x58fb0
KERNEL32.dll!GetModuleFileNameA+0x1ba

ReConnectionManager, ËÏÎÅÞÎÏ, ×ÓÔÕÐÁÅÔ × ÒÁÂÏÔÕ ÅÓÌÉ ÏÄÉÎ ÉÚ Ë×ÉËÏ×
ÕÐÁÌ, Á ÄÒÕÇÏÊ - ÎÅÔ. ô.Å., ×ÉÄÉÍÏ, ÓÔÏÉÔ ReconnectionManager
ÎÁÚÎÁÞÁÔØ ÏÔÄÅÌØÎÏ ÄÌÑ Ë×ÉËÏ×, Á ÎÅ ÄÌÑ ×ÓÅÇÏ MultiTrader?

Автор топика
Спасибо:

Alexander

Фотография
Дата: 05.08.2010
Ответить


Отключил логгирование для стратегий (убрал обработчик OnLog и не
добавляю StrategyLogger) - теперь робот ест от 0 до 50% процессора.
50% - крайне редко, всплески. в основном висит на 0%. т.е. разница
колоссальная.

Автор топика
Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 05.08.2010
Ответить


Хм, или логов много или дисковая операция тяжелая. Есть вероятность
что StrategyLogger глючит, он там так мало кода. Фактически, вот он
весь (что используется при активизации события):

_writer = new StreamWriter(logDirectory.IsEmpty() ? this.FileName :
Path.Combine(logDirectory, fileName)) { AutoFlush = true };

private void StrategyLog(Strategy strategy, StrategyErrorStates state,
string message)
{
WriteMessage(strategy + " " + message);

/// <summary>
/// Записать специальное сообщение, которое относится ко всем
статегиям <see cref="Strategies"/>.
/// </summary>
/// <param name="message">Сообщение.</param>
public void WriteMessage(string message)
{
if (message.IsEmpty())
throw new ArgumentNullException("message");

Debug.WriteLine(message);
_writer.WriteLine("{0} {1}", DateTime.Now.TimeOfDay, message);

Спасибо:

Иванов Андрей

Фотография
Дата: 07.08.2010
Ответить


Про тяжёлую дисковую операцию не понял =) Дисковые операции едят время
(iowait), а не CPU.

Про много лог-записей в точку -- операция конкатенации самая дорогая
по CPU в .NET (из тех, что не ожидаешь).
Единственный способ борьбы это писать сразу в файл. Если убрать
конкатенацию в _writer.WriteLine(..), производительность логирования
вырастет в полтора раза =)

То есть, оформить запись в виде трёх вызовов -- время, разделитель,
сообщение.

Захотите проверить мою теорию, включите логирование, а все строки с
конкатенацией замените на строки-константы примерно той же длины.

Ещё эффективнее по CPU будет, если событие лога генерировать не со
строкой конкатенации стратегии и сообщения, а объектом с двумя полями
или вызовом метода с двумя параметрами.

Единственный вариант привязать к CPU запись на диск это отключить DMA
для ATA-дисков. Я никогда не сталкивался с необходимостью такое
делать, но можно попровать начать с этого -- проверять надо в дисковом
контроллере, на всех системах названия свои. Раньше было переключение
между режимами (PIO/*DMA), в семёрке чекбокс выключения DMA.

Спасибо:

Alexander

Фотография
Дата: 08.08.2010
Ответить


логов много - каждая запущенная стратегия каждую секунду что-то да
пишет. про тяжёлую дисковую операцию я тоже не понял =)

Хотелось бы ещё закрыть вопрос с прекращением экспорта - я так понимаю
в случае с MultiTrader необходимо ReconnetionManager создавать для
каждого из добавленных AggregatedTraders? В этом случае экспорт не
прекратится если отвалится лишь один из квиков

Автор топика
Спасибо:

Иванов Андрей

Фотография
Дата: 09.08.2010
Ответить


У меня на процессоре Q6600 конкатенация съедала одно ядро примерно за
100 тысяч записей в секунду.
Если у вас 100 стратегий с тайм-фреймом секунда, современный процессор
не заметит этого -- тайм-фрейм должен быть 10 миллисекунд. У вас либо
очень много стратегий (100 тысяч), либо они не все работают с тайм-
фреймом, либо предположение о конкатенации неверно.

Посчитайте, сколько у вас записей в лог за секунду делается. Вместо
записи в лог инкрементируйте счётчик через

Interlocked.Increment(ref _logCounter);

и пишите это значение куда-нибудь раз в секунду примерно вот так:

int count = Interlocked.Exchange(ref _logCounter, 0);
Console.WriteLine("Log rps: {0}", count);

_logCounter -- глобальная переменная. Стратегии вроде сами по себе
ничего не пишут в лог, поэтому вы можете получить точное значение.

Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 11.08.2010
Ответить


private class MyStrategy : Strategy
{
public void DoLog(string message)
{
base.AddLog(StrategyErrorStates.None, message);
}

protected override bool OnProcess()
{
return false;
}

var str = new MyStrategy();

var logger = new StrategyLogger("1.txt");
logger.Strategies.Add(str);

const int count = 100000;

var t1 = Watch.Do(() =>
{
for (var i = 0; i < count; i++)
{
str.DoLog("111");
}

logger.Strategies.Remove(str);

var writer = new StreamWriter("2.txt") { AutoFlush = true };

str.Log += (s, e, m) =>
{
writer.Write(DateTime.Now.TimeOfDay);
writer.Write(" ");
writer.Write(s);
writer.Write(" ");
writer.WriteLine(m);

var t2 = Watch.Do(() =>
{
for (var i = 0; i < count; i++)
{
str.DoLog("111");
}

ňĹÚŐĚŘÔÁÔ ÎÁ ÍĎĹÍ ÍÁŰÉÎËĹ. t1 - 55 ÓĹËŐÎÄ. t2 - 1 ÓĹËŐÎÄÁ. îÉŢĹÇĎ
ÓĹÂĹ! áÎÄŇĹĘ, ÖÄŐ ËĎÍÍĹÎÔÁŇÉĘ. đĎÔĎÍŐ ËÁË Ń ÄĎ ÓÉČ ĐĎŇ ÓŢÉÔÁĚ, ŢÔĎ
ĎĐĹŇÁĂÉŃ ÚÁĐÉÓÉ × ĆÁĘĚ ÍÎĎÇĎ ÄĎŇĎÖĹ ËÁËĎĘ-ÔĎ ËĎÎËÁÔĹÎÁĂÉÉ ÓÔŇĎË.

Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 11.08.2010
Ответить


Можете сделать проще. Отнаследоваться от QuikTrader, переопределить
ReStartExport и там прописать свою логику проверки - нужно
перезапускать экспорт или не нужно.

Спасибо:

Иванов Андрей

Фотография
Дата: 12.08.2010
Ответить


Комментарий о чём? ;) У вас синтетический случай, поэтому такой
огромный прирост.

Если вопрос "почему?", то потому что когда пишете сразу в файл,
копирование в памяти всего одно, а когда у вас несколько раз
осуществляется операция конкатенации, вы копируете данные много раз.
Из неочевидного строки создаются на хипе, а не на стеке, их создаётся
много и GC освобождает занимаемую ими память. В случае 2 выделения/
освобождения памяти практически нет, все данные в кэше процессора,
поэтому он работает на максимальной скорости, а не спотыкается об
ожидание данных из медленной памяти.

Ну а запись в файл, как вы уже заметили, происходит очень быстро =)
Потому что вывод буферизованный -- когда вы делаете 5 вызовов вместо
одного, они все пишут в один буфер и оверхеада по сравнению с записью
всей строки одним вызовом почти нет.

Если будете увлекаться файлами, столкнётесь с ещё одной неочевидной
штукой. Называется IOPS. Пока читаете/пишете линейно, скорости
обычного SATA будет достаточно -- придумана куча технологий для
ускорения линейного доступа (кэши, буферы, группировка вызовов
операционной системой, NCQ), но как только чтение станет случайным,
пиковые 400-500 IOPS SAS-диска за тысячу долларов покажутся жалкой
пародией на скорость =) Потому что в переводе на мегабайты получится
чуть больше 10 мегабайтов в секунду, это в 10-15 раз меньше
современного бытового SATA-диска на линейном доступе.

Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 12.08.2010
Ответить


В том то и дело, что я устанавливаю AutoFlush = true. А это отключает
всякую буферизацию. А если бы был буфер, я правильно понимаю, что
сброс буфера в файл происходит асинхронно?

Насчет хипа - я так и думал. string - это ведь класс. Но ведь буферы у
StreamWriter наверняка хранят не массив object, а массивы byte[].
Последние - это перевод переданных строк в файловой представление. Так
вот, а массивы так же хранятся в хипе, и так же нужно время на перевод
из строки в byte[]. Где же тогда экономия?

Про рандом и последовательный доступ понятно. Я читал об этом раньше.
Надеюсь в S# не придется с этим столкнутся. Не хотелось бы погрязнуть
в системных тонкостях =)

Спасибо:

Иванов Андрей

Фотография
Дата: 13.08.2010
Ответить


AutoFlush занимается тем, что сбрасывает буферы самого StreamWriter в
подлежащий стрим. Использование этого флага с файлами ничего не
меняет. Писать можно без буферов, потому что под StreamWriter должен
быть какой-то FileStream, который сам будет буферизовать вывод, плюс
есть буфер ядра.

Выключить буферизацию в ядре можно только ключом в реестре на всю
систему или открывая файл. Отключить буфер после открытия файла, по-
моему, невозможно -- необходимость буферизации известна до
использования файла. По умолчанию буфер ядра в Windows Server
несколько мегабайтов, сколько он в десктопах, не знаю. В десктопах и
серверах разные схемы кэширования и буферов по умолчанию, в серверах
сейчас (2008/2008R2) кэширование по умолчанию может съесть всю память
и программам придётся свапиться =)

Сброс буфера ядра асинхронный. Сбросы прикладных буферов синхронные.

Массивы буферов не создаются и не удаляются 100 тысяч раз в секунду =)
Они один раз создаются и живут какое-то продолжительное время.
Когда вы делаете s1 += s2;, вы создаёте один объект (результат
конкатенации s1 и s2) и отдаёте GC один объект (тот, на который до
операции указывал s1). В случае с буферами все остаются при своих,
происходит только одно копирование. Самый шик, когда строка не
помещается в Gen 0 и отправляется в LOH -- в этом случае основную
часть процессорного времени занимает удаление объектов =) Но это не
наш случай, потому что строка должна быть больше 40 тысяч обычных
символов (а необычных, типа индийских, ещё на треть меньше).

Спасибо:

Alexander

Фотография
Дата: 15.08.2010
Ответить


т.е. перезапускать экспорт только в том случае, если нет подключения:

public class OwnQuikTrader : QuikTrader
{
public OwnQuikTrader(string path, string ddeServer, string
dllName) :
base(path, ddeServer, dllName)
{
}

public override void ReStartExport()
{
if (!IsConnected)
base.ReStartExport();
}
}

?

Автор топика
Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 16.08.2010
Ответить


Да, так, если такого поведения достаточно.

Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 16.08.2010
Ответить


Массив буферов создается один раз, согласен (возможно еще как-то
увеличивается уменьшается, но это не так важно). Но сами то буферы
нет. Они создаются так же часто, как мы вызываем Write. string в
byte[] - создаем, int в byte[] - создаем, DateTime в byte[] -
создаем... Тоесть, в любом случаем необходимо произвести конфертацию,
а это создание массива байтов.

Копирование byte[] в массив буферов?

Спасибо:

Иванов Андрей

Фотография
Дата: 17.08.2010
Ответить


Не, массивы байтов не создаются, создаются строки. Много-много строк
(иногда char[]).

Для начала форматная строка парсится (это TextWriter.Write).

public virtual void Write(string format, object arg0, object arg1)
{
this.Write(string.Format(this.FormatProvider, format, new object[]
{ arg0, arg1 }));

Из-за того, что выделенной памяти не хватило на создание конечной
строки (размер форматной + 16 символов), происходит реаллок с
выделением памяти в два раза больше и копированием. При парсинге
форматной строки аллоки поменьше для внутреннего использования.
Копирований, соответственно, пачка.
Тут основная часть CPU и съедается.

Я писал сделки в файлы инструментов и каждый раз (запись сделки)
считал путь к имени файла инструмента. Кэширование этой операции дало
примерно пятикратный прирост скорости записи. Закэшировал остальные
операции вычисления пути (тоже просто конкатенации и Path.Combine),
прирост ещё раза в два.

Форматная строка вида

string dealStr =
string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0};
{1:T};{2};{3};{4};{5}", _id, _timeStamp, _price, _count, _volume,
_operation.HasValue ? _operation.ToString() : "0");

по скорости почти идентична просто конкатенации или StringBuilder.
Предположу, что здесь хватило выделенного начального буфера (40
символов) и форматтер InvariantCulture работает быстрее
CurrentCulture. Выяснять лениво, мне достаточно того, что так писать
красивее, чем через + и работает некритично медленнее.

Писать напрямую в файл не стал, потому что после включения кэширования
путей время полного копирования сделок за сутки составляет секунд 10.
А если вместо форматной строки выше писать константу такой же длины,
скорость пару секунд. 2 секунды или 10, роли не играет.

Спасибо:

Alexander

Фотография
Дата: 23.08.2010
Ответить


Опять проблема возникла с запуском нескольких стратегий на нескольких
квиках.
Сейчас на сервере запущено 7 квиков и суммарно должно было бы
запуститься 13 стратегий.

Я запускаю следующим образом:

//цикл по всем стратегиям
//для каждой из стратегий получаю список из счетов для которых данная
стратегия не была запущена
TimeFrameStrategy tfStrategy = null;
//в зависимости от стратегии tfStrategy инициализирую конструктором
нужного класса (наследуемого как раз от TimeFrameStrategy)
//регистрирую ТФ

tfStrategy.Log += Strategy_OnLog;
var newDateTimeDir = string.Format("Logs\\{0}_{1:00}_{2:00}",
DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
if (!Directory.Exists(newDateTimeDir))
{
Directory.CreateDirectory(newDateTimeDir);

var logger = new StrategyLogger("{0}\\_{1}
_{2}.txt".Put(newDateTimeDir,
Enum.GetValues(typeof (StrategiesName)).GetValue(i),
account.Account));
logger.Strategies.Add(tfStrategy);

_strategyManager.Register(tfStrategy, port, riFut); // где port -
портфель нужный, riFut - фьюч из Securities. и то и то не null
tfStrategy.Start();

В результате у меня для всех 13 стратегий создаются логи, но работают
не все (в разные запуски разное число, от 4 до 8 было) - в логе
неработающих стратегий лишь строчка
11:06:42.8281250 Volume_1_01:00:05 Volume_1 запущена.
и ничего более, хотя при каждом вызове OnProcess в лог должно что-то
писаться.

Вот сейчас в 8 стратегиях из 13 пишется, в 5 - лишь 1 эта строчка.

С чем это связано?

Автор топика
Спасибо:

Alexander

Фотография
Дата: 23.08.2010
Ответить


Попробовал запускать стратегии после того, как добавил их в
StrategyManager - не помогло, последние 5 из добавленных стратегий всё
равно не работают - в лог пишут лишь 1 строку.

Автор топика
Спасибо:

Alexander

Фотография
Дата: 24.08.2010
Ответить


Оказывается, это было связано с тем, что не хватало потоков для
StrategyManager (я использовал конструктор без параметра для числа
потоков) - получал на выходе лишь 8 потоков. Сегодня попытался
создавать и указывать 15 потоков - всё заработало.
Только непонятно насколько в таком случае он быстро будет работать =)

Есть небольшая просьба - можно ли в случае если используется
конструктор StrategyManager по умолчанию (в который передаётся лишь
ITrader) в случае, если число добавленных стратегий больше, чем число
потоков, автоматически разбрасывать стратегии по потокам и запускать
несколько стратегий на одном ядре? А то такой тихий незапуск стратегий
как у меня очень смущает, проблема не очевидна первоначально.

Автор топика
Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 24.08.2010
Ответить


Не, проблема в пуле потоков. Должно обрабатывать необработанные, а
обрабатывает - обработанные. Ок, посмотрю.

Спасибо:

Maxim

Фотография
Дата: 28.04.2011
Ответить


Mikhail Sukhov Перейти
1. Да, одинаковые данные отбрасываются. Если это разные Квики, то это
будут все сделки и инструменты (если конечно у Вас не шарица еще что-
то). Хотя и сделки могут быть уникальными, если каждый Квик торгует
только свой диапазон инструментов.


Михаил, небольшое уточнение.
Предположим используется MultiTrader с двумя разными Квиками.
В этих Квиках настроены «Все сделки» и «Стаканы» для одной бумаги, например Сбера.

Выше Вы написали, что данные поступают от двух Квиков одновременно.
Дублирующие данные учитываются и отбрасываются.

Верно ли это для «Стаканов»?
Спасибо:

Maxim

Фотография
Дата: 28.04.2011
Ответить


Mikhail Sukhov Перейти

Насчет прекращения экспорта. Схема не такая. Роботу льется все. А он
уже смотрит на уникальность. Так что, если какой-то из Квиков лили
дубли и он упал, то на экспорт это не должно отразиться. Тут случаем
ReConnectionManager не вступает в работу? Вот он может все остальные
Квики перезапускать.


Насколько я понял из документации, для MultiTrader отсутствует ReConnectionManager.
То есть выше Вы имели ввиду, что вступает в работу ReConnectionManager для одного из Квика, который используется в MultiTrader.

Как может ReConnectionManager одного Квика перезапустить все остальные Квики?
Или я что то не понял?
Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 28.04.2011
Ответить


Maxim Перейти
Как может ReConnectionManager одного Квика перезапустить все остальные Квики?
Или я что то не понял?


С тех пор много воды утекло.
Спасибо:
1 2  >

Добавить файлы через драг-н-дроп, , или вставить из буфера обмена.

loading
clippy